10条PHP高级技巧[修正版]

1.使用一个SQL注射备忘单
一个基本的原则就是,永远不要相信用户提交的数据。
另一个规则就是,在你发送或者存储数据时对它进行转义(escape)。
可以总结为:filter input, escape output (FIEO). 输入过滤,输出转义。
通常导致SQL注射漏洞的原因是没有对输入进行过滤,如下语句:


代码如下:

<?php
$query = "SELECT *
FROM users
WHERE name = '{$_GET['name']}'";

在这个例子中,$_GET['name']来自用户提交的数据,既没有进行转义,也没有进行过滤~~
对于转义输出,你要记住用于你程序外部的数据需要被转义,否则,它可能被错误地解析。
相反,过滤输入能确保数据在使用前是正确的.
对于过滤输入,你要记住,在你程序外部的原始数据需要被过滤,因为它们是不可信任的。
如下例子演示了输入过滤和输出转义:


代码如下:

<?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']}'";
?>

另一个有效防止SQL注射的方法是使用prepare 语句,如:


代码如下:

<?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.了解比较运算符之间的不同
例如,你使用strpos() 来检测在一个字符串中是否存在一个子串 (如果子串没有找到,函数将返回 FALSE ), 结果可能会导致错误:


代码如下:

<?php
$authors = 'Chris & Sean';
if (strpos($authors, 'Chris')) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>

上例中,由于子串处于最开始的位置,因此strpos() 函数正确地返回了0,表明子串处于字符串中最开始的位置。然后,因为条件语句会把结果当成Boolean(布尔)类型的,因此 0 就被PHP给计算成了 FALSE,最终导致条件语句判断失败。
当然,这个BUG可以用严格的比较语句来修正:


代码如下:

<?php
if (strpos($authors, 'Chris') !== FALSE) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>

3.减少else(Shortcut the else)
记住,在你使用变量前总是要先初始化它们。
考虑如下一个用来根据用户名来检测用户是否是管理员的条件语句:


代码如下:

<?php
if (auth($username) == 'admin') {
$admin = TRUE;
} else {
$admin = FALSE;
}
?>

这个看起来似乎足够安全,因为看一眼就很容易理解。想象一下有一个更复杂一点的例子,它为name和email同时设置变量,为方便起见:


代码如下:

<?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;
}
?>

因为 $admin 还是明确地被设置为TRUE or FALSE,似乎一切都完好。但是,如果另一个开发者后来在代码里加了一个elseif语句,很可能他会忘记这回事:


代码如下:

<?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;
}
?>

如果一个用户提供一个能够触发elseif条件的用户名(username), $admin 没有被初始化,这可能会导致不必要的行为,或者更糟糕的情况,一个安全漏洞。另外,一个类似的情况对于 $moderator 变量来说同样存在,它在第一个条件中没有被初始化。
通过初始化$admin 和 $moderator ,这是完全很容易避免这一情况的发生的:


代码如下:

<?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'];
}
?>

不管剩下的代码是什么,现在已经明确了 $admin 值 为FALSE ,除非它被显式地设置为其它值。对于 $moderator 也是一样的。最坏的可能发生的情况就是,在任何条件下都没有修改$admin 或 $moderator ,导致某个是administrator 或moderator的人没有被当作相应的administrator 或moderator 。
如果你想 shortcut something ,并且你看到我们的例子有包含有else觉得有点失望。我们有一个bonus tip 你可能会感兴趣的。我们并不确定它可以被认为是a shortcut,但是我们希望它仍然是有帮助的。
考虑一下一个用于检测一个用户是否被授权查看一个特定页面的函数:


代码如下:

<?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;
}
}
?>

这个例子是相当的简单,因为只有三条规则需要考虑:
administrators 总是被允许访问的,
处于黑名单的永远是禁止访问的,
isAllowed()决定其它人是否有权访问。
(还有一个特例是:当一个administrator 处于黑名单中,但这似乎是不太可能的事,所以我们这里直接忽视这种情况)。
我们使用函数来做这个判断以保持代码的简洁然后集中注意力到业务逻辑上去。
如:


代码如下:

<?php
function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
?>

事实上,你可以精减整个函数到一个复合条件:


代码如下:

<?php
function authorized($username, $page) {
if (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
}
?>

最后,这个可以被减少到只有一个return:


代码如下:

<?php
function authorized($username, $page) {
return (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page));
}
?>

如果你的目标是誊清代码的行数,那么这样你做到的。但是,你要注意到,我们在用isBlacklisted(), isAdmin() 和 isAllowed() ,这取决于参与这些判断的东西,减少代码到只剩下一个复合条件可能不吸引人。
这下说到我们的小技巧上了,一个“立即返回”函数,所以,如果你尽快返回,你可以很简单地表达这些规则:


代码如下:

<?php
function authorized($username, $page) {
if (isBlacklisted($username)) {
return FALSE;
}
if (isAdmin($username)) {
return TRUE;
}
return isAllowed($username, $page);
}
?>

这个例子使用了更多行数的代码,但是它是非常简单和不惹人注意的。更重要的是,这个方法减少了你必需考虑的上下文的数量。例如,一旦你决定了用户是否处于黑名单里面,你就可以安全地忘掉这件事了。特别是你的逻辑很复杂的时候,这是相当的有帮助的。
4. 总是使用大括号
PS:原谅是“扔掉那些方括号 Drop Those Brackets”
根据本文的内容, 我们相应作者的意思应该是 “braces,” 而不是brackets. “Curly brackets” 可能有大括号的意思, 但是”brackets” 通常表示 “方括号”的意思。这个技巧应该被无条件的忽略,因为,没有大括号,可读性和可维护性被破坏了。
举一个简单的例子:


代码如下:

<?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, 你可能会想在5月21号参加社交聚会:


代码如下:

<?php
if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
?>

没有大括号,这个简单的条件导致你每天参加社交聚会 。也许你有毅力,因此这个错误是一个受欢迎的。希望那个愚蠢的例子并不分散这一的观点,那就是过度狂欢是一种出人意料的副作用。
为了提倡丢掉大括号,先前的文章使用类似下面的简短的语句作为例子:


代码如下:

<?php
if ($gollum == 'halfling') $height --;
else $height ++;
?>

因为每个条件被放在单独的一行, 这种错误似乎会较少发生, 但是这将导致另一个问题:代码的不一致和需要更多的时间来阅读和理解。一致性是这样一个重要的特性,以致开发人员经常遵守一个编码标准,即使他们不喜欢编码标准本身。
我们提倡总是使用大括号:


代码如下:

<?php
if (date('d M') == '21 May') {
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
}
?>

你天天聚会是没关系的,但要保证这是经过思考的,还有,请一定要邀请我们!
5. 尽量用str_replace() 而不是 ereg_replace() 和 preg_replace()
我们讨厌听到的否认的话,但是(原文)这个用于演示误用的小技巧导致了它试图避免的同样的滥用问题。(
We hate to sound disparaging, but this tip demonstrates the sort of misunderstanding that leads to the same misuse it's trying to prevent.)
很明显字符串函数比正则表达式函数在字符匹配方面更快速高效,但是作者糟糕地试图从失败中得出一个推论:
(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. 使用三重运算符
三元运算符的好处是值得讨论的. 下面是一行从最近我们进行的审计的代码中取出的:


代码如下:

<?php
$host = strlen($host) > 0 ? $host : htmlentities($host);
?>

啊,作者的真实意愿是如果字符串的长度大于0 就转义 $host , 但是却意外地做了相反的事情。很容易犯的错误是吧?也许吧。在代码审计过程中很容易错过?当然。简洁并不一定能使代码变得很好。
三重运算符对于单行,原型,和模板也行是适合的,但是我们相信一个普通的条件语句总是更好的。PHP是描述性的和详细的,我们认为代码也应该是。
7. Memcached
磁盘访问是慢速的,网络访问也是慢的,数据库通常使用这二者。
内存是很快的。使用本地缓存可以避免网络和磁盘访问的开销。结合这些道理,然后,你想到了memcached,一个“分布式内存对象缓存系统”,最初为基于Perl的博客平台LiveJournal开发的。
如果你的程序不是分布在多个服务器上,你可能并不需要memcached。单的缓存方法——序列化数据然后将它保存在一个临时文件中。例如 – 对每个请求可以消除很多多余的工作。事实上,这是我们考虑帮助我们的客户优化他们的应用程序时,低挂水果的类型。
什么是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 “low hanging fruit”, which generally means selecting the easiest targets with the least amount of effort.
一种最简易且最通用的将数据缓存在内存的方式是使用APC中的共享类型辅助方法,APC是一个最初由我们的同事George Schlossnagle开发的缓存系统,考虑如下例子:


代码如下:

<?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.
?>

使用这种类型的缓存,你不必在每一次请求时等待远程服务器发送Feed数据。一些延迟产生了 – 在这个例子中上限是五分钟,但可以根据您的应用程序需要调整到接近实时。
8. 使用框架
所有决定都会有结果的,我们喜欢框架——事实上,CakePHP 和 Solar 的主要开发者和我们一起在 OmniTI 工作—— 但是使用一个框架并不会奇迹般地使你在做的东西变得更好。
在十月份,我们的同事Paul Jones为HP Advent写一了篇文章,叫做The Framework as Franchise ,在文章中他将框架与商业专营权相比较。他引用 Michael Gerber “电子神话再现”(”The E-Myth Revisited”) 一书中的建议:
  格柏指出,运行一个成功的企业,企业家需要像他将要卖掉他的企业作为一个特许经营权的原型一样行动。这是企业拥有者可以不亲自参与每一项决策使企业运营的唯一方法。
( 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.)
这是一个好的建议。无论你是打算使用框架或者定义你自己的标签和惯例,从未来开发者的角度来看价值是很重要的。
虽然我们很乐意给你一个放之四海而皆准的真理,延伸这个想法来表明一个框架总是合适的,并不是我们想做的事情。
如果你问我们是否应该使用一个框架,我们可以给出的最好的答案是,“这要看情况。”
9. 正确的使用错误抑制操作符
总是试着避免使用错误抑制操作符号。在前面的文章,作者表明:
  @ 操作符是相当的慢的并且如果你需要写高性能的代码的话它会使得开销很大。
错误抑制慢是因为在执行抑制语句前,PHP动态的改变error_reporting等级到0 ,然后然后立即将其还原。这是要开销的。
更糟糕的是,使用错误抑制符使追踪问题的根本原因很困难。
先前的文章使用如下例子来支持通过引用来给一个变量赋值的做法。。。(这句怎么翻译?我晕~~~ )
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;
?>

尽管这样是工作的——对于现在——依靠奇怪的,未定义的行为,而对于为什么这样会工作有一个很好的理解是一个产生BUG的好方法。
因为 $albert 是引用了$albus的,后期对于$albus的修改将会同样影响到$albert .
一个更好的解决方案是使用isset(),加上大括号:


代码如下:

<?php
if (!isset($albus)) {
$albert = NULL;
}
?>

给$albert 赋值NULL和给它赋一个不存在的引用的效果是相同的,但是更加明确了,大大提高了代码的清晰度和避免的两个变量之间的引用关系。
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()
这实际上是一个巧妙的方法,虽然前面的文章完全没有解释这个。下面是补充的例子:


代码如下:

<?php
if (isset($username[5])) {
// The username is at least six characters long.
}
?>

当你把字符串当作一个数组时(荒野无灯:事实上,在C语言里面,字符中通常以数组形式存在),字符串里的每一个字符都是数组的一个元素。通过检测一个特定元素的存在与否,你可以检测这个字符串是否至少有那么多的字符存在。(注意第一个字符是元素0,因此 $username[5] 是 $username中的第6个字符。)
这样使用isset 比strlen稍快的原因是复杂的。简单的解释是,strlen() 是一个函数,而 isset() 是一个语法结构。通常来说,
调用一个函数是比使用语言结构的代价更为昂贵的。
关于作者:
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/

(0)

相关推荐

  • php静态文件返回304技巧分享

    有时一些静态文件(如图片)会由php输出,会发现请求都是200,静态文件每次都去服务器上请求太浪费资源了,这时如何让浏览器缓存图片呢?就需要我们在php中输出304了. 我们可以利用php中的 HTTP_IF_MODIFIED_SINCE 结合etag来干这事.Etag没有明确规定的格式,我们可以用文件修改时间的md5值,代码如下: 复制代码 代码如下: private function _addEtag($file) {     $last_modified_time = filemtime(

  • PHP中文编码小技巧

    PHP程序设计中中文编码问题曾经困扰很多人,导致这个问题的原因其实很简单,每个国家(或区域)都规定了计算机信息交换用的字符编码集,如美国的扩展 ASCII 码,中国的 GB2312-80,日本的 JIS 等.作为该国家/区域内信息处理的基础,字符编码集起着统一编码的重要作用.字符编码集按长度分为 SBCS(单字节字符集),DBCS(双字节字符集)两大类.早期的软件(尤其是操作系统),为了解决本地字符信息的计算机处理,出现了各种本地化版本(L10N),为了区分,引进了 LANG, Codepage

  • php导入大量数据到mysql性能优化技巧

    本文实例讲述了php导入大量数据到mysql性能优化技巧.分享给大家供大家参考.具体分析如下: 在mysql中我们结合php把一些文件导入到mysql中,这里就来分享一下我对15000条记录进行导入时分析与优化,需要的朋友可以参考一下. 之前有几篇文章,说了最近tiandi在帮朋友做一个小项目,用于统计电话号码的,每次按需求从数据库里随机生成打包的电话号码,然后不停地让人打这些电话号码推销产品(小小鄙视一下这样的行为).但是朋友要求帮忙,咱也不能不帮啊,是吧.程序两个星期前已经做好,测试完毕交工

  • php数组索引与键值操作技巧实例分析

    本文实例讲述了php数组索引与键值操作技巧.分享给大家供大家参考.具体如下: <?php $array = array("a", "b","c"); //定义数组 $array[] = "Simon"; //增加一个新的数组元素 print_r($array); //输出数组 ?> <?php $array = array("a", "b","c")

  • php定界符<<<使用技巧和实例

    php界定符就是为了照样输出内容.它的格式如下: 复制代码 代码如下: $str = <<< EOF     Here is your string     ...... EOF; 其中EOF是自定义的变量,但要成对出现! 附上一段php示例代码: 复制代码 代码如下: <?php $a = "www.jb51.net"; print <<< jb51 <select>  <option value="1"

  • PHP编程之高级技巧——利用Mysql函数

    尽管PHP为我们提供了很多函数,但有些东西处理起来还是不很方便.譬如PHP提供的日期时间函数就很有限.Mysql为我们提供了不少此类的函数.是否可以利用Mysql函数来处理PHP程序呢?笔者做了以下的尝试. <?php     $data_time="1998-12-31 23:59:59";     $connect_id=mysql_connect('localhost');     $query_id=mysql_query("SELECT DATE_ADD('$

  • 提高php编程效率技巧

    用单引号代替双引号来包含字符串,这样做会更快一些.因为PHP会在双引号包围的字符串中搜寻变量,单引号则 不会,注意:只有echo能这么做,它是一种可以把多个字符串当作参数的"函数"(译注:PHP手册中说echo是语言结构,不是真正的函数,故把函数加 上了双引号).    1.如果能将类的方法定义成static,就尽量定义成static,它的速度会提升将近4倍. 2.$row['id'] 的速度是$row[id]的7倍. 3.echo 比 print 快,并且使用echo的多重参数(译注

  • PHP小技巧之函数重载

    1.可以使用func_get_args()和func_num_args()这两个函数实现函数的重载!! PHP代码: 复制代码 代码如下: function rewrite() {               $args = func_get_args();               if(func_num_args() == 1) {                       func1($args[0]);               } else if(func_num_args()

  • 10条php编程小技巧

    1.写程序的时候会用到这种情况,比如对一个数字进行四舍五入取整.很多人会这样写: 复制代码 代码如下: input a if a - int(a) >= 0.5 then a =  a +1 end if 其实这个判断语句可以使用一个很简单的表达式来写 复制代码 代码如下: a =  fix(a + sgn(a) *0.5) 用php写作: 复制代码 代码如下: $a =  intval($a +  0.5 *  ($a >0 ? 1 : -1)  ); 分析: 假设 a为 4.4 那么 a+

  • 十个PHP高级应用技巧果断收藏

    PHP 独特的语法混合了 C.Java.Perl 以及 PHP 自创新的语法.它可以比 CGI或者Perl更快速的执行动态网页.用PHP做出的动态页面与其他的编程语言相比,PHP是将程序嵌入到HTML文档中去执行,执行效率比完全生成HTML标记的CGI要高许多.下面介绍了十个PHP高级应用技巧. 1, 使用ip2long() 和 long2ip() 函数来把 IP 地址转化成整型存储到数据库里. 这种方法把存储空间降到了接近四分之一(char(15) 的 15 个字节对整形的 4 个字节),计算

  • PHP Mysql编程之高级技巧

    笔者做了以下的尝试. <?php  $data_time="1998-12-31 23:59:59";  $connect_id=mysql_connect('localhost');  $query_id=mysql_query("SELECT DATE_ADD(' $data_time',INTERVAL 1 YEAR)", $connect_id);  $data_time=mysql_result( $query_id,0); mysql_close(

  • PHP网站开发中常用的8个小技巧

    PHP是一种用于创建动态WEB页面的服务端脚本语言.如同ASP和ColdFusion,用户可以混合使用PHP和HTML编写WEB页面,当访 问者浏览到该页面时,服务端会首先对页面中的PHP命令进行处理,然后把处理后的结果连同HTML内容一起传送到访问端的浏览器.但是与ASP或 ColdFusion不同,PHP是一种源代码开放程序,拥有很好的跨平台兼容性.用户可以在Windows NT系统以及许多版本的Unix系统上运行PHP,而且可以将PHP作为Apache服务器的内置模块或CGI程序运行. 本

  • php数组键名技巧小结

    本文较为详细的总结了php数组键名的技巧.分享给大家供大家参考.具体分析如下: 1.$arr[true] 等价于 $arr[1]:$arr[false] 等价于 $arr[0]. 2.使null做为键名,相当于创建或覆盖一个$arr[null],可以使用$arr[null]或$arr[""]来访问. 3.使用带小数点的数字作为键名时,键名会自动截取整数部分作为键名.如$arr[123.45]=5,你使用$arr[123.45]或$arr[123]均可以取得键值:用foreach遍历时,

  • PHP的十个高级技巧(上中下)第1/3页

    全球超过300万个互联网网站的管理员都在使用PHP,使得它成为最为普及的服务器端脚本语言之一.其特点是运行速度快.稳定可靠.跨平台,而且是开放源代码软件.随你使用的水平不同,PHP可以很简单,也可以很复杂,可以只使用它发送HTML表格元素,还可以在PHP应用程序中集成Java和XML. 如果你对PHP有一定的了解或者看过一些初步的教材,这些技巧可以扩展你对PHP的认识,使你掌握一些常见的和高级的PHP功能. 一.把PHP安装为Apache的DSO  PHP在Linux/Unix平台上经常与Apa

随机推荐