一般PHP地雷
其他人在编写PHP Web应用程序时发现了什么惊喜? 有一个众所周知的问题是编译时类的inheritance问题,但是我知道其他一些问题,并且想要尝试构build一个语言中最重要的问题列表。
注意:
我已经担任了PHP5的高级开发人员,所以PHP工作支付我的账单,这个问题并不意味着要把PHP当做一种语言来破译,因为我所使用的每一种语言都有一些众所周知的或者不是很知名的意外。
我不确定这是否重要,但编译PHP脚本的需求是一个巨大的性能问题。 在任何严肃的PHP项目中,您都需要某种编译器caching,如APC , eAccelerator , PHP加速器或(商业) Zend平台 。
recursion引用泄漏内存
如果您创build两个对象并将它们存储在对方的属性中,则垃圾收集器将永远不会触及它们:
$a = new stdClass; $b = new stdClass; $a->b = $b; $b->a = $a;
当一个大类创build一个通常存储主类的小型辅助对象时,这实际上很容易:
// GC will never clean up any instance of Big. class Big { function __construct() { $this->helper = new LittleHelper($this); } } class LittleHelper { function __construct(Big $big) { $this->big = $big; } }
只要PHP针对短快速页面请求,他们不可能解决这个问题。 这意味着PHP不能依赖于守护进程或其他长寿命的应用程序。
require_once和include_once在过度使用时经常会导致主要的性能杀手。 如果你包含/需要一个文件,持有一个类…这样的模式可以节省一些严重的处理时间。
class_exists("myFoo") or require("myFoo.someClass.php");
更新:这仍然是一个问题 – http://www.techyouruniverse.com/software/php-performance-tip-require-versus-require_once
更新:阅读以下问题的select答案: 性能会受到损害使用自动加载在PHP和search类文件? 如果按照这些方法实施,则尽可能地尽可能减小文件包含/需要的处罚。
一个有趣的地雷: 当register_globals
打开时,全局variables会影响$_SESSION
。 但是我想这就是当地雷本身register_globals打开的时候会发生什么。
NULL和“0”string在PHP中是纯粹的邪恶
if ("0" == false) //true if ("0" == NULL) //true if ("0" == "NULL")//true
-
foreach()默默地在后台复制数组,然后遍历该副本。 如果你有一个大的arrays,这会降低性能。 在这些情况下,foreach()的引用选项是php5的新增function,或者使用for()循环。
-
注意平等(==)与身份(===)。
-
请注意什么是空()与什么构成isset()。
现在有更多的地雷,我有更多的时间:
- 不要比较花车平等。 PHP不是matlab,它不是为精确的浮点运算而devise的。 试试这个:
if (0.1 + 0.2 == 0.3) echo "equal"; else echo "nope"; // <-- ding ding
- 同样,不要忘记你的八进制! 带有前导零的int将作为八进制数来投射。
if (0111 == 111) echo "equal"; else echo "nope"; // <-- ding ding
在事实发生后,这种情况显而易见,但是在foreach中使用范围和引用的知识却是众所周知的。
foreach($myArray as &$element){ //do something to the element here... maybe trim or something more complicated } //Multiple lines or immediately after the loop $element = $foobar;
数组中的最后一个单元现在变成了$ foobar,因为上面的foreach中的引用仍然在当前的上下文范围内。
__autoload()
最近certificate是我的主要地雷。 我们的一些遗留的代码和库使用class_exists()
,它试图自动加载那些永远不会以这种方式加载的类。 大量的致命错误和警告。 class_exists()
仍然可以使用,如果你有自动加载,但第二个参数(自PHP 5.2.0以来的新的)必须设置为false
不知道运营商的优先权可能会导致一些问题:
if ($foo = getSomeValue() && $bar) { // … } // equals if ($foo = (getSomeValue() && $bar)) { // … }
应始终避免@
错误消音器。
一个例子:
// Don't let the user see an error if this unimportant header file is missing: @include 'header.inc.php';
使用上面的代码,您将永远不会知道header.inc.php
中的任何代码中的任何错误,或者从header.inc.php
调用的任何函数中的任何错误,并且如果在某处存在致命错误,您的网页将停下来无法找出错误是什么。
我见过的大个子陷入了精确(php和其他语言)。
如果你想要一点乐趣,比较任何浮动与> =整体,并找出多less次,你会得到预期的结果。
这已经是很多PHP内部工作人员的失败,并试图根据不允许舍入为整数的比较来作出逻辑决策。
例如 – 面料
织物以1码或1码半单位出售,并保持织物左侧精确测量的清单。
如果这个系统不是用整数表示,而是用浮点数来表示的话,那么这个系统很难做出坚定的决定。
你最好的办法是把1码半码表示为1,例如,如果你有300码的面料,你可以有一个600(600码半码单位)的库存。
无论如何,这是我的窍门 – 时间重构4个月的编程,由于不了解的精度….
数字string自动转换为整数
这是迄今为止,PHP中最丑陋,最晦涩的黑客攻击手段。 只要你有一个全是数字的string,在某些情况下它会自动被视为是整数。
php > var_dump("0" == "00"); bool(true)
这可能会真正讨厌与PHP的“关联数组”相结合,导致怪异$a == $b
并不意味着$arr[$a] == $arr[$b];
php > var_dump(array('00'=>'str(zerozero)', '0'=>'str(zero)')); array(2) { ["00"]=> string(13) "str(zerozero)" [0]=> string(9) "str(zero)" }
我最喜欢的PHP陷阱:
考虑这个包括:
# ... lots of code ... $i = 42; # ... more code ...
然后使用这个包括某处:
for($i = 0; $i < 10; $i++){ # ... include 'that_other_file.php'; }
然后尝试猜测循环运行多less次。 是的,曾经。 词汇范围(和适当的dynamic范围)都是解决的问题。 但不是在PHP中。
如果您习惯于使用智能逻辑运算符的语言,您将尝试执行如下操作:
$iShouldTalkTo = $thisObj || $thatObj;
在PHP中, $iShouldTalkTo
现在是一个布尔值。 你被迫写:
$iShouldTalkTo = $thisObj ? $thisObj : $thatObj;
在PHP的早期devise决定试图抓住无能的程序员的手中,以换取权威的程序员的交换的所有例子中,这可能是最刺激我的那个。
switch()
构造中的深度脑损伤很多。 考虑一下:
switch($someVal) { case true : doSomething(); break; case 20 : doSomethingElse(); break; }
原来doSomethingElse()
将永远不会被调用,因为“case true”会吸收所有真实的$ someVal。
想想这也许是合理的吗? 那么试试这个:
for($ix = 0; $ix < 10; $ix++) { switch($ix) { case 3 : continue; default : echo ':'; } echo $ix; }
猜猜它的输出是什么? 应该是:0:1:2:4:5:6:7:8:9,对不对? 不,这是:0:1:23:4:5:6:7:8:9 也就是说,它忽略了 continue
语句的语义并把它当作break
。
其中最糟糕的一个是PHP的“关联数组”的概念,这是一个数组,一个字典和一个列表的完全混合。 PHP的作者似乎不确定它在每种情况下应该如何performance,这会导致怪异,例如数组的加运算符和array_merge
函数的不同行为。
php > $a = array(1=>'one'); php > $b = array(2=>'two'); php > var_dump($a+$b); /* plus preserves original keys */ array(2) { [1]=> string(3) "one" [2]=> string(3) "two" } php > var_dump(array_merge($a,$b)); /* array_merge reindexes numeric keys */ array(2) { [0]=> string(3) "one" [1]=> string(3) "two" } php > $a = array(1=>'one'); php > $b = array(1=>'another one'); php > var_dump($a+$b); /* plus ignores duplicate keys, keeping the first value */ array(1) { [1]=> string(3) "one" } php > var_dump(array_merge($a,$b)); /* array_merge just adds them all, reindexing */ array(2) { [0]=> string(3) "one" [1]=> string(11) "another one" } php > $a = array(1,2,3); php > $b = array(4,5,6); /* non-associative arrays are really associative arrays with numeric keys… */ php > var_dump($a+$b); /* … so plus doesn't work as you'd normally expect */ array(3) { [0]=> int(1) [1]=> int(2) [2]=> int(3) } php > var_dump(array_merge($a,$b)); /* you should use array_merge instead */ array(6) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) [4]=> int(5) [5]=> int(6) }
运行PHP时的内存总量。 许多大型项目都包含所有的类文件,并在需要时使用它们。 这增加了PHP每次运行所需的总内存。
此外项目使用帧或IFrames可以轻松地加倍你的内存使用量。
所以使用你的类文件的条件加载,没有加载,你不使用
PHP应用程序的性能问题通常是下列之一:
- 文件系统访问 – 读取和写入磁盘
- 这就是APC,eAccelerator等派上用场的地方,它们通过在内存中cachingparsing的PHP文件来减less文件系统访问
- 数据库 – 慢查询,大数据集
- networkingI / O – 访问外部资源
遇到与PHP(或用任何语言编写的任何Web应用程序)有关的性能问题是非常罕见的。 上述问题通常比代码执行慢几个数量级。
一如既往,简介您的代码!
PHP的另一个缺陷是,我发现来自其他语言但不经常出现的错误。
<?php /** * regular */ echo (true && true); // 1 echo (true && false); // nothing echo (true || false); // 1 echo (false || false); // nothing echo (true xor false); // 1 echo (false xor false); // nothing /** * bitwise */ echo (true & true); // 1 echo (true & false); // 0 echo (true | false); // 1 echo (false | false); // 0 echo (true ^ false); // 1 echo (false ^ false); // 0 ?>
没有得到if / else分支的编译器消息:
if( $foo ) { some_function(); } else { non_existing_function(); // oops! }
PHP不会提到non_existing_function
不存在,直到你input$foo
为false的情况。
忘记设置:
error_reporting( E_ALL );
所以通知不被捕获,花费时间debugging:
- 不存在的variables
- 无效的对象属性
- 无效的数组键
将不同“types”/来源的string粘贴在一起,
// missing mysql_real_escape_string() or an int cast ! $sql = "SELECT * FROM persons WHERE id=$id"; // missing htmlentities() and urlencode() ! $html = "<a href='?page=$id'>$text</a>";
根据为什么调用一个函数(如strlen,count等)在一个引用的值如此之慢?
如果你通过引用将一个variables传递给一个函数,然后调用一个函数,这个variables非常慢。
如果循环调用函数并且variables很大,则可能比variables按值传递要慢许多个数量级。
例:
<?php function TestCount(&$aArray) { $aArray = range(0, 100000); $fStartTime = microtime(true); for ($iIter = 0; $iIter < 1000; $iIter++) { $iCount = count($aArray); } $fTaken = microtime(true) - $fStartTime; print "took $fTaken seconds\n"; } $aArray = array(); TestCount($aArray); ?>
这一直需要大约20秒钟在我的机器上运行(在PHP 5.3上)。
但是如果我改变函数传递的值(即function TestCount($aArray)
而不是function TestCount(&$aArray)
),那么它运行在大约2毫秒 – 从字面上快10000倍 !
对于任何按值传递的函数( strlen
)和用户定义函数的内置函数都是如此。
这是我以前不知道的一个相当可怕的tarpit!
幸运的是,在许多情况下,都有一个简单的解决方法 – 在循环中使用临时局部variables,并在最后复制到引用variables。
只是想到了更多的惊喜。 对数组应用callback的array_map是一个严重的性能杀手。 我不完全确定为什么,但我认为这与PHP的循环写入机制的复制有关。
在开始的时候,可能会花费大量的时间来debugging这种代码:
$a = 1; echo $a; # 1 echo "$a"; # 1 echo '$a'; # $a
该死的报价! 很沮丧:(
types转换和三重平等
通常在大多数语言中,当您操作两种不同types的数据时,您可能会得到一个exception,或者其中一个被转换为更一般的数据。 在语言中,除PHP以外,string被认为比整数更普遍。 只有在PHP中你有:
php > var_dump('nada' == 0); bool(true)
为了应付那个PHP引进的三等分运算符。 如果值是相同的types和相同的值,则根据定义返回true。 适用于以上示例:
php > var_dump('nada' === 0); bool(false)
但是当你真正想要价值观是平等的时候,它也performance得相当丑陋。
php > var_dump(0.0 === 0); bool(false)
如果你使用任何其他语言的经验与PHP的工作,你一定会有这个问题。
$x = array(); $x == null ? "true": "false";
输出是“真”。
$x = array("foo"); $x == null ? "true": "false";
输出是“假”;