foreach 是 PHP 中最常用的遍歷構造,但其底層如何與 Iterator 介面交互、方法調用順序如何、如何正確實現自定義迭代器,往往被忽視。掌握這些細節對調試遍歷異常、優化大數據集內存佔用、實現惰性計算至關重要。
| 方法 | 職責 | 返回值 |
|---|---|---|
rewind() | 將指針重置到第一個元素 | void |
valid() | 檢查當前位置是否有效 | bool |
current() | 返回當前元素的值 | mixed |
key() | 返回當前元素的鍵 | mixed |
next() | 指針移動到下一個元素 | void |
foreach ($iterator as $key => $value) {
// 循環體
}
等價於:
$iterator->rewind();
while ($iterator->valid()) {
$key = $iterator->key();
$value = $iterator->current();
// 循環體
$iterator->next();
}
class MyIterator implements Iterator {
private $position = 0;
private $array = ["first_element", "second_element", "last_element"];
public function __construct() { $this->position = 0; }
public function rewind() {
var_dump(__METHOD__);
$this->position = 0;
}
public function current() {
var_dump(__METHOD__);
return $this->array[$this->position];
}
public function key() {
var_dump(__METHOD__);
return $this->position;
}
public function next() {
var_dump(__METHOD__);
++$this->position;
}
public function valid() {
var_dump(__METHOD__);
return isset($this->array[$this->position]);
}
}
$it = new MyIterator();
foreach ($it as $key => $value) {
echo "輸出鍵值:";
var_dump($key, $value);
echo "\n";
}
string(18) "MyIterator::rewind" string(17) "MyIterator::valid" string(19) "MyIterator::current" string(15) "MyIterator::key" 輸出鍵值:int(0) string(13) "first_element" string(16) "MyIterator::next" string(17) "MyIterator::valid" string(19) "MyIterator::current" string(15) "MyIterator::key" 輸出鍵值:int(1) string(14) "second_element" string(16) "MyIterator::next" string(17) "MyIterator::valid" string(19) "MyIterator::current" string(15) "MyIterator::key" 輸出鍵值:int(2) string(12) "last_element" string(16) "MyIterator::next" string(17) "MyIterator::valid" // 返回 false,循環結束
class MyIterator implements Iterator {
private $var = array();
public function __construct($array) {
if (is_array($array)) $this->var = $array;
}
public function rewind() {
echo "倒回第一個元素\n";
reset($this->var);
}
public function current() {
$var = current($this->var);
echo "當前元素: $var\n";
return $var;
}
public function key() {
$var = key($this->var);
echo "當前元素的鍵: $var\n";
return $var;
}
public function next() {
$var = next($this->var);
echo "移向下一個元素: $var\n";
return $var;
}
public function valid() {
$var = $this->current() !== false;
echo "檢查有效性: " . ($var ? '1' : '0') . "\n";
return $var;
}
}
$values = array(1, 2, 3);
$it = new MyIterator($values);
foreach ($it as $k => $v) {
print "此時鍵值對 -- key $k: value $v\n\n";
}
倒回第一個元素 當前元素: 1 檢查有效性: 1 當前元素: 1 當前元素的鍵: 0 此時鍵值對 -- key 0: value 1 移向下一個元素: 2 當前元素: 2 檢查有效性: 1 當前元素: 2 當前元素的鍵: 1 此時鍵值對 -- key 1: value 2 移向下一個元素: 3 當前元素: 3 檢查有效性: 1 當前元素: 3 當前元素的鍵: 2 此時鍵值對 -- key 2: value 3 移向下一個元素: 當前元素: 檢查有效性: 0
示例 2 中 valid() 調用 $this->current() 導致 current() 每輪被調用兩次(一次在 valid,一次在循環體前)。正確寫法:
public function valid() {
return key($this->var) !== null; // 或使用內部標記位
}
介面規定 next() 返回 void,雖然 PHP 不強制,但返回值會被忽略,徒增困惑。
若需支援 count($iterator) 或 $iterator->seek(5),分別實現:
class MyIterator implements Iterator, Countable, SeekableIterator {
// ...
public function count() { return count($this->array); }
public function seek($position) {
if (!isset($this->array[$position])) throw new OutOfBoundsException();
$this->position = $position;
}
}
PHP 5.5+ 可用 yield 關鍵字自動生成 Generator 物件(實現 Iterator):
function myGenerator($array) {
foreach ($array as $key => $value) {
yield $key => $value;
}
}
foreach (myGenerator([1,2,3]) as $k => $v) {
echo "$k => $v\n";
}
| 方案 | 內存佔用 | 適用場景 |
|---|---|---|
| 陣列 + foreach | 全量載入 | 小數據集、需隨機訪問 |
| 自定義 Iterator | 可控(惰性加載) | 大數據集、資料庫游標、API 分頁 |
| Generator (yield) | 極低(單元素) | 流式處理、無限序列、管道操作 |
核心結論:foreach 對 Iterator 的調用順序固定為 rewind → (valid → current → key → next)* → valid(false)。正确实现 valid() 且避免副作用是自定義迭代器穩健運作的關鍵。