notes
notes copied to clipboard
PHP foreach引用赋值方式遍历完成后必须清除引用 or The reference &$value must be destroyed after the foreach loop
情景1
$x = ['zero','one','two', 'three'];
foreach ($x as &$v)
{
// do nothing
}
foreach ($x as $v)
{
echo $v.'-'.$x[3].PHP_EOL;
}
结果
zero-zero
one-one
two-two
two-two
说明
可以看到,由于引用赋值的变量 &$v 没有被销毁,因此 $x[3] 指向的始终是这个 &$v 的引用。
当 &$v 的值随着第二个 foreach 循环被不断改写时,$x[3] 的值也随着 &$v 不断变化。
直到指针循环到 [3] 三时,$x[3] 的值是上一个指针时 &$v 的值。
所以,最后 $v - $x[3] 两次打印都是 two-two
情景2
function test(array $y): void
{
foreach ($y as $k => $v)
{
$y[$k] = 'hello world!';
}
}
$x = [1, 2, 3, 4];
foreach ($x as &$v)
{
// do nothing
}
test($x);
print_r($x);
$v = 'abc';
print_r($x);
结果
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => hello world!
)
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => abc
)
说明
可以看到代码中 test() 方法并没有定义引用传参,而且也没有任何返回值。
但是 $x 数组还是被改变了,而且只有 $x[3] 的值被改变了。
这是因为 foreach 以引用赋值方式遍历的时候,数组最后一个元素的 &$v 引用在循环完成之后仍会保留。
所以,在循环完成后,不论是修改 $x[3] 还是 &$v 都会引起彼此的变化。
而且,由于 $x[3] 已经指向了一个引用,因此当 $x 作为参数传递时,元素 $x[3] 始终是被作为引用传递的。
所以,当在 test() 函数内改变这个引用的值时,指向这个引用的所有变量当然会跟着变。
结论
为了避免 情景1 和 情景2 中的情形出现,在循环完成后务必对引用变量进行销毁。
以免引起难以发现的错误。比如将引用赋值循环后的结果不断向下传递,那么一旦出现问题,Debug起来可就没有那么容易了。
解决办法
foreach ($data as &$v)
{
// ...
}
unset($v);