This tutorial covers the following topics:
- Generator function as an iterator
- Yielding with keys
- Reference yield
- Return a value
- Generator delegation:
yield from
- Generator class
- How to generate sequential unique id with generator function
Any function containing yield
is a generator function. A generator is used to generate a series of values that returned with yield
statement. The yield
statement saves the state of the generator function, allowing it to continue from where it left off when it is called again:
<?php function getUniqueId() { $i = 1; while (true) { yield $i; $i++; } } $getId = getUniqueId(); echo $getId->current(); //Prints: 1 $getId->next(); echo $getId->current(); //Prints: 2 $getId->next(); echo $getId->current(); //Prints: 3
Generator function as an iterator
We can create iterator objects using generators to save processing time and memory without needing to calculate the entire data set (such as a big array). A generator function can be used with foreach
loop. The loop continues until the generator has no more values to yield:
<?php function Generator() { yield 'a'; yield 'b'; yield 'c'; yield 'd'; } $itr = Generator(); foreach ($itr as $val){ echo "$val\n"; }
The above example will output:
a b c d
Iterating with keys:
<?php function Generator() { yield 'a'; yield 'b'; yield 'c'; yield 'd'; } $itr = Generator(); foreach ($itr as $key => $val){ echo "$key. $val\n"; }
The above example will output:
0. a 1. b 2. c 3. d
Yielding with keys
It is possible to yield key-value pairs similar to associative arrays:
<?php function Generator() { yield 'key1' => 'val 1'; yield 'key2' => 'val 2'; yield 'key3' => 'val 3'; } $gen = Generator(); foreach ($gen as $k => $v) { echo "$k => $v\n"; }
The above example will output:
key1 => val 1 key2 => val 2 key3 => val 3
Reference yield
Generator functions are able to yield values by reference as well as by value. To yield
a reference from a generator, use the reference operator &
in both the function declaration and when assigning the returned value to a variable:
<?php function &Generator() { $val = 1; yield $val; yield $val; yield $val; } $gen = Generator(); foreach ($gen as &$v) { echo "$v\n"; $v++; }
The above example will output:
1 2 3
Return a value
The getReturn()
returns the generator’s return value once it has finished executing. Prior to PHP 7, a generator cannot return a value. An empty return statement can be used and this will terminate the generator. PHP 7 made it possible for generators to return expressions:
<?php function Generator() { yield 1; yield 'a'; return true; } $itr = Generator(); var_dump ( $itr->getReturn() );
Calling getReturn()
when the generator has not yet returned will throw an exception, above example will show the following error:
Fatal error: Uncaught Exception: Cannot get return value of a generator that hasn't returned...
Calling the getReturn()
function on the generator, while it is still open (not iterated over), will result in a fatal error.
In the following example, we received the return value after iterating the generator function:
<?php function Generator() { yield 1; yield 'a'; return true; } $itr = Generator(); foreach ($itr as $i){ echo "$i\n"; } //prints: bool(true) var_dump ( $itr->getReturn() );
Generator delegation: yield from
The yield from
syntax (or generator delegation) allows a generator to yield other generators or arrays as sub-generator:
<?php function Gen1(){ yield 'a'; yield 'b'; } function Gen2(){ yield from Gen1(); yield 'c'; yield 'd'; } function Generator(){ yield from Gen2(); yield from ['e','f','g']; } $itr = Generator(); foreach ($itr as $i){ echo "$i. "; }
Above example prints the following output:
a. b. c. d. e. f. g.
Generator class
We can use the following function while working with a generator:
current()
Returns the yielded value.
key()
Returns the key of yielded value.
next()
Resume execution of the generator.
rewind()
Rewind the iterator.
send()
Send a value to generator.
throw()
Throw an exception into the generator.
valid()
Check if the iterator has been closed.
__wakeup()
Serialize callback.
<?php function Generator(){ yield 'a'; yield 'a' => 'apple'; return 'something'; } $g = Generator(); while ( $g->valid() ){ echo $g->key(); echo $g->current(); echo "\n"; $g->next(); } /*Usage without loop*/ /*echo $g->current(); //a. echo $g->key(); // 0 var_dump($g->valid()); // bool(true) $g->next(); echo $g->current(); //apple. echo $g->key(); //b. $g->next(); var_dump($g->valid()); // bool(false) var_dump( $g->current() ); //NULL*/
Generate sequential unique id using generator function
We already created the getUniqueId()
generator (the first example on this page). The getUniqueId
generator only generates numbers in sequence and you can not change the sequence, for example, if you need to start id from a specific number or an alphabet, then the following generate will not be helpful:
<?php function getUniqueId() { $i = 1; while (true) { yield $i; $i++; } } $getId = getUniqueId(); echo $getId->current(); //Prints: 1 $getId->next(); echo $getId->current(); //Prints: 2 ... ... ... $getId->next(); echo $getId->current(); //Prints: 100
To solve this problem we’ll use the send()
function. The send()
function sends the given value to the generator as the result of the current yield expression and resumes execution of the generator. Let’s create the modified generator:
<?php function getUniqueId() { $i = 1; while (true) { $v = (yield $i); if ($v !== null){ $i = $v; } else { $i++; } } }
See the $v = (yield $i)
expression in above example. Generator yield the $i
and assigned a value to $v
if sent by send()
function. If the $v
is not null then it assign to $i
which then yield not next iteration:
$g = getUniqueId(); echo $g->current(); // 1 $g->next(); echo $g->current(); // 2 $g->next();
The above example outputs 1
and 2
. Let’s change the sequence using send()
function:
$g->send(10); //change the sequence echo $g->current(); // 10 $g->next(); echo $g->current(); // 11 $g->next();
We send the value 10
to generator which then assigned to $i so we receive the 10
and 11
. Let’s start the sequence with an alphabet:
$g->send('a'); //change the sequence echo $g->current(); // a $g->next(); echo $g->current(); // b $g->next(); echo $g->current(); // c $g->next();
Now the sequence started with an alphabet character a
. Following is the complete version of the example:
<?php function getUniqueId() { $i = 1; while (true) { $v = (yield $i); if ($v !== null){ $i = $v; } else { $i++; } } } $g = getUniqueId(); echo $g->current(); // 1 $g->next(); echo $g->current(); // 2 $g->next(); $g->send(10); //change the sequence echo $g->current(); // 10 $g->next(); echo $g->current(); // 11 $g->next(); $g->send('a'); //change the sequence echo $g->current(); // a $g->next(); echo $g->current(); // b $g->next(); echo $g->current(); // c $g->next();
For details, visit http://php.net/manual/class.generator.php
PHP OOP Tutorials: