6.9 KiB
yield / Generator
TypePHP 将包含 yield 或 yield from 的函数、方法、匿名函数和箭头函数编译为 Fiber generator。调用时不会立即执行 generator 函数体,而是返回一个全局 \FiberGenerator 对象;第一次迭代、调用 current()、valid()、send() 或 throw() 时才创建并启动 Fiber。
生成的 C++ 函数体运行在 Fiber 中。每次 yield 都通过 Fiber::suspend() 将 key/value 交给迭代驱动方,并在 next()、send() 或 throw() 时恢复原来的 C++ 调用栈。运行时使用 NEW、RUNNING、SUSPENDED、CLOSED_RETURNED 和 CLOSED_FAILED 区分未启动、执行中、挂起、正常返回和异常关闭。
迭代互操作
以下组合已经可以工作:
| 驱动方 | 被迭代对象 |
|---|---|
TypePHP Native foreach |
TypePHP Native generator |
TypePHP Native foreach |
动态 PHP 返回的 Zend Generator |
ZendVM foreach |
TypePHP Native generator 返回的 \FiberGenerator |
TypePHP yield from |
数组、Iterator、IteratorAggregate、Zend Generator、\FiberGenerator |
\FiberGenerator 实现了 Iterator,其接口签名与 PHP 一致:
rewind(): void
next(): void
valid(): bool
current(): mixed
key(): mixed
send(mixed $value): mixed
throw(Throwable $exception): mixed
getReturn(): mixed
当前状态机已经覆盖首次和重复 rewind()、未启动时的 next()/send()/throw()、关闭后的调用、自动整数 key、正常及异常 getReturn()、异常传播和挂起状态析构时执行 finally。
与 PHP 的差异
不是 Zend Generator
TypePHP generator 返回的是全局 \FiberGenerator,不是 PHP 内置的 Generator:
$generator instanceof Iterator; // true
$generator instanceof Generator; // false
因此存在以下差异:
- generator 函数不能声明精确返回类型
Generator。 - 可以使用
Iterator、Traversable、iterable、object、mixed,或包含这些兼容类型的联合类型。 ReflectionGenerator只接受 ZendGenerator,不能用于\FiberGenerator。get_class()、Reflection class 信息和异常栈中的类名与 ZendGenerator不同。- 不保证
var_dump()、调试属性及内部对象布局与 ZendGenerator一致。 \FiberGenerator是 final 运行时内部类型,禁止业务代码直接实例化、继承、clone 或序列化。
不支持引用 Generator
以下 PHP 语法暂不支持:
function &values(): iterable
{
yield $value;
}
foreach (values() as &$value) {
}
TypePHP 不支持:
- generator 函数或方法按引用返回。
- by-reference yield 语义。
- 对 generator 使用按引用
foreach。 - 通过 generator 维持元素引用身份。
current()、send()、throw() 和 getReturn() 都按普通 PHP 值返回;运行时会解除 INDIRECT 和 REFERENCE 包装,不会返回引用容器。
参数限制
TypePHP generator 暂不支持以下参数声明:
- 按引用参数,例如
function values(&$value)。 - 可变参数,例如
function values(...$values)。 - 按引用可变参数,例如
function values(&...$values)。
普通参数、默认值、联合类型参数、对象参数和方法中的 $this 可以使用。参数类型检查及 constructor property promotion 在 generator 对象创建时执行,函数体仍保持延迟执行。
Traversable 边界
yield from 与 TypePHP Native 对象 foreach 使用不同的底层路径。foreach 统一通过 PHPX ForeachIterator 驱动数组、普通对象和 Zend Traversable;yield from 仍由 generator 自己完成委托。
这意味着:
- 用户态
Iterator、IteratorAggregate、ZendGenerator以及扩展提供的内部Traversable均通过类的get_iteratorhandler 迭代。 - 普通对象直接遍历实时 property table,并按当前 TypePHP 类作用域执行 public、protected、private 可见性检查。
- 不读取 key 的
foreach ($iterable as $value)不会调用Iterator::key()。 - TypePHP
yield from会检测IteratorAggregate::getIterator()返回自身或形成对象环,并抛出异常。 - TypePHP Native
foreach将IteratorAggregate的展开与环检测交给 Zend iterator handler,行为与当前 PHP 运行时保持一致。 - 非法
getIterator()返回值的异常类型、消息文本和栈信息可能与 ZendVM 不完全一致。
Fiber 可观察差异
Zend Generator 是 ZendVM 的专用执行对象;TypePHP generator 使用 PHP Fiber 保存完整 C/C++ 栈,因此:
- 运行环境必须提供 PHP Fiber。
- 异常栈中可能出现
Fiber、内部 closure 或 TypePHP runtime frame。 - 文件名、行号和调用栈形状不保证与
ReflectionGenerator或 Zend Generator 完全相同。 - 正常迭代、异常传播和挂起析构的
finally已有回归测试,但复杂对象环、请求关闭、进程退出及析构函数再次抛出异常时的执行顺序仍可能与 Zend Generator 不同。 - Fiber 被强制关闭时使用 Zend 内部 graceful-exit 展开 C++ 栈;该对象不是业务代码可捕获或依赖的公开异常类型。
yield from 差异
数组、普通 Iterator 和 generator 的 key/value 转发、generator return value、send() 和 throw() 委托已经实现,但底层实现不是 Zend yield from opcode:
- 委托通过
rewind()、valid()、key()、current()、next()、send()、throw()和getReturn()方法完成。 - 自定义 Iterator 方法产生的副作用、异常栈和调用次数应避免依赖 Zend Generator 的内部实现细节。
- 非 generator Iterator 的
yield from结果为null;只有 ZendGenerator和\FiberGenerator会读取getReturn()。
性能差异
Fiber generator 不会改变普通数组或普通容器 foreach 的生成代码。只有实际创建并驱动 generator 时才产生额外成本。
每个 yield 目前需要:
- Fiber suspend/resume。
Iterator方法调用。- generator 状态和对象属性读写。
- key/value payload 数组的创建与释放。
yield from场景中的额外委托调用。
因此 TypePHP Fiber generator 通常比 Native C++ 数组 foreach 慢,也可能比 Zend 专用 Generator opcode 慢。高频、短元素迭代应优先使用数组或 Native 容器;generator 更适合延迟计算、流式处理和需要保存完整 Native 调用栈的场景。
不兼容清单
当前不能依赖以下 PHP 行为:
- 返回对象是 Zend
Generator。 instanceof Generator。- 返回类型声明为精确的
Generator。 ReflectionGenerator。- generator 按引用返回、by-reference yield 或按引用 foreach。
- generator 的按引用参数或可变参数。
- 调试输出及内部属性与 Zend Generator 相同。
- 所有内部扩展
Traversable都能通过 Zend iterator handlers 迭代。 - Fiber 关闭、复杂析构和进程退出时与 Zend Generator 完全相同的栈及析构顺序。