生成器是php流式处理的底层机制,核心在于控制权移交与资源生命周期管理;错误使用会导致内存泄漏或迭代失败,正确做法需禁用pdo缓冲、安全处理yield from异常、显式管理文件句柄等。

生成器不是“更高级的数组”,它是 PHP 做流式处理的底层机制——用对了,100 万行 CSV 不会崩;用错了,yield 写得再漂亮,内存照样爆。
生成器函数里为什么不能写 return $data
一旦函数体内出现 yield,它就自动变成生成器函数,返回值强制为 Generator 对象。此时若写 return $data(PHP 7.1 之前直接报错;7.1+ 虽允许,但该值只能通过 getReturn() 获取,且 yield 流程已终止)。
- 常见错误:在循环里写
if ($done) return $summary;,结果foreach什么也不迭代——因为没执行到任何yield - 正确做法:用
yield吐数据;终结逻辑放在最后,或用return仅作终值标记(极少需要) -
var_dump($gen)看不到数据,只显示对象结构;真要看内容,得用iterator_to_array($gen, false),但慎用——它会把整个流拉进内存
读大文件时,fgets() + yield 为什么还可能内存泄漏
表面看是“一行一行读”,但如果文件句柄没关、或 fgets() 遇到超长行(比如日志里嵌了 base64),缓冲区仍可能膨胀。关键不在 yield,而在资源生命周期管理。
- 必须确保
fclose($handle)在生成器结束时被执行;推荐用try/finally包裹文件操作 - 避免
while (!feof($handle))的经典陷阱:EOF 判定滞后,最后一行可能重复 yield;改用while (($line = fgets($handle)) !== false) - 对超长行敏感场景,改用
fgets($handle, 8192)显式限制单次读取长度
PDO 查询百万行,只写 yield $row 还不够
数据库驱动默认启用缓冲查询(buffered query),意味着即使你用生成器,PDO 仍会把整张结果集先捞进 PHP 内存,yield 只是“从内存数组里假装流式吐”。真正起作用的是禁用缓冲 + 游标式消费。
- 必须设置:
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false) - 查询语句避免
LIMIT 100000, 100:MySQL 仍要扫描前 10 万行;改用基于主键的分段,如WHERE id > ? ORDER BY id LIMIT 100 - fetch 时用
$stmt->fetch(PDO::FETCH_ASSOC),别用fetchAll()或未指定 fetch mode 的fetch() - 生成器内部别调远程 API 或写 DB——每次
yield都该是轻量操作,否则“流式”只是假象
yield from 委托子生成器时,异常会直接中断整个链
yield from 不是语法糖,它把子生成器完全接入当前迭代器协议:包括 throw()、return、关闭通知。但子生成器抛出的异常不会被自动捕获,而是直接冒泡到消费者代码。
- 典型翻车点:合并多个日志文件,其中一个路径权限不足,
new \SplFileObject($path)抛RuntimeException,整个foreach立刻终止 - 安全写法:在
yield from外层加try/catch,捕获后可记录、跳过,甚至 yield 替代值 -
yield from someFunction()合法,但someFunction()必须返回Generator、array或Traversable对象;返回普通数组会直接展开,不走子生成器逻辑
生成器真正的复杂点不在语法,而在于它把“控制权移交”显性化了:你写的不是一段顺序代码,而是一组暂停/恢复点。一旦涉及 I/O、异常、资源释放,就必须按协程思维去校验每一步的边界——否则,yield 写得再多,也只是一层好看的壳。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











