PHP 引用 foreach 结束后,再次遍历导致数组最后一项被覆盖
PHP 引用遍历结束后,循环变量仍绑定最后一个元素。再次赋值或遍历可能覆盖数组末项;及时 unset 循环变量,并按键和值核对受影响数据。
前一次遍历结束时数组正常,第二次只读取元素,最后一项却变成倒数第二项的值。若前一个循环使用过 &$item,先检查这个引用有没有解除:
<?php
$items = ['alpha', 'beta', 'gamma'];
foreach ($items as &$item) {
// 这里没有修改元素
}
foreach ($items as $item) {
// 这里只是取值,仍会覆盖最后一项
}
print_r($items);
按这种写法,数组最后一项会变成 beta。本文针对普通 PHP 数组、引用遍历之后复用同一变量的情况,示例适用于 PHP 7 和 PHP 8。它不要求数据库、缓存或并发操作参与。
循环结束,没有解除最后一次引用
第一次循环中的 $item 与当前数组元素建立引用关系。循环走完后,最后一次引用仍然存在,也就是 $item 仍关联 $items[2]。
第二次循环虽然没有 &,但每轮仍要把当前元素赋给 $item。这个赋值会沿着已有引用修改数组末项:第一轮把末项改成 alpha,第二轮改成 beta,到第三轮读取末项时,原来的 gamma 已经不在了。
判断时应检查两个循环之间的边界,而不只是第二个循环内部。对该边界前后的数组做比较,才能确定末项是在哪里第一次发生变化的。不要因为第二个循环体为空,就排除它对循环变量的赋值。
在引用循环后立即 unset 循环变量
在进入其他赋值或遍历之前解除绑定:
<?php
$items = ['alpha', 'beta', 'gamma'];
foreach ($items as &$item) {
$item = strtoupper($item);
}
unset($item);
foreach ($items as $item) {
echo $item, PHP_EOL;
}
print_r($items);
这个例子的预期结果是三个元素分别为 ALPHA、BETA、GAMMA,第二轮遍历不会再改写末项。
unset($item) 解除的是循环变量与值之间的绑定,不会删除 $items 中的最后一个元素。不要误写成 unset($items[2]),也不要用 $item = null 代替:后者仍是通过引用赋值,会把最后一项改成 null。
把第二个循环的变量改名,可以避开那一次覆盖,但旧引用仍然留着,后续再次给旧变量赋值仍有影响。应在引用循环结束后立即解除绑定。
不需要引用时,直接通过键更新
如果只是读取数组,不使用引用即可。需要按值计算并写回原数组,也可以采用键赋值:
<?php
$items = ['alpha', 'beta', 'gamma'];
foreach ($items as $key => $value) {
$items[$key] = strtoupper($value);
}
print_r($items);
这个独立示例没有创建循环变量的引用。若是在已有代码后面接上它,仍须先解除此前遗留的引用,不能只删掉新循环中的 &。
验证时检查数组长度、键与每项内容,而不只看最后两项是否相等:原始数据本来就可能存在重复值。还要覆盖空数组、单元素数组,以及同一函数内连续执行的多次遍历。
已经被覆盖的数据不会在 unset 后自动回来。修复代码前保留可信的原始输入;如错误结果已经写入数据库,应依据原始数据定位受影响记录并修正,不能通过去重来猜测丢失的值。


