Generators
PHP 5.5 was the first version of PHP with support for generator
functions, or generators for short. The xrange()
function shown in the example below is the “Hello world” example for
generators:
function xrange(int $start, int $end, int $step = 1) {
for ($i = $start; $i <= $end; $i += $step) {
yield $i;
}
}
foreach (xrange(1, 1000000) as $number) {
print $number . \PHP_EOL;
}
Unlike the range()
function that is built into PHP,
the xrange()
generator function shown above does not
compute an array with all numbers at once. Instead, it returns an
iterator that will calculate and then emit one number per iteration
step. This approach is more memory-efficient than an iterator, which
needs to keep all values in memory the whole time.
A generator is an interruptible function that returns a sequence
of values (using the yield
keyword) instead of a single
value (using the return
keyword). Two things happen
when the yield
statement of a generator function is
executed: the argument of the yield
statement is
yielded, and the execution of the generator function is suspended.
The execution of the generator function is resumed when the next
value is requested.
In PHP, invoking a generator function creates an object (of the
built-in Generator
class) that implements the
Iterator
interface and can, therefore, be used with
foreach
. Generators can be thought of as a convenient
way to implement iterators. Using them does not require the manual
implementation of the five methods of the built-in
Iterator
interface (current()
,
next()
, key()
, valid()
,
rewind()
).
Generator Return Expressions
Since their execution can be suspended and then later resumed,
generator functions can be used to implement cooperative
multitasking in the form of so-called coroutines. Before PHP 7,
however, such a coroutine was able to process concurrent tasks but
had no standard way to access the results of those computations
because a generator function could only yield
a
sequence of values but not return
a result.
PHP 7 allows the usage of return
statements in
generator functions:
function g()
{
yield 1;
yield 2;
return true;
}
$g = g();
foreach ($g as $element) {
// ...
}
var_dump($g->getReturn());
Executing the code shown above will print the output shown below:
bool(true)
In the example shown above we use the getReturn()
method on the generator object to retrieve the return value of a
generator.
Generator Delegation
PHP 7 introduces the yield from <expression>
syntax that allows the implementation of generators that delegate
operations to Traversable
objects and arrays. It is now
possible to compose yield
statements from smaller
conceptual units.
function delegating_generator()
{
yield from subgenerator();
}
function subgenerator()
{
yield 'value';
}
foreach (delegating_generator() as $value) {
print $value . "\n";
}
Executing the code shown above will print the following:
value
A delegating generator is a generator that uses the new
yield from <expression>
syntax. A
subgenerator is a generator that is used in the
<expression>
part of a delegating generator’s
yield from <expression>
statement.
yield
in Expression
Context
We already discussed the concept of operator precedence in the
“Uniform
Variable Syntax” chapter. This comes into play once again, as
the yield
language construct is now a right-associative
operator with precedence between the print
and
=>
operators:
Expression | PHP 5 interpretation | PHP 7 interpretation |
---|---|---|
yield -1 |
(yield) - 1 |
yield (-1) |
yield $foo or die |
yield ($foo or die) |
(yield $foo) or die |
This change was implemented to eliminate the need to use
parentheses when using yield
in an expression
context.