7.8 KiB
yield / Generator
TypePHP compiles functions, methods, anonymous functions, and arrow functions that contain yield or yield from into Fiber generators. Calling them does not immediately execute the generator function body; instead it returns a global \FiberGenerator object. The Fiber is created and started only on the first iteration, or when current(), valid(), send(), or throw() is called.
The generated C++ function body runs inside the Fiber. Each yield hands its key/value to the iteration driver via Fiber::suspend(), and resumes the original C++ call stack on next(), send(), or throw(). The runtime distinguishes not-started, running, suspended, normally-returned, and exceptionally-closed states via NEW, RUNNING, SUSPENDED, CLOSED_RETURNED, and CLOSED_FAILED.
Iteration interop
The following combinations already work:
| Driver | Iterated object |
|---|---|
TypePHP Native foreach |
TypePHP Native generator |
TypePHP Native foreach |
Zend Generator returned by dynamic PHP |
ZendVM foreach |
\FiberGenerator returned by a TypePHP Native generator |
TypePHP yield from |
arrays, Iterator, IteratorAggregate, Zend Generator, \FiberGenerator |
\FiberGenerator implements Iterator with an interface signature consistent with PHP:
rewind(): void
next(): void
valid(): bool
current(): mixed
key(): mixed
send(mixed $value): mixed
throw(Throwable $exception): mixed
getReturn(): mixed
The current state machine already covers first and repeated rewind(), next()/send()/throw() when not started, calls after closure, automatic integer keys, normal and exceptional getReturn(), exception propagation, and executing finally when destructing in a suspended state.
Differences from PHP
Not a Zend Generator
TypePHP generators return the global \FiberGenerator, not PHP's built-in Generator:
$generator instanceof Iterator; // true
$generator instanceof Generator; // false
Therefore the following differences exist:
- A generator function cannot declare the precise return type
Generator. Iterator,Traversable,iterable,object,mixed, or union types containing these compatible types can be used.ReflectionGeneratoronly accepts ZendGeneratorand cannot be used with\FiberGenerator.get_class(), Reflection class info, and the class name in exception stacks differ from ZendGenerator.- No guarantee that
var_dump(), debug properties, or the internal object layout match ZendGenerator. \FiberGeneratoris a final runtime-internal type; business code is forbidden from directly instantiating, inheriting, cloning, or serializing it.
By-reference Generators not supported
The following PHP syntax is not yet supported:
function &values(): iterable
{
yield $value;
}
foreach (values() as &$value) {
}
TypePHP does not support:
- generator functions or methods returning by reference.
- by-reference yield semantics.
- by-reference
foreachover a generator. - maintaining element reference identity through a generator.
current(), send(), throw(), and getReturn() all return ordinary PHP values; the runtime unwraps INDIRECT and REFERENCE wrappers and does not return reference containers.
Parameter limitations
TypePHP generators do not yet support the following parameter declarations:
- by-reference parameters, e.g.
function values(&$value). - variadic parameters, e.g.
function values(...$values). - by-reference variadic parameters, e.g.
function values(&...$values).
Ordinary parameters, defaults, union-typed parameters, object parameters, and $this in methods can be used. Parameter type checks and constructor property promotion execute when the generator object is created, while the function body remains lazily executed.
Traversable boundary
yield from and TypePHP Native object foreach use different underlying paths. foreach uniformly drives arrays, ordinary objects, and Zend Traversable through PHPX ForeachIterator; yield from still performs delegation within the generator itself.
This means:
- userland
Iterator,IteratorAggregate, ZendGenerator, and internalTraversableprovided by extensions are all iterated through the class'sget_iteratorhandler. - ordinary objects directly traverse the live property table, performing public, protected, and private visibility checks under the current TypePHP class scope.
foreach ($iterable as $value)that does not read the key does not callIterator::key().- TypePHP
yield fromdetectsIteratorAggregate::getIterator()returning itself or forming an object cycle, and throws an exception. - TypePHP Native
foreachdelegatesIteratorAggregateunwrapping and cycle detection to the Zend iterator handler, keeping behavior consistent with the current PHP runtime. - the exception type, message text, and stack info for an invalid
getIterator()return value may not fully match ZendVM.
Fiber observable differences
Zend Generator is a dedicated ZendVM execution object; TypePHP generators use PHP Fibers to save the full C/C++ stack, so:
- the runtime environment must provide PHP Fibers.
Fiber, internal closures, or TypePHP runtime frames may appear in exception stacks.- file names, line numbers, and call-stack shapes are not guaranteed to exactly match
ReflectionGeneratoror Zend Generator. - normal iteration, exception propagation, and
finallyduring suspended destructors already have regression tests, but execution order for complex object cycles, request shutdown, process exit, and destructors that throw again may still differ from Zend Generator. - when a Fiber is forcibly closed, Zend's internal graceful-exit is used to unwind the C++ stack; this object is not a public exception type that business code can catch or rely on.
yield from differences
Key/value forwarding for arrays, ordinary Iterators, and generators, generator return values, and send()/throw() delegation are implemented, but the underlying implementation is not the Zend yield from opcode:
- delegation is done through the
rewind(),valid(),key(),current(),next(),send(),throw(), andgetReturn()methods. - side effects, exception stacks, and call counts produced by custom Iterator methods should avoid depending on Zend Generator's internal implementation details.
yield fromover a non-generator Iterator yieldsnull; only ZendGeneratorand\FiberGeneratorreadgetReturn().
Performance differences
Fiber generators do not change the generated code for ordinary array or ordinary container foreach. Extra cost arises only when a generator is actually created and driven.
Each yield currently requires:
- Fiber suspend/resume.
Iteratormethod calls.- generator state and object property reads/writes.
- creation and release of the key/value payload array.
- additional delegation calls in the
yield fromscenario.
Therefore TypePHP Fiber generators are usually slower than Native C++ array foreach, and may also be slower than Zend's dedicated Generator opcode. High-frequency, short-element iteration should prefer arrays or Native containers; generators are more suited to lazy computation, streaming, and scenarios that need to preserve the full Native call stack.
Incompatibility checklist
The following PHP behaviors currently cannot be relied upon:
- the returned object being a Zend
Generator. instanceof Generator.- declaring the precise
Generatorreturn type. ReflectionGenerator.- by-reference generator returns, by-reference yield, or by-reference foreach.
- by-reference or variadic generator parameters.
- debug output and internal properties identical to Zend Generator.
- all internal extension
Traversablebeing iterable through Zend iterator handlers. - Fiber closure, complex destruction, and process exit with exactly the same stack and destruction order as Zend Generator.