這是 PHP 開發中極易踩中、且難以排查的經典坑:foreach 參考遍歷後的變量殘留會污染後續迴圈。本文深度剖析原理、展示現象、提供三種解決方案,並延伸至底層變量容器機制,幫助徹底理解並避免此問題。
$arr = range(1, 3); // [1, 2, 3]
// 第一輪:參考遍歷
foreach ($arr as &$val) {
// 無實際操作,僅建立參考綁定
}
// 第二輪:值遍歷(預期輸出 1,2,3)
foreach ($arr as $val) {
// 此時 $val 仍與 $arr[2] 綁定!
// 迴圈過程中 $val 依次被賦值為 1、2、3
// 最後一次迴圈結束後,$val=3,導致 $arr[2] 也變為 3
}
print_r($arr);
// 輸出:Array ( [0] => 1 [1] => 2 [2] => 2 ) // 最後一個元素被修改為 2!
PHP 變量在底層由 zval 結構體表示,包含:
foreach 參考遍歷機制:
foreach ($arr as &$val):PHP 為每個元素建立 is_ref=1 的 zval,$val 指向該 zval。迴圈結束後,$val 仍指向最後一個元素($arr[2])的 zval,且 is_ref=1、refcount=2($arr[2] 與 $val 共享)。foreach ($arr as $val):重用同一個 $val 變量。由於 $val 已是引用(is_ref=1),PHP 不會複製 zval,而是直接將迴圈值寫入該 zval。每次迴圈賦值都會修改 $arr[2] 的值!Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().
— PHP Manual: foreach
foreach ($arr as &$value) {
// 操作 $value
}
unset($value); // 關鍵:斷開 $value 與 $arr[最後元素] 的綁定
foreach ($arr as $value) {
// 正常值遍歷
}
print_r($arr); // [1, 2, 3] ✅
foreach ($arr as &$value) { }
foreach ($arr as $val) { } // $val 是新變量,不受 $value 引用影響
print_r($arr); // [1, 2, 3] ✅
foreach ($arr as &$value) { }
foreach ($arr as &$value) { } // 仍是引用,但第二輪賦值會正確更新各元素
print_r($arr); // [1, 2, 3] ✅
$arr1 = ['a' => 1, 'b' => 2, 'c' => 3];
$arr2 = ['x' => 4, 'y' => 5, 'z' => 6];
foreach ($arr1 as $key => &$val) {} // $key 也成為引用!
foreach ($arr2 as $key => $val) {} // $key 重用,污染 $arr1 最後鍵名對應的值
var_dump($arr1); // ['a'=>1, 'b'=>2, 'c'=>&int(6)] ← c 的值變為 6 的引用!
var_dump($arr2); // ['x'=>4, 'y'=>5, 'z'=>6]
結論:foreach ($arr as $key => &$val) 會同時使 $key 與 $val 成為引用,同樣需要 unset($key, $val)。
| 場景 | 建議做法 |
|---|---|
| 需要修改陣列元素 | foreach ($arr as &$v) { ... } unset($v); |
| 僅讀取陣列元素 | foreach ($arr as $v) { ... }(無參考,無風險) |
| 需獲取鍵名並修改值 | foreach ($arr as $k => &$v) { ... } unset($k, $v); |
| 嵌套 foreach | 每層結束後 unset 參考變量,或使用不同變量名 |
| 函數參數為陣列引用 | function foo(array &$arr) 明確宣告,調用端無需 & |
collect($arr)->map(fn($v) => $v*2)->all(),鏈式操作、不可變、型別安全。理解 foreach 參考機制,不僅能避免生產事故,更能加深對 PHP 變量容器、寫時複製、引用計數核心機制的掌握。養成「參考必 unset」習慣,是 PHP 進階工程師的基本素養。