yield是php生成器的核心机制,使函数可暂停恢复、内存友好迭代;与return立即终止不同,yield返回generator对象,逐次产出值,适用于大文件、数据库等流式处理场景。

yield 不是语法糖,它是 PHP 生成器(Generator)的核心执行机制;不用 yield 就没有真正的内存友好型迭代——尤其处理大数组、文件流或数据库游标时,return 会一次性加载全部数据到内存,而 yield 让函数变成可暂停、可恢复的迭代器。
yield 和 return 的本质区别在哪
普通函数遇到 return 立即终止并返回值;含 yield 的函数(生成器函数)首次调用时并不执行函数体,而是返回一个 Generator 对象。每次调用 Generator->next() 或用 foreach 迭代时,才执行到下一个 yield 行,挂起状态并产出值。
常见错误现象:Fatal error: Uncaught Exception: Cannot use "yield" outside a function —— yield 只能在函数体内使用,且该函数必须有 yield 语句(否则 PHP 不识别为生成器)。
-
yield后面可以跟值(yield $value),也可以不跟(yield),此时产出NULL,键为自动递增整数 - 若需指定键名,写成
yield $key => $value,比如遍历关联数组时保留原始键 - 生成器函数不能同时使用
return和yield(PHP 7.1+ 允许return在最后用于返回终值,但该值需通过Generator->getReturn()获取,foreach不会遍历它)
如何用 yield 处理大文件逐行读取
直接 file_get_contents() 读取几个 GB 的日志文件会 OOM;用 fopen() + fgets() 手动循环虽可行,但逻辑分散、难复用。生成器把“打开→逐行→关闭”封装成可迭代接口,调用方只管 foreach。
function readLargeFile($path) {
$handle = fopen($path, 'r');
if (!$handle) throw new RuntimeException("Cannot open $path");
try {
while (($line = fgets($handle)) !== false) {
yield rtrim($line, "\n\r");
}
} finally {
fclose($handle);
}
}
使用场景:日志分析、CSV 解析、ETL 流水线预处理。注意 rtrim() 是必须的——fgets() 保留换行符,不清理会导致每行末尾多一个空行。
- 不要在生成器里做耗时操作(如远程请求、sleep),否则每次
yield都会阻塞整个迭代流程 - 资源(如文件句柄、PDOStatement)必须在
finally块中释放,否则生成器被销毁前可能未关闭句柄 - 生成器对象本身不可序列化,也不能被
json_encode()直接处理
yield from 怎么简化嵌套生成器调用
当你要合并多个生成器(比如多个日志文件、多个 API 分页结果),传统做法是嵌套 foreach 并 yield 每个子项,代码冗长易错。yield from 直接委托迭代,把子生成器的产出“透传”给外层调用者。
function logsFromDirectories(array $paths) {
foreach ($paths as $path) {
if (is_dir($path)) {
yield from readDirectoryLogs($path); // 直接展开另一个生成器
} else {
yield from readLargeFile($path);
}
}
}
参数差异:yield from 后只能接可迭代对象(Generator、Traversable、数组),不能接标量或 null;否则报 Fatal error: Uncaught TypeError: Cannot use yield from on string。
-
yield from会原样转发子生成器的键值对,包括自定义键名 - 子生成器抛出的异常会冒泡到外层,无需额外 try/catch 包裹
yield from - PHP 7.0+ 支持,低于此版本只能手动循环 +
yield
生成器调试和性能要注意什么
生成器不是银弹。它节省内存,但可能增加 CPU 开销(每次 resume 需上下文切换),且无法随机访问(不支持 $gen[100] 或 count($gen))。
容易踩的坑:foreach ($gen as $item) { ... } 跑完后,该 Generator 对象就 exhausted(耗尽)了,再次 foreach 无输出,也不会报错——这点和数组完全不同。
- 调试时可用
iterator_to_array($gen, false)强制转数组查看内容,但仅限小数据,否则失去生成器意义 - 想重用生成器?只能重新调用原函数(
readLargeFile($path)),不能 clone 或 reset - 生成器函数内不能使用
$this(除非是 static 方法),因为生成器对象与原类实例无绑定关系 - 协程库(如 ReactPHP、Amp)底层大量依赖生成器,但它们对
yield的用法已超出 PHP 原生语义(比如yield wait($promise)),那是另一层抽象了
真正关键的是:只要涉及“数据流”而非“数据集”,就该本能地想到 yield;但别为了用而用——小数组、简单循环,return array_map(...) 更直白,也更不容易出错。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











