PHP 语言让 WEB 端程序设计变得简单,这也是它能流行起来的原因。但也是因为它的简单,PHP 也慢慢发展成一个相对复杂的语言,层出不穷的框架,各种语言特性和版本差异都时常让搞的我们头大,不得不浪费大量时间去调试。这篇文章列出了十个最容易出错的地方,值得我们去注意。
易犯错误 #1: 在 foreach循环后留下数组的引用还不清楚 PHP 中 foreach 遍历的工作原理?如果你在想遍历数组时操作数组中每个元素,在 foreach 循环中使用引用会十分方便,例如
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr 现在是 array(2, 4, 6, 8)问题是,如果你不注意的话这会导致一些意想不到的负面作用。在上述例子,在代码执行完以后,$value 仍保留在作用域内,并保留着对数组最后一个元素的引用。之后与 $value 相关的操作会无意中修改数组中最后一个元素的值。
你要记住 foreach 并不会产生一个块级作用域。因此,在上面例子中 $value 是一个全局引用变量。在 foreach 遍历中,每一次迭代都会形成一个对 $arr 下一个元素的引用。当遍历结束后, $value 会引用 $arr 的最后一个元素,并保留在作用域中
这种行为会导致一些不易发现的,令人困惑的bug,以下是一个例子
$array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // 通过引用遍历 echo implode(',', $array), "\n"; foreach ($array as $value) {} // 通过赋值遍历 echo implode(',', $array), "\n";以上代码会输出
1,2,3 1,2,3 1,2,2你没有看错,最后一行的最后一个值是 2 ,而不是 3 ,为什么?
在完成第一个 foreach 遍历后, $array 并没有改变,但是像上述解释的那样, $value 留下了一个对 $array 最后一个元素的危险的引用(因为 foreach 通过引用获得 $value )
这导致当运行到第二个 foreach ,这个"奇怪的东西"发生了。当 $value 通过赋值获得, foreach 按顺序复制每个 $array 的元素到 $value 时,第二个 foreach 里面的细节是这样的
第一步:复制 $array[0] (也就是 1 )到 $value ($value 其实是 $array最后一个元素的引用,即 $array[2]),所以 $array[2] 现在等于 1。所以 $array 现在包含 [1, 2, 1]
第二步:复制 $array[1](也就是 2 )到 $value ( $array[2] 的引用),所以 $array[2] 现在等于 2。所以 $array 现在包含 [1, 2, 2]
第三步:复制 $array[2](现在等于 2 ) 到 $value ( $array[2] 的引用),所以 $array[2] 现在等于 2 。所以 $array 现在包含 [1, 2, 2]
为了在 foreach 中方便的使用引用而免遭这种麻烦,请在 foreach 执行完毕后 unset() 掉这个保留着引用的变量。例如
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value 不再引用 $arr[3] 常见错误 #2: 误解 isset() 的行为尽管名字叫 isset,但是 isset() 不仅会在变量不存在的时候返回 false,在变量值为 null 的时候也会返回 false。
这种行为比最初出现的问题更为棘手,同时也是一种常见的错误源。
看看下面的代码:
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }开发者想必是想确认 keyShouldBeSet 是否存在于 $data 中。然而,正如上面说的,如果 $data['keyShouldBeSet'] 存在并且值为 null 的时候, isset($data['keyShouldBeSet']) 也会返回 false。所以上面的逻辑是不严谨的。
我们来看另外一个例子:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }上述代码,通常认为,假如 $_POST['active'] 返回 true,那么 postData 必将存在,因此 isset($postData) 也将返回 true。反之, isset($postData) 返回 false 的唯一可能是 $_POST['active'] 也返回 false。
然而事实并非如此!
如我所言,如果$postData 存在且被设置为 null, isset($postData) 也会返回 false 。 也就是说,即使 $_POST['active'] 返回 true, isset($postData) 也可能会返回 false 。 再一次说明上面的逻辑不严谨。
顺便一提,如果上面代码的意图真的是再次确认 $_POST['active'] 是否返回 true,依赖 isset() 来做,不管对于哪种场景来说都是一种糟糕的决定。更好的做法是再次检查 $_POST['active'],即:
if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }对于这种情况,虽然检查一个变量是否真的存在很重要(即:区分一个变量是未被设置还是被设置为 null);但是使用 array_key_exists() 这个函数却是个更健壮的解决途径。