生成器 是 PHP 5.5 引入的語法糖,允許以極低內存開銷實現迭代器模式。本文系統講解生成器原理、核心語法、yield from 委托、與 Iterator 對象對比、實際應用場景及性能基準。
傳統 range(0, 1000000) 需在內存中創建包含 100 萬整數的數組,佔用 **>100MB** 內存。生成器逐個產出值,內存佔用 **<1KB**,僅維持迭代狀態。
yield 關鍵字的函數。調用時**不執行函數體**,而是返回一個 Generator 對象(實現 Iterator 接口)。yield:產出值並**暫停**函數執行,保留局部變量、指令指針等完整狀態。下次迭代時從暫停處恢復。Generator 對象:可用於 foreach,提供 current(), key(), next(), valid(), rewind(), send(), throw(), getReturn() 等方法。<?php
// 1. 簡單生成器:實現 xrange()
function xrange($start, $limit, $step = 1) {
if ($start < $limit) {
if ($step <= 0) throw new LogicException('Step must be +ve');
for ($i = $start; $i <= $limit; $i += $step) yield $i;
} else {
if ($step >= 0) throw new LogicException('Step must be -ve');
for ($i = $start; $i >= $limit; $i += $step) yield $i;
}
}
foreach (xrange(1, 9, 2) as $num) echo "$num "; // 1 3 5 7 9
// 2. yield 鍵值對
function inputParser($input) {
foreach (explode("\n", $input) as $line) {
$fields = explode(';', $line);
$id = array_shift($fields);
yield $id => $fields; // 指定鍵名
}
}
$input = "1;PHP;Likes \$\n2;Python;Whitespace";
foreach (inputParser($input) as $id => $fields) {
echo "$id: {$fields[0]} - {$fields[1]}\n";
}
// 3. yield 引用 (PHP 7+)
function &genRef() {
$val = 3;
while ($val > 0) yield $val--;
}
foreach (genRef() as &$n) { echo --$n . ' '; } // 2 1 0 (修改影響生成器內部變量)
將迭代委托給另一個生成器、Traversable 對象或數組,並可獲取子生成器的返回值。
function genA() { yield 1; yield 2; yield from [3, 4]; yield from genB(); yield 5; }
function genB() { yield 6; yield from genC(); }
function genC() { yield 7; return 'genC return'; }
$gen = genA();
foreach ($gen as $v) echo "$v "; // 1 2 3 4 6 7 5
echo "\nReturn: " . $gen->getReturn(); // Return: genC return
send($value) 將值傳入生成器,作為 yield 表達式的返回值;throw() 注入異常。
function logger() {
while (true) {
$msg = (yield); // 接收 send() 傳入的值
echo "[LOG] $msg\n";
}
}
$log = logger();
$log->send('System started');
$log->send('User logged in');
| 維度 | 生成器 | Iterator 類 |
|---|---|---|
| 代碼量 | 極少 (專注業務邏輯) | 大量樣板代碼 (5+ 接口方法) |
| 可讀性 | 線性流程,直觀 | 狀態機分散在多方法中 |
| 內存 | O(1) 狀態 | O(1) 狀態 |
| 方向性 | **僅前向**,不可倒退 | 可實現雙向/隨機訪問 |
| 重用性 | 需重新調用函數或 clone | 可重置 rewind() |
| 適用場景 | 數據流處理、大文件讀取、API 分頁、惰性計算 | 複雜集合、需隨機訪問、雙向迭代 |
function readLines(string $filename): Generator {
$fh = fopen($filename, 'r');
try {
while (($line = fgets($fh)) !== false) {
yield rtrim($line, "\r\n");
}
} finally {
fclose($fh);
}
}
foreach (readLines('huge.log') as $line) {
if (strpos($line, 'ERROR') !== false) processError($line);
}
function fetchAllPages(string $baseUrl): Generator {
$page = 1;
do {
$data = json_decode(file_get_contents("$baseUrl?page=$page"), true);
foreach ($data['items'] as $item) yield $item;
$page++;
} while ($data['has_more']);
}
foreach (fetchAllPages('https://api.example.com/users') as $user) {
// 處理單個用戶,無需一次性加載所有頁
}
function fibonacci(): Generator {
$a = 0; $b = 1;
while (true) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
$fib = fibonacci();
for ($i = 0; $i < 10; $i++) echo $fib->current() . ' ', $fib->next(); // 0 1 1 2 3 5 8 13 21 34
| 方式 | 時間 | 峰值內存 |
|---|---|---|
range(1, 10000000) | ~1.2s | ~800 MB |
生成器 xrange() | ~0.9s | ~2 MB |
| 手寫 Iterator 類 | ~1.0s | ~2 MB |
生成器**更快且內存極低**,是大數據集處理的首選。
return $val; 會編譯錯誤。PHP 7.1+ 允許 return 並通過 getReturn() 獲取。yield (PHP 5):$x = yield 1; 需寫為 $x = (yield 1);。PHP 7+ 無限制。finally 釋放資源:文件句柄、DB 連接、鎖必須在 finally 中關閉,確保異常/提前 break 也能釋放。filter(map(readLines($f), 'trim'), fn($l)=>strlen($l)>10) 鏈式處理,內存恆定。function gen(): Generator { ... } 提升 IDE 支持與靜態分析。PHP 生成器以極簡語法實現了惰性求值、恆定內存迭代、協程式雙向通信,是處理大數據流、構建高性能數據管道的利器。掌握 yield、yield from、send()、Generator 對象方法,將顯著提升代碼質量與系統吞吐能力。