10條PHP高級(jí)技巧[修正版]
更新時(shí)間:2011年08月02日 19:41:58 作者:
10條PHP高級(jí)技巧,讓你提高php效率。
1.使用一個(gè)SQL注射備忘單
一個(gè)基本的原則就是,永遠(yuǎn)不要相信用戶提交的數(shù)據(jù)。
另一個(gè)規(guī)則就是,在你發(fā)送或者存儲(chǔ)數(shù)據(jù)時(shí)對(duì)它進(jìn)行轉(zhuǎn)義(escape)。
可以總結(jié)為:filter input, escape output (FIEO). 輸入過(guò)濾,輸出轉(zhuǎn)義。
通常導(dǎo)致SQL注射漏洞的原因是沒(méi)有對(duì)輸入進(jìn)行過(guò)濾,如下語(yǔ)句:
<?php
$query = "SELECT *
FROM users
WHERE name = '{$_GET['name']}'";
在這個(gè)例子中,$_GET['name']來(lái)自用戶提交的數(shù)據(jù),既沒(méi)有進(jìn)行轉(zhuǎn)義,也沒(méi)有進(jìn)行過(guò)濾~~
對(duì)于轉(zhuǎn)義輸出,你要記住用于你程序外部的數(shù)據(jù)需要被轉(zhuǎn)義,否則,它可能被錯(cuò)誤地解析。
相反,過(guò)濾輸入能確保數(shù)據(jù)在使用前是正確的.
對(duì)于過(guò)濾輸入,你要記住,在你程序外部的原始數(shù)據(jù)需要被過(guò)濾,因?yàn)樗鼈兪遣豢尚湃蔚摹?
如下例子演示了輸入過(guò)濾和輸出轉(zhuǎn)義:
<?php
// Initialize arrays for filtered and escaped data, respectively.
$clean = array();
$sql = array();
// Filter the name. (For simplicity, we require alphabetic names.)
if (ctype_alpha($_GET['name'])) {
$clean['name'] = $_GET['name'];
} else {
// The name is invalid. Do something here.
}
// Escape the name.
$sql['name'] = mysql_real_escape_string($clean['name']);
// Construct the query.
$query = "SELECT *
FROM users
WHERE name = '{$sql['name']}'";
?>
另一個(gè)有效防止SQL注射的方法是使用prepare 語(yǔ)句,如:
<?php
// Provide the query format.
$query = $db->prepare('SELECT *
FROM users
WHERE name = :name');
// Provide the query data and execute the query.
$query->execute(array('name' => $clean['name']));
?>
2.了解比較運(yùn)算符之間的不同
例如,你使用strpos() 來(lái)檢測(cè)在一個(gè)字符串中是否存在一個(gè)子串 (如果子串沒(méi)有找到,函數(shù)將返回 FALSE ), 結(jié)果可能會(huì)導(dǎo)致錯(cuò)誤:
<?php
$authors = 'Chris & Sean';
if (strpos($authors, 'Chris')) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>
上例中,由于子串處于最開(kāi)始的位置,因此strpos() 函數(shù)正確地返回了0,表明子串處于字符串中最開(kāi)始的位置。然后,因?yàn)闂l件語(yǔ)句會(huì)把結(jié)果當(dāng)成Boolean(布爾)類型的,因此 0 就被PHP給計(jì)算成了 FALSE,最終導(dǎo)致條件語(yǔ)句判斷失敗。
當(dāng)然,這個(gè)BUG可以用嚴(yán)格的比較語(yǔ)句來(lái)修正:
<?php
if (strpos($authors, 'Chris') !== FALSE) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>
3.減少else(Shortcut the else)
記住,在你使用變量前總是要先初始化它們。
考慮如下一個(gè)用來(lái)根據(jù)用戶名來(lái)檢測(cè)用戶是否是管理員的條件語(yǔ)句:
<?php
if (auth($username) == 'admin') {
$admin = TRUE;
} else {
$admin = FALSE;
}
?>
這個(gè)看起來(lái)似乎足夠安全,因?yàn)榭匆谎劬秃苋菀桌斫?。想象一下有一個(gè)更復(fù)雜一點(diǎn)的例子,它為name和email同時(shí)設(shè)置變量,為方便起見(jiàn):
<?php
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} else {
/* Get the name and email from the database. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
}
?>
因?yàn)椤?admin 還是明確地被設(shè)置為TRUE or FALSE,似乎一切都完好。但是,如果另一個(gè)開(kāi)發(fā)者后來(lái)在代碼里加了一個(gè)elseif語(yǔ)句,很可能他會(huì)忘記這回事:
<?php
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
$moderator = FALSE;
}
?>
如果一個(gè)用戶提供一個(gè)能夠觸發(fā)elseif條件的用戶名(username), $admin 沒(méi)有被初始化,這可能會(huì)導(dǎo)致不必要的行為,或者更糟糕的情況,一個(gè)安全漏洞。另外,一個(gè)類似的情況對(duì)于 $moderator 變量來(lái)說(shuō)同樣存在,它在第一個(gè)條件中沒(méi)有被初始化。
通過(guò)初始化$admin 和 $moderator ,這是完全很容易避免這一情況的發(fā)生的:
<?php
$admin = FALSE;
$moderator = FALSE;
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
}
?>
不管剩下的代碼是什么,現(xiàn)在已經(jīng)明確了 $admin 值 為FALSE ,除非它被顯式地設(shè)置為其它值。對(duì)于 $moderator 也是一樣的。最壞的可能發(fā)生的情況就是,在任何條件下都沒(méi)有修改$admin 或 $moderator ,導(dǎo)致某個(gè)是administrator 或moderator的人沒(méi)有被當(dāng)作相應(yīng)的administrator 或moderator 。
如果你想 shortcut something ,并且你看到我們的例子有包含有else覺(jué)得有點(diǎn)失望。我們有一個(gè)bonus tip 你可能會(huì)感興趣的。我們并不確定它可以被認(rèn)為是a shortcut,但是我們希望它仍然是有幫助的。
考慮一下一個(gè)用于檢測(cè)一個(gè)用戶是否被授權(quán)查看一個(gè)特定頁(yè)面的函數(shù):
<?php
function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username)) {
return TRUE;
} elseif (isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
?>
這個(gè)例子是相當(dāng)?shù)暮?jiǎn)單,因?yàn)橹挥腥龡l規(guī)則需要考慮:
administrators 總是被允許訪問(wèn)的,
處于黑名單的永遠(yuǎn)是禁止訪問(wèn)的,
isAllowed()決定其它人是否有權(quán)訪問(wèn)。
(還有一個(gè)特例是:當(dāng)一個(gè)administrator 處于黑名單中,但這似乎是不太可能的事,所以我們這里直接忽視這種情況)。
我們使用函數(shù)來(lái)做這個(gè)判斷以保持代碼的簡(jiǎn)潔然后集中注意力到業(yè)務(wù)邏輯上去。
如:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
?>
事實(shí)上,你可以精減整個(gè)函數(shù)到一個(gè)復(fù)合條件:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
}
?>
最后,這個(gè)可以被減少到只有一個(gè)return:
<?php
function authorized($username, $page) {
return (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page));
}
?>
如果你的目標(biāo)是謄清代碼的行數(shù),那么這樣你做到的。但是,你要注意到,我們?cè)谟胕sBlacklisted(), isAdmin() 和 isAllowed() ,這取決于參與這些判斷的東西,減少代碼到只剩下一個(gè)復(fù)合條件可能不吸引人。
這下說(shuō)到我們的小技巧上了,一個(gè)“立即返回”函數(shù),所以,如果你盡快返回,你可以很簡(jiǎn)單地表達(dá)這些規(guī)則:
<?php
function authorized($username, $page) {
if (isBlacklisted($username)) {
return FALSE;
}
if (isAdmin($username)) {
return TRUE;
}
return isAllowed($username, $page);
}
?>
這個(gè)例子使用了更多行數(shù)的代碼,但是它是非常簡(jiǎn)單和不惹人注意的。更重要的是,這個(gè)方法減少了你必需考慮的上下文的數(shù)量。例如,一旦你決定了用戶是否處于黑名單里面,你就可以安全地忘掉這件事了。特別是你的邏輯很復(fù)雜的時(shí)候,這是相當(dāng)?shù)挠袔椭摹?
4. 總是使用大括號(hào)
PS:原諒是“扔掉那些方括號(hào) Drop Those Brackets”
根據(jù)本文的內(nèi)容, 我們相應(yīng)作者的意思應(yīng)該是 “braces,” 而不是brackets. “Curly brackets” 可能有大括號(hào)的意思, 但是”brackets” 通常表示 “方括號(hào)”的意思。這個(gè)技巧應(yīng)該被無(wú)條件的忽略,因?yàn)?,沒(méi)有大括號(hào),可讀性和可維護(hù)性被破壞了。
舉一個(gè)簡(jiǎn)單的例子:
<?php
if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
?>
If you're good enough, smart enough, secure enough, notorious enough, or pitied enough, 你可能會(huì)想在5月21號(hào)參加社交聚會(huì):
<?php
if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
?>
沒(méi)有大括號(hào),這個(gè)簡(jiǎn)單的條件導(dǎo)致你每天參加社交聚會(huì) 。也許你有毅力,因此這個(gè)錯(cuò)誤是一個(gè)受歡迎的。希望那個(gè)愚蠢的例子并不分散這一的觀點(diǎn),那就是過(guò)度狂歡是一種出人意料的副作用。
為了提倡丟掉大括號(hào),先前的文章使用類似下面的簡(jiǎn)短的語(yǔ)句作為例子:
<?php
if ($gollum == 'halfling') $height --;
else $height ++;
?>
因?yàn)槊總€(gè)條件被放在單獨(dú)的一行, 這種錯(cuò)誤似乎會(huì)較少發(fā)生, 但是這將導(dǎo)致另一個(gè)問(wèn)題:代碼的不一致和需要更多的時(shí)間來(lái)閱讀和理解。一致性是這樣一個(gè)重要的特性,以致開(kāi)發(fā)人員經(jīng)常遵守一個(gè)編碼標(biāo)準(zhǔn),即使他們不喜歡編碼標(biāo)準(zhǔn)本身。
我們提倡總是使用大括號(hào):
<?php
if (date('d M') == '21 May') {
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
}
?>
你天天聚會(huì)是沒(méi)關(guān)系的,但要保證這是經(jīng)過(guò)思考的,還有,請(qǐng)一定要邀請(qǐng)我們!
5. 盡量用str_replace() 而不是 ereg_replace() 和 preg_replace()
我們討厭聽(tīng)到的否認(rèn)的話,但是(原文)這個(gè)用于演示誤用的小技巧導(dǎo)致了它試圖避免的同樣的濫用問(wèn)題。(
We hate to sound disparaging, but this tip demonstrates the sort of misunderstanding that leads to the same misuse it's trying to prevent.)
很明顯字符串函數(shù)比正則表達(dá)式函數(shù)在字符匹配方面更快速高效,但是作者糟糕地試圖從失敗中得出一個(gè)推論:
(FIX ME: It's an obvious truth that string functions are faster at string matching than regular expression functions, but the author's attempt to draw a corollary from this fails miserably:)
If you're using regular expressions, then ereg_replace() and preg_replace() will be much faster than str_replace().
Because str_replace() does not support pattern matching, this statement makes no sense. The choice between string functions and regular expression functions comes down to which is fit for purpose, not which is faster. If you need to match a pattern, use a regular expression function. If you need to match a string, use a string function.
6. 使用三重運(yùn)算符
三元運(yùn)算符的好處是值得討論的. 下面是一行從最近我們進(jìn)行的審計(jì)的代碼中取出的:
<?php
$host = strlen($host) > 0 ? $host : htmlentities($host);
?>
啊,作者的真實(shí)意愿是如果字符串的長(zhǎng)度大于0 就轉(zhuǎn)義 $host , 但是卻意外地做了相反的事情。很容易犯的錯(cuò)誤是吧?也許吧。在代碼審計(jì)過(guò)程中很容易錯(cuò)過(guò)?當(dāng)然。簡(jiǎn)潔并不一定能使代碼變得很好。
三重運(yùn)算符對(duì)于單行,原型,和模板也行是適合的,但是我們相信一個(gè)普通的條件語(yǔ)句總是更好的。PHP是描述性的和詳細(xì)的,我們認(rèn)為代碼也應(yīng)該是。
7. Memcached
磁盤(pán)訪問(wèn)是慢速的,網(wǎng)絡(luò)訪問(wèn)也是慢的,數(shù)據(jù)庫(kù)通常使用這二者。
內(nèi)存是很快的。使用本地緩存可以避免網(wǎng)絡(luò)和磁盤(pán)訪問(wèn)的開(kāi)銷。結(jié)合這些道理,然后,你想到了memcached,一個(gè)“分布式內(nèi)存對(duì)象緩存系統(tǒng)”,最初為基于Perl的博客平臺(tái)LiveJournal開(kāi)發(fā)的。
如果你的程序不是分布在多個(gè)服務(wù)器上,你可能并不需要memcached。單的緩存方法——序列化數(shù)據(jù)然后將它保存在一個(gè)臨時(shí)文件中。例如 – 對(duì)每個(gè)請(qǐng)求可以消除很多多余的工作。事實(shí)上,這是我們考慮幫助我們的客戶優(yōu)化他們的應(yīng)用程序時(shí),低掛水果的類型。
什么是low-hanging fruit:
A fruit-bearing tree often contains some branches low enough for animals and humans to reach without much effort. The fruit contained on these lower branches may be not be as ripe or attractive as the fruit on higher limbs, but it is usually more abundant and easier to harvest. From this we get the popular expression “l(fā)ow hanging fruit”, which generally means selecting the easiest targets with the least amount of effort.
一種最簡(jiǎn)易且最通用的將數(shù)據(jù)緩存在內(nèi)存的方式是使用APC中的共享類型輔助方法,APC是一個(gè)最初由我們的同事George Schlossnagle開(kāi)發(fā)的緩存系統(tǒng),考慮如下例子:
<?php
$feed = apc_fetch('news');
if ($feed === FALSE) {
$feed = file_get_contents('http://example.org/news.xml');
// Store this data in shared memory for five minutes.
apc_store('news', $feed, 300);
}
// Do something with $feed.
?>
使用這種類型的緩存,你不必在每一次請(qǐng)求時(shí)等待遠(yuǎn)程服務(wù)器發(fā)送Feed數(shù)據(jù)。一些延遲產(chǎn)生了 – 在這個(gè)例子中上限是五分鐘,但可以根據(jù)您的應(yīng)用程序需要調(diào)整到接近實(shí)時(shí)。
8. 使用框架
所有決定都會(huì)有結(jié)果的,我們喜歡框架——事實(shí)上,CakePHP 和 Solar 的主要開(kāi)發(fā)者和我們一起在 OmniTI 工作—— 但是使用一個(gè)框架并不會(huì)奇跡般地使你在做的東西變得更好。
在十月份,我們的同事Paul Jones為HP Advent寫(xiě)一了篇文章,叫做The Framework as Franchise ,在文章中他將框架與商業(yè)專營(yíng)權(quán)相比較。他引用 Michael Gerber “電子神話再現(xiàn)”(”The E-Myth Revisited”) 一書(shū)中的建議:
格柏指出,運(yùn)行一個(gè)成功的企業(yè),企業(yè)家需要像他將要賣掉他的企業(yè)作為一個(gè)特許經(jīng)營(yíng)權(quán)的原型一樣行動(dòng)。這是企業(yè)擁有者可以不親自參與每一項(xiàng)決策使企業(yè)運(yùn)營(yíng)的唯一方法。
( Gerber notes that to run a successful business, the entrepreneur needs to act as if he is going to sell his business as a franchise prototype. It is the only way the business owner can make the business operate without him being personally involved in every decision.)
這是一個(gè)好的建議。無(wú)論你是打算使用框架或者定義你自己的標(biāo)簽和慣例,從未來(lái)開(kāi)發(fā)者的角度來(lái)看價(jià)值是很重要的。
雖然我們很樂(lè)意給你一個(gè)放之四海而皆準(zhǔn)的真理,延伸這個(gè)想法來(lái)表明一個(gè)框架總是合適的,并不是我們想做的事情。
如果你問(wèn)我們是否應(yīng)該使用一個(gè)框架,我們可以給出的最好的答案是,“這要看情況?!?
9. 正確的使用錯(cuò)誤抑制操作符
總是試著避免使用錯(cuò)誤抑制操作符號(hào)。在前面的文章,作者表明:
@ 操作符是相當(dāng)?shù)穆牟⑶胰绻阈枰獙?xiě)高性能的代碼的話它會(huì)使得開(kāi)銷很大。
錯(cuò)誤抑制慢是因?yàn)樵趫?zhí)行抑制語(yǔ)句前,PHP動(dòng)態(tài)的改變error_reporting等級(jí)到0 ,然后然后立即將其還原。這是要開(kāi)銷的。
更糟糕的是,使用錯(cuò)誤抑制符使追蹤問(wèn)題的根本原因很困難。
先前的文章使用如下例子來(lái)支持通過(guò)引用來(lái)給一個(gè)變量賦值的做法。。。(這句怎么翻譯?我暈~~~ )
The previous article uses the following example to support the practice of assigning a variable by reference when it is unknown if $albus is set:
<?php
$albert =& $albus;
?>
盡管這樣是工作的——對(duì)于現(xiàn)在——依靠奇怪的,未定義的行為,而對(duì)于為什么這樣會(huì)工作有一個(gè)很好的理解是一個(gè)產(chǎn)生BUG的好方法。
因?yàn)椤?albert 是引用了$albus的,后期對(duì)于$albus的修改將會(huì)同樣影響到$albert .
一個(gè)更好的解決方案是使用isset(),加上大括號(hào):
<?php
if (!isset($albus)) {
$albert = NULL;
}
?>
給$albert 賦值NULL和給它賦一個(gè)不存在的引用的效果是相同的,但是更加明確了,大大提高了代碼的清晰度和避免的兩個(gè)變量之間的引用關(guān)系。
If you inherit code that uses the error suppression operator excessively, we've got a bonus tip for you. There is a new PECL extension called Scream that disables error suppression.
10. 使用 isset() 而不是 strlen()
這實(shí)際上是一個(gè)巧妙的方法,雖然前面的文章完全沒(méi)有解釋這個(gè)。下面是補(bǔ)充的例子:
<?php
if (isset($username[5])) {
// The username is at least six characters long.
}
?>
當(dāng)你把字符串當(dāng)作一個(gè)數(shù)組時(shí)(荒野無(wú)燈:事實(shí)上,在C語(yǔ)言里面,字符中通常以數(shù)組形式存在),字符串里的每一個(gè)字符都是數(shù)組的一個(gè)元素。通過(guò)檢測(cè)一個(gè)特定元素的存在與否,你可以檢測(cè)這個(gè)字符串是否至少有那么多的字符存在。(注意第一個(gè)字符是元素0,因此 $username[5] 是 $username中的第6個(gè)字符。)
這樣使用isset 比strlen稍快的原因是復(fù)雜的。簡(jiǎn)單的解釋是,strlen() 是一個(gè)函數(shù),而 isset() 是一個(gè)語(yǔ)法結(jié)構(gòu)。通常來(lái)說(shuō),
調(diào)用一個(gè)函數(shù)是比使用語(yǔ)言結(jié)構(gòu)的代價(jià)更為昂貴的。
關(guān)于作者:
Hi,我們是 Chris Shiflett和 Sean Coates. 我們都在 OmniTI (“the most important web company you've never heard of”)工作, blog about PHP and other stuff at shiflett.org and seancoates.com, curate PHP Advent, and do the Twitter thing as @shiflett and @coates.
譯 FROM : http://coding.smashingmagazine.com/2009/03/24/10-useful-php-tips-revisited/
一個(gè)基本的原則就是,永遠(yuǎn)不要相信用戶提交的數(shù)據(jù)。
另一個(gè)規(guī)則就是,在你發(fā)送或者存儲(chǔ)數(shù)據(jù)時(shí)對(duì)它進(jìn)行轉(zhuǎn)義(escape)。
可以總結(jié)為:filter input, escape output (FIEO). 輸入過(guò)濾,輸出轉(zhuǎn)義。
通常導(dǎo)致SQL注射漏洞的原因是沒(méi)有對(duì)輸入進(jìn)行過(guò)濾,如下語(yǔ)句:
復(fù)制代碼 代碼如下:
<?php
$query = "SELECT *
FROM users
WHERE name = '{$_GET['name']}'";
在這個(gè)例子中,$_GET['name']來(lái)自用戶提交的數(shù)據(jù),既沒(méi)有進(jìn)行轉(zhuǎn)義,也沒(méi)有進(jìn)行過(guò)濾~~
對(duì)于轉(zhuǎn)義輸出,你要記住用于你程序外部的數(shù)據(jù)需要被轉(zhuǎn)義,否則,它可能被錯(cuò)誤地解析。
相反,過(guò)濾輸入能確保數(shù)據(jù)在使用前是正確的.
對(duì)于過(guò)濾輸入,你要記住,在你程序外部的原始數(shù)據(jù)需要被過(guò)濾,因?yàn)樗鼈兪遣豢尚湃蔚摹?
如下例子演示了輸入過(guò)濾和輸出轉(zhuǎn)義:
復(fù)制代碼 代碼如下:
<?php
// Initialize arrays for filtered and escaped data, respectively.
$clean = array();
$sql = array();
// Filter the name. (For simplicity, we require alphabetic names.)
if (ctype_alpha($_GET['name'])) {
$clean['name'] = $_GET['name'];
} else {
// The name is invalid. Do something here.
}
// Escape the name.
$sql['name'] = mysql_real_escape_string($clean['name']);
// Construct the query.
$query = "SELECT *
FROM users
WHERE name = '{$sql['name']}'";
?>
另一個(gè)有效防止SQL注射的方法是使用prepare 語(yǔ)句,如:
復(fù)制代碼 代碼如下:
<?php
// Provide the query format.
$query = $db->prepare('SELECT *
FROM users
WHERE name = :name');
// Provide the query data and execute the query.
$query->execute(array('name' => $clean['name']));
?>
2.了解比較運(yùn)算符之間的不同
例如,你使用strpos() 來(lái)檢測(cè)在一個(gè)字符串中是否存在一個(gè)子串 (如果子串沒(méi)有找到,函數(shù)將返回 FALSE ), 結(jié)果可能會(huì)導(dǎo)致錯(cuò)誤:
復(fù)制代碼 代碼如下:
<?php
$authors = 'Chris & Sean';
if (strpos($authors, 'Chris')) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>
上例中,由于子串處于最開(kāi)始的位置,因此strpos() 函數(shù)正確地返回了0,表明子串處于字符串中最開(kāi)始的位置。然后,因?yàn)闂l件語(yǔ)句會(huì)把結(jié)果當(dāng)成Boolean(布爾)類型的,因此 0 就被PHP給計(jì)算成了 FALSE,最終導(dǎo)致條件語(yǔ)句判斷失敗。
當(dāng)然,這個(gè)BUG可以用嚴(yán)格的比較語(yǔ)句來(lái)修正:
復(fù)制代碼 代碼如下:
<?php
if (strpos($authors, 'Chris') !== FALSE) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>
3.減少else(Shortcut the else)
記住,在你使用變量前總是要先初始化它們。
考慮如下一個(gè)用來(lái)根據(jù)用戶名來(lái)檢測(cè)用戶是否是管理員的條件語(yǔ)句:
復(fù)制代碼 代碼如下:
<?php
if (auth($username) == 'admin') {
$admin = TRUE;
} else {
$admin = FALSE;
}
?>
這個(gè)看起來(lái)似乎足夠安全,因?yàn)榭匆谎劬秃苋菀桌斫?。想象一下有一個(gè)更復(fù)雜一點(diǎn)的例子,它為name和email同時(shí)設(shè)置變量,為方便起見(jiàn):
復(fù)制代碼 代碼如下:
<?php
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} else {
/* Get the name and email from the database. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
}
?>
因?yàn)椤?admin 還是明確地被設(shè)置為TRUE or FALSE,似乎一切都完好。但是,如果另一個(gè)開(kāi)發(fā)者后來(lái)在代碼里加了一個(gè)elseif語(yǔ)句,很可能他會(huì)忘記這回事:
復(fù)制代碼 代碼如下:
<?php
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
$moderator = FALSE;
}
?>
如果一個(gè)用戶提供一個(gè)能夠觸發(fā)elseif條件的用戶名(username), $admin 沒(méi)有被初始化,這可能會(huì)導(dǎo)致不必要的行為,或者更糟糕的情況,一個(gè)安全漏洞。另外,一個(gè)類似的情況對(duì)于 $moderator 變量來(lái)說(shuō)同樣存在,它在第一個(gè)條件中沒(méi)有被初始化。
通過(guò)初始化$admin 和 $moderator ,這是完全很容易避免這一情況的發(fā)生的:
復(fù)制代碼 代碼如下:
<?php
$admin = FALSE;
$moderator = FALSE;
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
}
?>
不管剩下的代碼是什么,現(xiàn)在已經(jīng)明確了 $admin 值 為FALSE ,除非它被顯式地設(shè)置為其它值。對(duì)于 $moderator 也是一樣的。最壞的可能發(fā)生的情況就是,在任何條件下都沒(méi)有修改$admin 或 $moderator ,導(dǎo)致某個(gè)是administrator 或moderator的人沒(méi)有被當(dāng)作相應(yīng)的administrator 或moderator 。
如果你想 shortcut something ,并且你看到我們的例子有包含有else覺(jué)得有點(diǎn)失望。我們有一個(gè)bonus tip 你可能會(huì)感興趣的。我們并不確定它可以被認(rèn)為是a shortcut,但是我們希望它仍然是有幫助的。
考慮一下一個(gè)用于檢測(cè)一個(gè)用戶是否被授權(quán)查看一個(gè)特定頁(yè)面的函數(shù):
復(fù)制代碼 代碼如下:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username)) {
return TRUE;
} elseif (isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
?>
這個(gè)例子是相當(dāng)?shù)暮?jiǎn)單,因?yàn)橹挥腥龡l規(guī)則需要考慮:
administrators 總是被允許訪問(wèn)的,
處于黑名單的永遠(yuǎn)是禁止訪問(wèn)的,
isAllowed()決定其它人是否有權(quán)訪問(wèn)。
(還有一個(gè)特例是:當(dāng)一個(gè)administrator 處于黑名單中,但這似乎是不太可能的事,所以我們這里直接忽視這種情況)。
我們使用函數(shù)來(lái)做這個(gè)判斷以保持代碼的簡(jiǎn)潔然后集中注意力到業(yè)務(wù)邏輯上去。
如:
復(fù)制代碼 代碼如下:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
?>
事實(shí)上,你可以精減整個(gè)函數(shù)到一個(gè)復(fù)合條件:
復(fù)制代碼 代碼如下:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
}
?>
最后,這個(gè)可以被減少到只有一個(gè)return:
復(fù)制代碼 代碼如下:
<?php
function authorized($username, $page) {
return (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page));
}
?>
如果你的目標(biāo)是謄清代碼的行數(shù),那么這樣你做到的。但是,你要注意到,我們?cè)谟胕sBlacklisted(), isAdmin() 和 isAllowed() ,這取決于參與這些判斷的東西,減少代碼到只剩下一個(gè)復(fù)合條件可能不吸引人。
這下說(shuō)到我們的小技巧上了,一個(gè)“立即返回”函數(shù),所以,如果你盡快返回,你可以很簡(jiǎn)單地表達(dá)這些規(guī)則:
復(fù)制代碼 代碼如下:
<?php
function authorized($username, $page) {
if (isBlacklisted($username)) {
return FALSE;
}
if (isAdmin($username)) {
return TRUE;
}
return isAllowed($username, $page);
}
?>
這個(gè)例子使用了更多行數(shù)的代碼,但是它是非常簡(jiǎn)單和不惹人注意的。更重要的是,這個(gè)方法減少了你必需考慮的上下文的數(shù)量。例如,一旦你決定了用戶是否處于黑名單里面,你就可以安全地忘掉這件事了。特別是你的邏輯很復(fù)雜的時(shí)候,這是相當(dāng)?shù)挠袔椭摹?
4. 總是使用大括號(hào)
PS:原諒是“扔掉那些方括號(hào) Drop Those Brackets”
根據(jù)本文的內(nèi)容, 我們相應(yīng)作者的意思應(yīng)該是 “braces,” 而不是brackets. “Curly brackets” 可能有大括號(hào)的意思, 但是”brackets” 通常表示 “方括號(hào)”的意思。這個(gè)技巧應(yīng)該被無(wú)條件的忽略,因?yàn)?,沒(méi)有大括號(hào),可讀性和可維護(hù)性被破壞了。
舉一個(gè)簡(jiǎn)單的例子:
復(fù)制代碼 代碼如下:
<?php
if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
?>
If you're good enough, smart enough, secure enough, notorious enough, or pitied enough, 你可能會(huì)想在5月21號(hào)參加社交聚會(huì):
復(fù)制代碼 代碼如下:
<?php
if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
?>
沒(méi)有大括號(hào),這個(gè)簡(jiǎn)單的條件導(dǎo)致你每天參加社交聚會(huì) 。也許你有毅力,因此這個(gè)錯(cuò)誤是一個(gè)受歡迎的。希望那個(gè)愚蠢的例子并不分散這一的觀點(diǎn),那就是過(guò)度狂歡是一種出人意料的副作用。
為了提倡丟掉大括號(hào),先前的文章使用類似下面的簡(jiǎn)短的語(yǔ)句作為例子:
復(fù)制代碼 代碼如下:
<?php
if ($gollum == 'halfling') $height --;
else $height ++;
?>
因?yàn)槊總€(gè)條件被放在單獨(dú)的一行, 這種錯(cuò)誤似乎會(huì)較少發(fā)生, 但是這將導(dǎo)致另一個(gè)問(wèn)題:代碼的不一致和需要更多的時(shí)間來(lái)閱讀和理解。一致性是這樣一個(gè)重要的特性,以致開(kāi)發(fā)人員經(jīng)常遵守一個(gè)編碼標(biāo)準(zhǔn),即使他們不喜歡編碼標(biāo)準(zhǔn)本身。
我們提倡總是使用大括號(hào):
復(fù)制代碼 代碼如下:
<?php
if (date('d M') == '21 May') {
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
}
?>
你天天聚會(huì)是沒(méi)關(guān)系的,但要保證這是經(jīng)過(guò)思考的,還有,請(qǐng)一定要邀請(qǐng)我們!
5. 盡量用str_replace() 而不是 ereg_replace() 和 preg_replace()
我們討厭聽(tīng)到的否認(rèn)的話,但是(原文)這個(gè)用于演示誤用的小技巧導(dǎo)致了它試圖避免的同樣的濫用問(wèn)題。(
We hate to sound disparaging, but this tip demonstrates the sort of misunderstanding that leads to the same misuse it's trying to prevent.)
很明顯字符串函數(shù)比正則表達(dá)式函數(shù)在字符匹配方面更快速高效,但是作者糟糕地試圖從失敗中得出一個(gè)推論:
(FIX ME: It's an obvious truth that string functions are faster at string matching than regular expression functions, but the author's attempt to draw a corollary from this fails miserably:)
If you're using regular expressions, then ereg_replace() and preg_replace() will be much faster than str_replace().
Because str_replace() does not support pattern matching, this statement makes no sense. The choice between string functions and regular expression functions comes down to which is fit for purpose, not which is faster. If you need to match a pattern, use a regular expression function. If you need to match a string, use a string function.
6. 使用三重運(yùn)算符
三元運(yùn)算符的好處是值得討論的. 下面是一行從最近我們進(jìn)行的審計(jì)的代碼中取出的:
復(fù)制代碼 代碼如下:
<?php
$host = strlen($host) > 0 ? $host : htmlentities($host);
?>
啊,作者的真實(shí)意愿是如果字符串的長(zhǎng)度大于0 就轉(zhuǎn)義 $host , 但是卻意外地做了相反的事情。很容易犯的錯(cuò)誤是吧?也許吧。在代碼審計(jì)過(guò)程中很容易錯(cuò)過(guò)?當(dāng)然。簡(jiǎn)潔并不一定能使代碼變得很好。
三重運(yùn)算符對(duì)于單行,原型,和模板也行是適合的,但是我們相信一個(gè)普通的條件語(yǔ)句總是更好的。PHP是描述性的和詳細(xì)的,我們認(rèn)為代碼也應(yīng)該是。
7. Memcached
磁盤(pán)訪問(wèn)是慢速的,網(wǎng)絡(luò)訪問(wèn)也是慢的,數(shù)據(jù)庫(kù)通常使用這二者。
內(nèi)存是很快的。使用本地緩存可以避免網(wǎng)絡(luò)和磁盤(pán)訪問(wèn)的開(kāi)銷。結(jié)合這些道理,然后,你想到了memcached,一個(gè)“分布式內(nèi)存對(duì)象緩存系統(tǒng)”,最初為基于Perl的博客平臺(tái)LiveJournal開(kāi)發(fā)的。
如果你的程序不是分布在多個(gè)服務(wù)器上,你可能并不需要memcached。單的緩存方法——序列化數(shù)據(jù)然后將它保存在一個(gè)臨時(shí)文件中。例如 – 對(duì)每個(gè)請(qǐng)求可以消除很多多余的工作。事實(shí)上,這是我們考慮幫助我們的客戶優(yōu)化他們的應(yīng)用程序時(shí),低掛水果的類型。
什么是low-hanging fruit:
A fruit-bearing tree often contains some branches low enough for animals and humans to reach without much effort. The fruit contained on these lower branches may be not be as ripe or attractive as the fruit on higher limbs, but it is usually more abundant and easier to harvest. From this we get the popular expression “l(fā)ow hanging fruit”, which generally means selecting the easiest targets with the least amount of effort.
一種最簡(jiǎn)易且最通用的將數(shù)據(jù)緩存在內(nèi)存的方式是使用APC中的共享類型輔助方法,APC是一個(gè)最初由我們的同事George Schlossnagle開(kāi)發(fā)的緩存系統(tǒng),考慮如下例子:
復(fù)制代碼 代碼如下:
<?php
$feed = apc_fetch('news');
if ($feed === FALSE) {
$feed = file_get_contents('http://example.org/news.xml');
// Store this data in shared memory for five minutes.
apc_store('news', $feed, 300);
}
// Do something with $feed.
?>
使用這種類型的緩存,你不必在每一次請(qǐng)求時(shí)等待遠(yuǎn)程服務(wù)器發(fā)送Feed數(shù)據(jù)。一些延遲產(chǎn)生了 – 在這個(gè)例子中上限是五分鐘,但可以根據(jù)您的應(yīng)用程序需要調(diào)整到接近實(shí)時(shí)。
8. 使用框架
所有決定都會(huì)有結(jié)果的,我們喜歡框架——事實(shí)上,CakePHP 和 Solar 的主要開(kāi)發(fā)者和我們一起在 OmniTI 工作—— 但是使用一個(gè)框架并不會(huì)奇跡般地使你在做的東西變得更好。
在十月份,我們的同事Paul Jones為HP Advent寫(xiě)一了篇文章,叫做The Framework as Franchise ,在文章中他將框架與商業(yè)專營(yíng)權(quán)相比較。他引用 Michael Gerber “電子神話再現(xiàn)”(”The E-Myth Revisited”) 一書(shū)中的建議:
格柏指出,運(yùn)行一個(gè)成功的企業(yè),企業(yè)家需要像他將要賣掉他的企業(yè)作為一個(gè)特許經(jīng)營(yíng)權(quán)的原型一樣行動(dòng)。這是企業(yè)擁有者可以不親自參與每一項(xiàng)決策使企業(yè)運(yùn)營(yíng)的唯一方法。
( Gerber notes that to run a successful business, the entrepreneur needs to act as if he is going to sell his business as a franchise prototype. It is the only way the business owner can make the business operate without him being personally involved in every decision.)
這是一個(gè)好的建議。無(wú)論你是打算使用框架或者定義你自己的標(biāo)簽和慣例,從未來(lái)開(kāi)發(fā)者的角度來(lái)看價(jià)值是很重要的。
雖然我們很樂(lè)意給你一個(gè)放之四海而皆準(zhǔn)的真理,延伸這個(gè)想法來(lái)表明一個(gè)框架總是合適的,并不是我們想做的事情。
如果你問(wèn)我們是否應(yīng)該使用一個(gè)框架,我們可以給出的最好的答案是,“這要看情況?!?
9. 正確的使用錯(cuò)誤抑制操作符
總是試著避免使用錯(cuò)誤抑制操作符號(hào)。在前面的文章,作者表明:
@ 操作符是相當(dāng)?shù)穆牟⑶胰绻阈枰獙?xiě)高性能的代碼的話它會(huì)使得開(kāi)銷很大。
錯(cuò)誤抑制慢是因?yàn)樵趫?zhí)行抑制語(yǔ)句前,PHP動(dòng)態(tài)的改變error_reporting等級(jí)到0 ,然后然后立即將其還原。這是要開(kāi)銷的。
更糟糕的是,使用錯(cuò)誤抑制符使追蹤問(wèn)題的根本原因很困難。
先前的文章使用如下例子來(lái)支持通過(guò)引用來(lái)給一個(gè)變量賦值的做法。。。(這句怎么翻譯?我暈~~~ )
The previous article uses the following example to support the practice of assigning a variable by reference when it is unknown if $albus is set:
復(fù)制代碼 代碼如下:
<?php
$albert =& $albus;
?>
盡管這樣是工作的——對(duì)于現(xiàn)在——依靠奇怪的,未定義的行為,而對(duì)于為什么這樣會(huì)工作有一個(gè)很好的理解是一個(gè)產(chǎn)生BUG的好方法。
因?yàn)椤?albert 是引用了$albus的,后期對(duì)于$albus的修改將會(huì)同樣影響到$albert .
一個(gè)更好的解決方案是使用isset(),加上大括號(hào):
復(fù)制代碼 代碼如下:
<?php
if (!isset($albus)) {
$albert = NULL;
}
?>
給$albert 賦值NULL和給它賦一個(gè)不存在的引用的效果是相同的,但是更加明確了,大大提高了代碼的清晰度和避免的兩個(gè)變量之間的引用關(guān)系。
If you inherit code that uses the error suppression operator excessively, we've got a bonus tip for you. There is a new PECL extension called Scream that disables error suppression.
10. 使用 isset() 而不是 strlen()
這實(shí)際上是一個(gè)巧妙的方法,雖然前面的文章完全沒(méi)有解釋這個(gè)。下面是補(bǔ)充的例子:
復(fù)制代碼 代碼如下:
<?php
if (isset($username[5])) {
// The username is at least six characters long.
}
?>
當(dāng)你把字符串當(dāng)作一個(gè)數(shù)組時(shí)(荒野無(wú)燈:事實(shí)上,在C語(yǔ)言里面,字符中通常以數(shù)組形式存在),字符串里的每一個(gè)字符都是數(shù)組的一個(gè)元素。通過(guò)檢測(cè)一個(gè)特定元素的存在與否,你可以檢測(cè)這個(gè)字符串是否至少有那么多的字符存在。(注意第一個(gè)字符是元素0,因此 $username[5] 是 $username中的第6個(gè)字符。)
這樣使用isset 比strlen稍快的原因是復(fù)雜的。簡(jiǎn)單的解釋是,strlen() 是一個(gè)函數(shù),而 isset() 是一個(gè)語(yǔ)法結(jié)構(gòu)。通常來(lái)說(shuō),
調(diào)用一個(gè)函數(shù)是比使用語(yǔ)言結(jié)構(gòu)的代價(jià)更為昂貴的。
關(guān)于作者:
Hi,我們是 Chris Shiflett和 Sean Coates. 我們都在 OmniTI (“the most important web company you've never heard of”)工作, blog about PHP and other stuff at shiflett.org and seancoates.com, curate PHP Advent, and do the Twitter thing as @shiflett and @coates.
譯 FROM : http://coding.smashingmagazine.com/2009/03/24/10-useful-php-tips-revisited/
您可能感興趣的文章:
- PHP的十個(gè)高級(jí)技巧(上中下)
- PHP編程之高級(jí)技巧——利用Mysql函數(shù)
- PHP Mysql編程之高級(jí)技巧
- PHP小技巧之函數(shù)重載
- php定界符<<<使用技巧和實(shí)例
- PHP中文編碼小技巧
- php導(dǎo)入大量數(shù)據(jù)到mysql性能優(yōu)化技巧
- php靜態(tài)文件返回304技巧分享
- PHP網(wǎng)站開(kāi)發(fā)中常用的8個(gè)小技巧
- php數(shù)組鍵名技巧小結(jié)
- php數(shù)組索引與鍵值操作技巧實(shí)例分析
- 10條php編程小技巧
- 提高php編程效率技巧
- 十個(gè)PHP高級(jí)應(yīng)用技巧果斷收藏
相關(guān)文章
使用PHP獲取網(wǎng)絡(luò)文件的實(shí)現(xiàn)代碼
PHP獲取網(wǎng)絡(luò)文件的實(shí)現(xiàn)代碼,其實(shí)就是一個(gè)小偷程序。學(xué)習(xí)php小偷程序的朋友可以參考下。2010-01-01
PHP設(shè)計(jì)模式之裝飾器模式定義與用法簡(jiǎn)單示例
這篇文章主要介紹了PHP設(shè)計(jì)模式之裝飾器模式定義與用法,結(jié)合簡(jiǎn)單實(shí)例形式分析了php裝飾器模式的概念、原理、定義與簡(jiǎn)單使用方法,需要的朋友可以參考下2018-08-08
php實(shí)現(xiàn)的驗(yàn)證碼文件類實(shí)例
這篇文章主要介紹了php實(shí)現(xiàn)的驗(yàn)證碼文件類,實(shí)例分析了php生成驗(yàn)證碼文件的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-06-06
一個(gè)PHP緩存類代碼(附詳細(xì)說(shuō)明)
一個(gè)PHP緩存類代碼,后面都有詳細(xì)的說(shuō)明,學(xué)習(xí)php的朋友可以參考下。2011-06-06
php-fpm開(kāi)啟狀態(tài)統(tǒng)計(jì)的方法詳解
這篇文章主要給大家介紹了php-fpm開(kāi)啟狀態(tài)統(tǒng)計(jì)的方法,文中介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-06-06
PHP中 empty() 和 isset() 的區(qū)別介紹
作為PHP中經(jīng)常用來(lái)判斷變量是否為空的函數(shù):empty()和isset() ,二者其實(shí)在很多方面還是存在區(qū)別的,本文將為大家詳細(xì)介紹一下他們的區(qū)別之處,需要的朋友可以了解一下2021-12-12
php聚合式迭代器的基礎(chǔ)知識(shí)點(diǎn)及實(shí)例代碼
在本篇文章里小編給大家整理的是一篇關(guān)于php聚合式迭代器的基礎(chǔ)知識(shí)點(diǎn)及實(shí)例代碼,有興趣的朋友們可以學(xué)習(xí)參考下。2021-07-07

