foreach 是 PHP 遍歷陣列/對象的核心構造,但其引用機制、拷貝語義、內部指針行為隱藏著多個經典陷阱。本文系統梳理 foreach 核心機制、三大經典坑、性能特徵與工程化最佳實踐。
<?php
// 值遍歷(預設:拷貝值)
foreach ($array as $value) { ... }
// 鍵值遍歷
foreach ($array as $key => $value) { ... }
// 引用遍歷(修改原陣列元素)
foreach ($array as &$value) { ... }
// 引用鍵值
foreach ($array as $key => &$value) { ... }
?>
<?php
$arr = [1, 2, 3];
foreach ($arr as &$v) { $v *= 2; } // $v 為引用
// 此時 $v 仍引用 $arr[2] (值 6)
foreach ($arr as $v) { // 無 &,但 $v 仍是引用!
echo $v . ' '; // 輸出: 2 4 6 (正常)
}
// 但若修改 $v:
$v = 999;
echo $arr[2]; // 999!汙染原陣列
// 修正:循環後立即銷毀引用
foreach ($arr as &$v) { $v *= 2; }
unset($v); // 關鍵!
?>
根因:PHP 引用變量在作用域結束前不銷毀,後續同名變量復用同一符號表條目。
<?php
$arr = [1,2,3];
// 情況 A:普通陣列(值拷貝)
foreach ($arr as $v) {
$arr = []; // 重置陣列
echo $v; // 輸出: 123(遍歷的是拷貝快照)
}
// 情況 B:引用陣列
$ref = &$arr;
foreach ($ref as $v) {
$arr = [];
echo $v; // 輸出: 1(遍歷被清空的原陣列)
}
// 情況 C:全局變量(隱式引用)
global $arr;
$arr = [1,2,3];
foreach ($arr as $v) {
$arr = [];
echo $v; // 輸出: 1
}
?>
原則:foreach 遍歷的目標若為引用/全局,則操作原陣列;否則操作拷貝。
<?php
$undefined; // 未定義
foreach ($undefined as $v) { } // Warning: Invalid argument supplied
$null = null;
foreach ($null as $v) { } // Warning
// 防護寫法
foreach ((array)$maybeArray as $v) { } // 強制轉型,安全無警告
?>
<?php
$arr = ['a'=>1, 'b'=>2, 'c'=>3];
// foreach 會重置內部指針
next($arr); // 指向 b
foreach ($arr as $k => $v) { /* ... */ }
echo key($arr); // 'a' (指針被重置)
// 但不影響 current()/next() 等函數在循環外的行為
foreach ($arr as $k => $v) {
// 循環內 current() 指向當前元素
}
?>
| 場景 | foreach | for + count | array_walk |
|---|---|---|---|
| 鍵值遍歷 | ⭐⭐⭐ 最快 | 需額外 key() | 需回調開銷 |
| 只需值 | ⭐⭐⭐ | 索引訪問略快 | 回調開銷大 |
| 需修改原值 | 引用模式 ⭐⭐⭐ | 索引賦值 | 引用回調 |
| 中途 break | 支援 | 支援 | 不支援(需拋異常) |
| 多維陣列遞歸 | 配合 RecursiveIteratorIterator | 複雜 | array_walk_recursive |
<?php
// 只讀:無引用,無副作用,JIT 優化最好
foreach ($users as $user) {
echo $user->name;
}
?>
<?php
foreach ($items as &$item) {
$item['processed'] = true;
}
unset($item); // 必須!
?>
<?php
function fetchUsers(): Generator {
$stmt = $db->query('SELECT * FROM users');
while ($row = $stmt->fetch()) {
yield $row; // 惰性加載,記憶體常數
}
}
foreach (fetchUsers() as $user) {
// 處理單條,無需載入全表
}
?>
<?php
class UserCollection implements IteratorAggregate {
private array $users = [];
public function getIterator(): Traversable {
return new ArrayIterator($this->users);
}
}
$collection = new UserCollection();
foreach ($collection as $user) { /* ... */ }
?>
<?php
// 工具函數
function safeIterate(mixed $data, callable $callback): void {
if (!is_array($data) && !$data instanceof Traversable) {
return; // 或拋異常
}
foreach ($data as $k => $v) {
$callback($k, $v);
}
}
?>
foreach 是 PHP 最高級的遍歷抽象,掌握其拷貝/引用雙模式、引用殘留機制、內部指針重置行為,配合防護性轉型、Generator 惰性遍歷、IteratorAggregate 物件遍歷,可寫出安全、高效、現代化的遍歷代碼。