良好實(shí)踐,這次主要挑了一些開發(fā)PHP應(yīng)用時(shí)應(yīng)該運(yùn)用上的良好實(shí)踐進(jìn)行詳細(xì)記錄,特別是良好實(shí)踐部分中密碼和流兩個(gè)點(diǎn)。關(guān)于代碼風(fēng)格、(我個(gè)人)常用或者常見的做法會(huì)簡(jiǎn)單帶過。
二、標(biāo)準(zhǔn) 如果你了解PHP-FIG和PSR可以跳過這部分
PHP組件和框架的數(shù)量很多,隨之產(chǎn)生的問題就是:?jiǎn)为?dú)開發(fā)的框架沒有考慮到與其他框架的通信。這樣對(duì)開發(fā)者和框架本身都是不利的。
打破舊局面的PHP-FIG
多位PHP框架的開發(fā)者認(rèn)識(shí)到了這個(gè)問題,在2009年的 php|tek(一個(gè)受歡迎的PHP會(huì)議)上談?wù)摿诉@個(gè)問題。經(jīng)過討論后得出:我們需要一個(gè)標(biāo)準(zhǔn),用來提高框架的互操作性。于是這幾位在php|tek意外碰頭的PHP框架開發(fā)者組織了PHP Framework Interop Group,簡(jiǎn)稱PHP-FIG。
PHP-FIG是框架代表自發(fā)組織的,其成員不是選舉產(chǎn)生的,任何人都可以申請(qǐng)加入PHP-FIG,并且能對(duì)處于提議階段的推薦規(guī)范提交反饋。另外,PHP-FIG發(fā)布的是推薦規(guī)范,而不是強(qiáng)制規(guī)定。
1.PSR是什么?
PSR是PHP Standards Recommendation(PHP推薦標(biāo)準(zhǔn))的簡(jiǎn)稱。截至今日,PHP-FIG發(fā)布了五個(gè)推薦規(guī)范:
你會(huì)發(fā)現(xiàn)只有四個(gè),沒錯(cuò),因?yàn)榈谝环萃扑]規(guī)范PSR-0廢棄了,新發(fā)布的PSR-4替代了。
2.PSR-1:基本的代碼風(fēng)格
如果想編寫符合社區(qū)標(biāo)準(zhǔn)的PHP代碼,首先要遵守PSR-1。遵守這個(gè)標(biāo)準(zhǔn)非常簡(jiǎn)單,可能你已經(jīng)再使用了。標(biāo)準(zhǔn)的細(xì)節(jié)就不寫啦,點(diǎn)鏈接就能看。
3.PSR-2:嚴(yán)格的代碼風(fēng)格
PSR-2是在PSR-1的基礎(chǔ)上更進(jìn)一步的定義PHP代碼規(guī)范。這個(gè)標(biāo)準(zhǔn)解決了很多世紀(jì)問題哈,比如縮進(jìn),大括號(hào)等等。細(xì)節(jié)也不多記錄啦。
另外,現(xiàn)在很多IDE(比如,PHPStorm)會(huì)有代碼格式化功能,設(shè)置代碼格式化的標(biāo)準(zhǔn),編寫完代碼,然后全部格式化,可以幫助你遵循推薦規(guī)范,修復(fù)一些換行、縮進(jìn)、大括號(hào)等細(xì)節(jié)。

4.PSR-3:日志記錄器接口
這個(gè)推薦規(guī)范與前兩個(gè)不同,這是一個(gè)接口,規(guī)定PHP日志記錄器組件可以實(shí)現(xiàn)的方法。符合PSR-3推薦規(guī)范的PHP日志記錄器組件,必須包含一個(gè)實(shí)現(xiàn)Psr\Log\LoggerInterface接口的PHP類。PSR-3接口復(fù)用了RFC 5424系統(tǒng)日志協(xié)議,規(guī)定要實(shí)現(xiàn)的九個(gè)方法:
<?php
namespace Psr\Log;
interface LoggerInterface
{
public function emergency($message, array $context = array());
public function alert($message, array $context = array());
public function critical($message, array $context = array());
public function error($message, array $context = array());
public function warning($message, array $context = array());
public function notice($message, array $context = array());
public function info($message, array $context = array());
public function debug($message, array $context = array());
public function log($level, $message, array $context = array());
}
每個(gè)方法對(duì)應(yīng)RFC 5424協(xié)議的一個(gè)日志級(jí)別。
使用PRS-3日志記錄器
如果你正在編寫自己的PSR-3日志記錄器,可以停下來了。因?yàn)橐呀?jīng)有一些十分出色的日志記錄器組件。比如:monolog/monolog,直接用就可以了。如果不能滿足要求,也建議在此基礎(chǔ)上做擴(kuò)展。
5.PSR-4:自動(dòng)加載器
這個(gè)推薦規(guī)范描述了一個(gè)標(biāo)準(zhǔn)的自動(dòng)加載器策略。自動(dòng)加載器策略是指,在運(yùn)行時(shí)按需查找PHP類,接口或性狀,并將其載入PHP解釋器。
為什么自動(dòng)加載很重要
在PHP文件的頂部你是不是經(jīng)??吹筋愃葡旅娴拇a?
<?php
include 'path/to/file1.php';
include 'path/to/file2.php';
include 'path/to/file3.php';
如果只需載入幾個(gè)PHP腳本,使用這些函數(shù)(include()、include_once()、require()、require_once())能很好的完成工作??墒侨绻阋胍磺€(gè)PHP腳本呢?
在PSR-4推薦規(guī)范之前,PHP組件和框架的作者使用__autoload()和spl_autoload_register()函數(shù)注冊(cè)自定義的自動(dòng)加載器策略??墒?,每個(gè)PHP組件和框架的自動(dòng)加載器都使用獨(dú)特的自動(dòng)加載器。因此,使用的組件多的時(shí)候,也是很麻煩的事情。
推薦使用PSR-4自動(dòng)加載器規(guī)范,就是解決這個(gè)問題,促進(jìn)組件實(shí)現(xiàn)互操作性。
PSR-4自動(dòng)加載器策略
PSR-4推薦規(guī)范不要求改變代碼的實(shí)現(xiàn)方式,只建議如何使用文件系統(tǒng)目錄結(jié)構(gòu)和PHP命名空間組織代碼。PSR-4的精髓是把命名空間的前綴和文件系統(tǒng)中的目錄對(duì)應(yīng)起來。比如,我可以告訴PHP,\Oreilly\ModernPHP命名空間中的類、接口和性狀在物理文件系統(tǒng)的src/目錄中,這樣PHP就知道,前綴為\Oreilly\ModernPHP的命名空間中的類、接口和性狀對(duì)應(yīng)的src/目錄里的目錄和文件。
如何編寫PSR-4自動(dòng)加載器
如果你在寫自己的PSR-4自動(dòng)加載器,請(qǐng)停下來。我們可以使用依賴管理器Composer自動(dòng)生成的PSR-4自動(dòng)加載器。
三、良好實(shí)踐
1.過濾、驗(yàn)證和轉(zhuǎn)義
過濾HTML
使用htmlentities()函數(shù)過濾輸入。
<?php
$input = '<p><script>alert("You won the Nigerian lottery!");</script></p>';
echo htmlentities($input, ENT_QUOTES, 'UTF-8');
需要注意的是:默認(rèn)情況下,htmlentities()函數(shù)不會(huì)轉(zhuǎn)義單引號(hào),而且也檢測(cè)不出輸入字符串的字符集。正確的使用方式是:第一個(gè)參數(shù)輸入字符串;第二個(gè)參數(shù)設(shè)為ENT_QUOTES常量,轉(zhuǎn)移單引號(hào);第三個(gè)參數(shù)設(shè)為輸入字符串的字符集。
更多過濾HTML輸入的方式,可以使用HTML Purifier庫。這個(gè)庫強(qiáng)健且安全,缺點(diǎn):慢,且可能難以配置。
SQL查詢
構(gòu)建SQL查詢不好的方式:
$sql = sprintf(
'UPDATE users SET password = "%s" WHERE id = %s',
$_POST['password'],
$_GET['id']
);
如果 psasword=abc";-- ,則導(dǎo)致修改了整個(gè)users表的記錄password都未abc。如果需要在SQL查詢中使用輸入數(shù)據(jù),要使用PDO預(yù)處理語句。
用戶資料信息
A.過濾用戶資料中的電子郵件地址
這里會(huì)刪除除字符、數(shù)字和!#$%&'*+-/=?^_{|}~@.[]`之外的所有其他符號(hào)。
<?php
$email = 'beckjiang@meijiabang.cn';
$emailSafe = filter_var($email, FILTER_SANITIZE_EMAIL);
B.過濾用戶資料中的外國(guó)字符
<?php
$string = "外國(guó)字符";
$safeString = filter_var(
$string,
FILTER_SANITIZE_STRING,
FILTER_FLAG_STRIP_LOW|FILTER_FLAG_ENCODE_HIGH
);
驗(yàn)證數(shù)據(jù)
驗(yàn)證數(shù)據(jù)與過濾不同,驗(yàn)證不會(huì)從輸入數(shù)據(jù)中刪除信息,而是只確認(rèn)輸入數(shù)據(jù)是否符合預(yù)期。
驗(yàn)證電子郵件地址
我們可以把某個(gè)FILTER_VALIDATE_*標(biāo)志傳給filter_var()函數(shù),除了電子郵件地址,還可以驗(yàn)證布爾值、浮點(diǎn)數(shù)、整數(shù)、IP地址、正則表達(dá)式和URL。
<?php
$input = 'beckjiang@meijiabang.cn';
$isEmail = filter_var($input, FILTER_VALIDAE_EMAIL);
if ($isEmail !== false) {
echo "Success";
} else {
echo "Fail";
}
2.密碼
哈希算法有很多種,例如:MD5、SHA1、bcrypt和scrypt。有些算法的速度很快,用于驗(yàn)證數(shù)據(jù)完整性;有些算法速度則很慢,旨在提高安全性。生成密碼和存儲(chǔ)密碼時(shí)需要使用速度慢、安全性高的算法。
目前,經(jīng)同行審查,最安全的哈希算法是bcrypt。與MD5和SHA1不同,bcrypt是故意設(shè)計(jì)的很慢。bcrypt算法會(huì)自動(dòng)加鹽,防止?jié)撛诘牟屎绫砉?。bcrypt算法永不過時(shí),如果計(jì)算機(jī)的運(yùn)算速度變快了,我們只需提高工作因子的值。
重新計(jì)算密碼的哈希值
下面是登錄用戶的腳本:
<?php
session_start();
try {
// 從請(qǐng)求主體中獲取電子郵件地址
$email = filter_input(INPUT_POST, 'email');
// 從請(qǐng)求主體中獲取密碼
$password = filter_input(INPUT_POST, 'password');
// 使用電子郵件地址獲取用戶(注意,這是虛構(gòu)代碼)
$user = User::findByEmail($email);
// 驗(yàn)證密碼和賬戶的密碼哈希值是否匹配
if (password_verify($password, $user->password_hash) === false) {
throw new Exception('Invalid password');
}
// 如果需要,重新計(jì)算密碼的哈希值
$currentHashAlgorithm = PASSWORD_DEFAULT;
$currentHashOptions = array('cost' => 15);
$passwordNeedRehash = password_needs_rehash(
$user->password_hash,
$currentHashAlgorithm,
$currentHashOptions
);
if ($passwordNeedsRehash === true) {
// 保存新計(jì)算得出的密碼哈希值(注意,這是虛構(gòu)代碼)
$user->password_hash = password_hash(
$password,
$currentHashAlgorithm,
$currentHashOptions
);
$user->save();
}
// 把登錄狀態(tài)保存到回話中
...
// 重定向到個(gè)人資料頁面
...
} catch (Exception $e) {
//異常處理
...
}
值得注意的是:在登錄前,一定要使用password_needs_rehash()函數(shù)檢查用戶記錄中現(xiàn)有的密碼哈希值是否過期。如果過期了,要重新計(jì)算密碼哈希值。
PHP5.5.0之前的密碼哈希API
如果無法使用PHP5.5.0或以上版本,可以使用安東尼·費(fèi)拉拉開發(fā)的ircmaxell/password-compat組件。這個(gè)組件實(shí)現(xiàn)了PHP密碼哈希API中的所有函數(shù):
- password_hash()
- password_get_info()
- password_needs_rehash()
- password_verify()
3.日期、時(shí)間和時(shí)區(qū)
DateTime類
DateTime類提供一個(gè)面向?qū)ο蠼涌冢糜诠芾砣掌诤蜁r(shí)間。
沒有參數(shù),創(chuàng)建的是一個(gè)表示當(dāng)前日期和時(shí)間的實(shí)例:
<?php
$datetime = new DateTime();
傳入?yún)?shù)創(chuàng)建實(shí)例:
<?php
$datetime = new DateTime('2017-01-28 15:27');
指定格式,靜態(tài)構(gòu)造:
<?php
$datetime = DateTime::createFromFormat('M j, Y H:i:s', 'Jan 2, 2017 15:27:30');
DateInterval類
DateInterval實(shí)例表示長(zhǎng)度固定的時(shí)間段(比如,“兩天”),或者相對(duì)而言的時(shí)間段(比如,“昨天”)。DateInterval實(shí)例用于修改DateTime實(shí)例。
使用DateInterval類:
<?php
// 創(chuàng)建DateTime實(shí)例
$datetime = new DateTime();
// 創(chuàng)建長(zhǎng)度為兩周的間隔
$interval = new DateInterval('P2W');
// 修改DateTime實(shí)例
$datetime->add($interval);
echo $datetime->format('Y-m-d H:i:s');
創(chuàng)建反向的DateInterval實(shí)例:
<?php
// 過去一天
$interval = new DateInterval('-1 day');
DateTimeZone類
如果應(yīng)用要迎合國(guó)際客戶,可能要和時(shí)區(qū)斗爭(zhēng)。
創(chuàng)建、使用時(shí)區(qū):
<?php
$timezone = new DateTimeZone('America/New_York');
$datetime = new DateTime('2017-01-28', $timezone);
實(shí)例化之后,也可以使用setTimeZone()函數(shù)設(shè)置市區(qū):
$datetime->setTimeZone(new DateTimeZone('Asia/Hong_Kong'));
DatePeriod類
有時(shí)我們需要迭代處理一段時(shí)間內(nèi)反復(fù)出現(xiàn)的一系列日期和時(shí)間,DatePeriod類可以解決這種問題。DatePeriod類的構(gòu)造方法接受三個(gè)參數(shù),而且都必須提供:
- 一個(gè)Datetime實(shí)例,表示迭代開始時(shí)的日期和時(shí)間。
- 一個(gè)DateInterval實(shí)例,表示到下個(gè)日期和時(shí)間的間隔。
- 一個(gè)整數(shù),表示迭代的總次數(shù)。
DatePeriod實(shí)例是迭代器,每次迭代時(shí)都會(huì)產(chǎn)出一個(gè)DateTime實(shí)例。
使用DatePeriod類:
<?php
$start = new DateTime();
$interval = new DateInterval('P2W');
$period = new DatePeriod($start, $interval, 3);
foreach ($period as $nextDateTime) {
echo $nextDateTime->format('Y-m-d H:i:s'), PHP_EOL;
}
4.數(shù)據(jù)庫
PHP應(yīng)用可以在很多種數(shù)據(jù)庫中持久保存信息,比如:MySQL、SQLite、Oracle等。如果在項(xiàng)目中使用多種數(shù)據(jù)庫,需要安裝并學(xué)習(xí)多種PHP數(shù)據(jù)庫擴(kuò)展和接口,這增加了認(rèn)知和技術(shù)負(fù)擔(dān)。
正是基于這個(gè)原因,PHP原生提供了PDO擴(kuò)展(PHP Data Objects,意思是PHP數(shù)據(jù)對(duì)象),PDO是一系列PHP類,抽象了不同數(shù)據(jù)庫的具體實(shí)現(xiàn)。PDO的介紹和使用就不寫了,比較常用。
5.流
在現(xiàn)代的PHP特性中,流或許是最出色但最少使用的。雖然PHP4.3.0就引入了流,但很多開發(fā)者不知道流的存在,因?yàn)楹苌偃颂峒傲?,而且流的文檔也匱乏。官方的解釋比較難理解,一句話說就是:流的作用是在出發(fā)地和目的地之間傳輸數(shù)據(jù)。
我把流理解為管道,相當(dāng)于把水從一個(gè)地方引到另一個(gè)地方。在水從出發(fā)地流到目的地的過程中,我們可以過濾水,可以改變水質(zhì),可以添加水,也可以排出水(提示:水是數(shù)據(jù)的隱喻)。
流封裝協(xié)議
流式數(shù)據(jù)的種類各異,每種類型需要獨(dú)特的協(xié)議,以便讀寫數(shù)據(jù)。我們稱這些協(xié)議為流封裝協(xié)議。比如,我們可以讀寫文件系統(tǒng),可以通過HTTP、HTTPS或SSH與遠(yuǎn)程Web服務(wù)器通信,還可以打開并讀寫ZIP、RAR或PHAR壓縮文件。這些通信方式都包含下述相同的過程:
- 開始通信。
- 讀取數(shù)據(jù)。
- 寫入數(shù)據(jù)。
- 結(jié)束通信。
雖然過程一樣的,但是讀寫文件系統(tǒng)中文件的方式與手法HTTP消息的方式有所不同。流封裝協(xié)議的作用是使用通用的幾口封裝這些差異。
每個(gè)流都有一個(gè)協(xié)議和一個(gè)目標(biāo)。格式如下:
<scheme>://<target>
說這么多有點(diǎn)懵,先看例子,使用HTTP流封裝協(xié)議與Flickr API通信:
<?php
$json = file_get_contents(
'http://api.flickr.com/services/feeds/photos_public.gne?format=json'
);
不要誤以為這是普通的網(wǎng)頁URL,file_get_contents()函數(shù)的字符串參數(shù)其實(shí)是一個(gè)流標(biāo)識(shí)符。http協(xié)議會(huì)讓PHP使用HTTP流封裝協(xié)議??雌饋硐袷瞧胀ǖ木W(wǎng)頁URL,是因?yàn)镠TTP流封裝協(xié)議就是這樣規(guī)定的:)。其他流封裝協(xié)議可能不是這樣。
file://流封裝協(xié)議
我們使用file_get_contents(),fopen(),fwrite()和fclose()函數(shù)讀寫文件系統(tǒng)。因?yàn)镻HP默認(rèn)使用的流封裝協(xié)議是file://,所以我們很少認(rèn)為這些函數(shù)使用的是PHP流。
隱式使用file://流封裝協(xié)議:
<?php
$handle = fopen('/etc/hosts', 'rb');
while (feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);
顯式使用file://流封裝協(xié)議:
<?php
$handle = fopen('file:///etc/hosts', 'rb');
while (feof($handle) !== true) {
echo fgets($handle);
}
fclose($handle);
流上下文
有些PHP流能接受一些列可選的參數(shù),這些參數(shù)叫流上下文,用于定制流的行為。流上下文使用stream_context_create()函數(shù)創(chuàng)建。
比如,你知道可以使用file_get_contents()函數(shù)發(fā)送HTTP POST請(qǐng)求嗎?如果想這么做,可以使用一個(gè)流上下文對(duì)象:
<?php
$requestBody = '{"username": "beck"}';
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => "Content-Type: application/json;charset=utf-8;\r\n" .
"Content-Length: " . mb_strlen($requestBody),
"content" => $requestBody
)
));
$response = file_get_contents('https://my-api.com/users', false, $context);
流過濾器
關(guān)于PHP的流,其實(shí)真正強(qiáng)大的地方在于過濾、轉(zhuǎn)換、添加或刪除流中傳輸?shù)臄?shù)據(jù)。
注意:PHP內(nèi)置了幾個(gè)流過濾器:string.rot13、string.toupper、string.tolower和string.strp_tags。這些過濾器沒什么用,我們要使用自定義的過濾器。
若想把過濾器附加到現(xiàn)有的流上,要使用stream_filter_append()函數(shù)。比如,想要把文件中的內(nèi)容轉(zhuǎn)換成大寫字母,可以使用string.toupper過濾器。書中不建議使用這個(gè)過濾器,這里只是演示如何把過濾器附加到流上:
<?php
$handle = fopen('data.txt', 'rb');
stream_filter_append($handle, 'string.toupper');
while (feof($handle) !== true) {
echo fgets($handle); // <-- 輸出的全是大寫字母
}
fclose($handle);
使用php://filter流封裝協(xié)議把過濾器附加到流上:
<?php
$handle = fopen('php://filter/read=string.toupper/resource=data.txt', 'rb');
while (feof($handle) !== true) {
echo fgets($handle); // <-- 輸出的全是大寫字母
}
fclose($handle);
來看個(gè)更實(shí)際的流過濾?器示例,假如我們nginx訪問日志保存在rsync.net,一天的訪問情況保存在一個(gè)日志文件中,而且會(huì)使用bzip2壓縮每個(gè)日志文件,名稱格式為:YYYY-MM-DD.log.bz2。某天,領(lǐng)導(dǎo)讓我提取過去30天某個(gè)域名的訪問數(shù)據(jù)。使用DateTime類和流過濾器迭代bzip壓縮的日志文件:
<?php
$dateStart = new \DateTime();
$dateInterval = \DateInterval::createFromDateString('-1 day');
$datePeriod = new \DatePeriod($dateStart, $dateInterval, 30);//創(chuàng)建迭代器
foreach ($datePeriod as $date) {
$file = 'sftp://USER:PASS@rsync.net/' . $date->format('Y-m-d') . 'log.bz2';
if (file_exists($file)) {
$handle = fopen($file, 'rb');
stream_filter_append($handle, 'bzip2.decompress');
while (feof($handle) !== true) {
$line = fgets($handle);
if (strpos($line, 'www.example.com') !== false) {
fwrite(STDOUT, $line);
}
}
fclose($handle);
}
}
計(jì)算日期范圍,確定日志文件的名稱,通過FTP連接rsync.net,下載文件,解壓縮文件,逐行迭代每個(gè)文件,把相應(yīng)的行提取出來,然后把訪問數(shù)據(jù)寫入一個(gè)輸出目標(biāo)。使用PHP流,不到20行代碼就能做完所有這些事情。
自定義流過濾器
其實(shí)大多數(shù)情況下都要使用自定義的流過濾器。自定義的流過濾器是個(gè)PHP類,繼承內(nèi)置的php_user_filter類。這個(gè)類必須實(shí)現(xiàn)filter()、onCreate()和onClose()方法。而且,必須使用stream_filter_register()函數(shù)注冊(cè)自定義的流過濾器。
PHP流會(huì)把數(shù)據(jù)分成按次序排列的桶,一個(gè)桶中盛放的流數(shù)據(jù)量是固定的。一定時(shí)間內(nèi)過濾器接收到的桶叫做桶隊(duì)列。桶隊(duì)列中的每個(gè)桶對(duì)象都有兩個(gè)公開屬性:data和datalen,分別是桶中的內(nèi)容和內(nèi)容的長(zhǎng)度。
下面定義一個(gè)處理臟字的流過濾器:
<?php
class DirtyWordsFilter extends php_user_filter
{
/**
* @param resource $in 流來的桶隊(duì)列
* @param resource $out 流走的桶隊(duì)列
* @param resource $consumed 處理的字節(jié)數(shù)
* @param resource $closing 是流中最后一個(gè)桶隊(duì)列嗎?
*/
public function filter()
{
$words = array('grime', 'dirt', 'grease');
$wordData = array();
foreach ($words as $word) {
$replacement = array_fill(0, mb_strlen($word), '*');
$wordData[$word] = implode(' ', $replacement);
}
$bad = array_keys($wordData);
$goods = array_values($wordData);
// 迭代流來的桶隊(duì)列中的每個(gè)桶
while ($bucket = stream_bucket_make_writeable($in)) {
// 審查桶數(shù)據(jù)中的臟字
$bucket->data = str_replace($bad, $goods, $bucket->data);
// 增加已處理的數(shù)據(jù)量
$consumed += $bucket->datalen;
// 把桶放入流向下游的隊(duì)列中
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
filter()方法的作用是接受、處理再轉(zhuǎn)運(yùn)桶中的流數(shù)據(jù)。這個(gè)方法的返回值是PSFS_PASS_ON常量,表示操作成功。
注冊(cè)流過濾器
接著,我們必須使用stream_filter_register()函數(shù)注冊(cè)這個(gè)自定義的DirtWordsFilter流過濾器:
<?php
stream_filter_register('dirty_words_filter', 'DirtWordsFilter');
第一個(gè)參數(shù)是用于識(shí)別這個(gè)自定義過濾器的過濾器名,第二個(gè)參數(shù)是自定義過濾器的類名。
使用DirtWordsFilter流過濾器
<?php
$handle = fopen('data.txt', 'rb');
stream_filter_append($handle, 'dirty_words_filter');
while (feof($handle) !== true) {
echo fgets($handle); // <-- 輸出審查后的文本
}
fclose($handle);
6.錯(cuò)誤與異常
對(duì)錯(cuò)誤和異常的處理,一定要遵守四個(gè)規(guī)則:
- 一定要讓PHP報(bào)告錯(cuò)誤。
- 在開發(fā)環(huán)境中要顯示錯(cuò)誤。
- 在生產(chǎn)環(huán)境中不能顯示錯(cuò)誤。
- 在開發(fā)環(huán)境和生產(chǎn)環(huán)境中都要記錄錯(cuò)誤。
錯(cuò)誤與異常在日常使用的比較多,就不記錄啦。