请看下面的代码,感受一下执行结果是否符合自己的预期,如果不符合那么以后写代码的时候就要注意一下。两次遍历之后,数组的值被改变了,即便是第一次遍历 什么都不做,只不过第一次遍历是通过引用的方式,第二次就是只读模式。
<?php
$test = [1,2,3,4];
foreach ($test as &$v) { }
echo json_encode($test).PHP_EOL;
var_dump($v);
foreach ($test as $v) {
echo $v . ' => '. json_encode($test).PHP_EOL;
}
/**
foreach ($test as &$v) { }
echo json_encode($test).PHP_EOL;
unset($v);
var_dump($v);
foreach ($test as $v) {
echo $v . ' => '. json_encode($test).PHP_EOL;
}
*/
原因
PHP foreach 通过引用 & 遍历的时候,在遍历结束之后,会产生一个隐藏的变量,迭代变量,示例程序中是(&v),并且这个变量是对数组的一个引用,意思就 是这个变量和数组中的变量存在一个地址,其中的一个值发生变化,都会引起另外一个值的变化。示例程序中,紧接着进行第二次遍历,在这一次遍厉中,我们用 了同样的迭代变量&v,在这次foreach迭代的过程中,$v 的值不断的被覆盖,而这里的$v 时机上是数组最后一个元素的引用,导致了在遍历过程中,数组的值其实 一直在动态的变化,下面就是变化过程。
我们略加说明:第一次遍历之后,产生了一个隐藏的迭代变量,这个变量是数组的一个引用,上面已经详细描述。这个坑 的猫腻就从这个隐藏的引用变量开始。在第二次遍历,我们采用了和这个隐藏变量相同的变量,实际上就是直接使用了这个引用变量作为了迭代变量。在迭代过程中, 隐藏变量的值一直被覆盖,从而导致数组的内容实际上在一直被动变化,本来的只读访问实际上已经变成了写入,这就非常危险了。写入的过程可以通过测试代码 看出。 第一次迭代,数组变化成 [1,2,3,1],第二次变化成 [1,2,3,2],… 一直到倒数第二次,数组变成了 [1,2,…v[n],v[n]],导致了最后两个元素相 同,最后一次,v[n] 覆盖 v[n],迭代结束。所以最终数组的内容成了 [1,2,… v[n], v[n]]. 这就是整个过程的细节。
[1,2,3,4]
int(4)
1 => [1,2,3,1]
2 => [1,2,3,2]
3 => [1,2,3,3]
3 => [1,2,3,3]
解决办法
在知道了这个坑之后,我们就不难知道解决办法了。既然猫腻的根源在于那个隐藏的变量,我们从这里下手即可。
- 可以直接干掉这个隐藏变量 unset($v);
- 可以在下一次换一个新的迭代变量名称
推荐1,因为暴露一个隐藏的数组修改方式太不安全,无意识操作很有可能,所以最好在foreach 引用迭代之后,主动销毁迭代变量,以免后患。
参考资料
https://php.net/manual/zh/control-structures.foreach.php