PHP 參考(Reference)雖強大,但因「變數可重複綁定參考」、「陣列元素參考會改變元素型別」、「foreach 參考殘留」等特性,極易產生難以追蹤的 Bug。本文深入剖析三大經典陷阱、底層機制,並提供避免使用參考的實務替代方案。
$x = 21; $y = 7;
$z = &$x; // $z 參考 $x
$z = &$y; // $z 改參考 $y(舊綁定自動解除)
var_dump($x, $y, $z); // 21, 7, 7 ← $x 未受影響
不同於 C++「參考不可重綁定」,PHP 參考更像「別名可隨時改指向」。這導致程式碼閱讀時無法靜態判斷變數最終指向何處。
$a = [21, 7];
$c = &$a[0]; // 取第一個元素參考
$b = $a; // 陣列複製(本應為值複製)
$b[0] = "21";
var_dump($a); // [0] => &string(2) "21" ← $a[0] 變成參考型別!
原因:對陣列元素取參考(&$a[0])會將該元素標記為 is_ref=1。後續陣列複製時,該元素採用**參考複製**而非值複製,導致修改 `$b[0]` 影響 `$a[0]`。
**更詭異**:若 unset($b); unset($c); 移除所有參考,$a[0] 又會恢復正常值型別。
$arr = [1,2,3];
foreach ($arr as &$v) { $v *= 2; } // $arr 變 [2,4,6]
unset($v); // 必須 unset!
foreach ($arr as $v) { ... } // 若未 unset,$v 仍參考 $arr[2],迴圈會修改最後一個元素
這是 PHP 官方文件明確警告的陷阱,無數專案因此產生離譜 Bug。
is_ref=1,阻止 COW,直接修改共享 zval。
// ❌ 參考傳遞
function process(&$data) { $data['status'] = 'done'; }
// ✅ 回傳新陣列(函數式風格)
function process($data) { $data['status'] = 'done'; return $data; }
$data = process($data);
// ✅ 物件封裝(狀態內聚)
class Processor { public function process(Data $d) { $d->status = 'done'; } }
$processor->process($data);
// ❌ 參考參數
function divide($a, $b, &$quotient, &$remainder) { ... }
// ✅ 回傳關聯陣列
function divide($a, $b) {
return ['quotient' => intdiv($a,$b), 'remainder' => $a % $b];
}
$result = divide(10, 3);
// ❌ 參考
foreach ($arr as &$v) { $v = strtoupper($v); } unset($v);
// ✅ 鍵名存取
foreach ($arr as $k => $v) { $arr[$k] = strtoupper($v); }
// ✅ array_map (PHP 7.4+ 箭頭函式)
$arr = array_map(fn($v) => strtoupper($v), $arr);
PHP 內核自動處理:唯讀傳遞不複製,修改時才複製。手動加參考反而破壞 COW 優化,增加心智負擔。
foreach ($arr as &$v) 修改原陣列(且陣列極大、效能敏感) → 用畢 立即 unset($v)。PHP 參考是「雙面刃」:看似節省記憶體、方便修改外部變數,實則引入「動態別名重綁定」、「陣列元素型別污染」、「foreach 殘留」三大不可控行為。現代 PHP 開發**原則上拒絕參考**,改用**回傳值、物件封裝、函數式陣列操作**,讓資料流向單向、可預測、易測試。若不得不寫參考,務必加上詳細註解並最小化作用域。