foreach ($data as &$item) 循环引用产生的问题

Version

PHP version 7.4

问题呈现

最小化分析代码:

1
2
3
4
5
6
7
8
9
$data = ['foo', 'bar'];

foreach ($data as &$item) {
}

foreach ($data as $item) {
}

print_r($data);

输出结果:

1
2
3
4
5
Array
(
[0] => 'foo'
[1] => 'foo'
)

我们可以发现,$data的值莫名奇妙变了,而它只是经过了两个空循环而已,发生了什么?!

先总结一下 PHP 中两条关于引用的两个规则:

  • 给引用变量赋值,实际上是给引用所指向的变量赋值
  • 一个引用变量可以被修改为对另外一个变量的引用

下面我来一行行代码分析产生这个问题的原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$data = ['foo', 'bar'];

// 循环开始,$item 变量不存在,新建一个$item 变量,且是一个引用变量,它不指向任何变量地址
foreach ($data as &$item) {
// loop 1: 执行了 $item = &$data[0];$item 指向 $data[0] 的地址
// loop 2: 执行了 $item = &$data[1];$item 指向 $data[1] 的地址
}
// 提示:这个循环没有改变 $data 的数据,只是 $item 依然指向第二个元素 的地址

// 循环开始,$item 变量存在,不会新建变量
foreach ($data as $item) {
// loop 1: 执行了 $item = $data[0];$item 所指向的变量(即 第二个元素)的值被修改为$data[0](即'foo'),这里已经导致了$data 两个元素都等于 'foo'
// loop 2: 执行了 $item = $data[1];由于$item 指向的是$data[1],实际上相当于执行$data[1] = $data[1],没有任何意义
}
// 最后$data 中的两个元素都是 'foo'

如何避免这个问题1:

1
2
3
4
5
foreach ($data as &$item) {

// 每次 loop 销毁$item (实际上只要在最后一次 loop 销毁即可,因此你可以把 unset 写到 foreach 后面,就是不是很好看)
unset($item);
}

如何避免这个问题2:

不使用引用