php生成器是用yield实现内存友好的迭代逻辑,返回generator对象(实现iterator接口),每次遍历才执行到下一个yield;不可直接echo、count或重复遍历,大文件需配合流式i/o。

PHP生成器(Generator)不是“学怎么写代码生成器”,而是学怎么用 yield 写内存友好的迭代逻辑。它和 AST、Zend VM 生成器完全无关,别被“生成器”这个词带偏。
yield 函数不是普通函数,是状态机
调用一个含 yield 的函数,不执行函数体,而是立即返回一个 Generator 对象——它实现了 Iterator 接口,但内部保存着函数的全部执行上下文(局部变量、当前行号、堆栈帧)。每次 foreach 或 ->next() 触发时,才从上次 yield 暂停处继续执行,直到下一个 yield 或函数结束。
常见错误现象:
- 直接
echo numberGenerator()→ 报错Object of class Generator could not be converted to string - 试图对生成器做
count()、array_map()、json_encode()→ 失败,因为生成器不是数组 - 重复遍历同一个生成器对象 → 第二次
foreach什么也不输出(已耗尽)
正确做法:
- 只用在
foreach、for+->valid()、或手动调用->current()/->next() - 需要多次使用时,重新调用生成器函数(
numberGenerator()是新对象) - 若真要转数组,显式用
iterator_to_array($gen, false),但会失去内存优势
处理大文件时,yield 必须配合流式 I/O
生成器本身不省内存,省的是“把所有数据一次性加载进内存”。关键在你怎么读数据。
错误示范(仍会爆内存):
function badRead($file) {
$lines = file($file); // 全部读入内存!
foreach ($lines as $line) {
yield trim($line);
}
}
正确做法(逐行 fopen + fgets):
function readLines($file) {
$fp = fopen($file, 'r');
if (!$fp) return;
while (($line = fgets($fp)) !== false) {
yield rtrim($line, "\r\n");
}
fclose($fp);
}
注意事项:
-
fgets()比file_get_contents()+explode()安全得多,尤其对超长行 - 必须手动
fclose(),否则文件句柄泄漏 - Windows 换行符是
\r\n,rtrim($line, "\r\n")比trim()更精准
yield 键值对比只 yield 值更易踩坑
支持 yield $key => $value 很方便,但要注意键类型限制:
- 键必须是
int或string,不能是null、array、object - 如果键重复,后一次会覆盖前一次(和数组行为一致)
- 若省略键(
yield $value),PHP 自动用整数递增索引,从 0 开始
典型误用场景:
// ❌ 键是对象,运行时报致命错误
foreach ($users as $user) {
yield $user => $user->name; // $user 是 object
}
// ✅ 改用 ID 作键
foreach ($users as $user) {
yield $user->id => $user->name;
}
send() 和 return 语句容易被忽略的边界行为
Generator 支持双向通信:->send($value) 会把值传给上一个 yield 表达式的左边(即 $received = yield $something 中的 $received);return 语句会在生成器结束时提供最终返回值(通过 ->getReturn() 获取)。
但这两个特性有硬性前提:
-
->send()只能在生成器暂停时调用,首次必须先->next()或foreach启动,否则报Cannot send to unstarted generator -
return值仅在生成器彻底结束(没有更多yield)后才可取,提前调->getReturn()返回NULL - 一旦生成器结束,再调
->send()或->next()会触发Exception: Cannot resume an already closed generator
所以,除非你真在写协程调度器或状态驱动解析器,否则 send() 和 return 不建议新手碰——95% 的业务场景只需单向 yield。
真正难的不是写 yield,而是判断“这里该不该用生成器”。比如数据库查询,PDO 默认是缓冲模式,fetch() 仍会把整张结果集加载进内存;必须设 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false 并配合 yield 才有效。这种细节,文档不写,报错不提,只能靠实测和翻源码确认。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











