PHP generators allow functions to behave like iterators by yielding values one at a time rather than building and returning an array all at once. Generators are automatically created when the yield keyword is used in a function. They implement the Iterator interface and can be used in foreach loops. Data and control flow can be passed into generators using the send() method to influence their behavior.
3. PHP Generators
Wikipedia defines a Generator as:
A generator is very similar to a function that returns an array, in that
a generator has parameters, can be called, and generates a
sequence of values. However, instead of building an array containing
all the values and returning them all at once, a generator yields the
values one at a time, which requires less memory and allows the
caller to get started processing the first few values immediately. In
short, a generator looks like a function but behaves like an iterator.
4. PHP Generators
• Introduced in PHP 5.5
• Iterable (Traversable) Objects
• Can return a series of values, one at a time
• Maintain state between iterations
• Can accept values when sent to the generator
• Similar to enumerators in Ruby, or Sequence Expressions in F#
5. PHP Generators
• Don’t
• Add anything to PHP that couldn’t be done before
• Do
• Allow you to perform iterative operations without an array to iterate
• Potentially reduce memory use
• Potentially faster than iterating over an array
• Can be type-hinted in function/method definitions
• Potentially cleaner and shorter code
• Add semantics to your code
6. PHP Generators
• Automatically created when PHP identifies a function or method
containing the “yield” keyword
function myGeneration() {
yield 1;
}
$generator = myGeneration();
var_dump($generator);
object(Generator)#1 (0) { }
7. PHP Generators
• Implemented as an Object
final class Generator implements Iterator {
mixed current( void );
mixed key( void );
void next( void );
void rewind( void );
mixed send( mixed $value );
mixed throw( Exception $exception );
bool valid( void );
public void __wakeup ( void )
}
• Can’t be extended
10. PHP Generators
foreach (range(0,65535) as $i => $value) {
echo $i , ' -> ' , $value, PHP_EOL;
}
function xrange($lower, $upper) {
for ($i = $lower; $i <= $upper; ++$i) {
yield $i;
}
}
foreach (xrange(0, 65535) as $i => $value) {
echo $i , ' -> ' , $value, PHP_EOL;
}
for($i = 0; $i <= 65535; ++$i) {
echo $i , ' -> ' , $value, PHP_EOL;
}
Time: 0.0183 s
Current Memory: 123.44 k
Peak Memory: 5500.11 k
Time: 0.0135 s
Current Memory: 124.33 k
Peak Memory: 126.84 k
Time: 0.0042 s
Current Memory: 122.92 k
Peak Memory: 124.49 k
11. PHP Generators
function xlColumnRange($lower, $upper) {
++$upper;
for ($i = $lower; $i != $upper; ++$i) {
yield $i;
}
}
foreach (xlColumnRange('A', 'CQ') as $i => $value) {
printf('%3d -> %2s', $i, $value);
echo (($i > 0) && ($i+1 % 5 == 0)) ?
PHP_EOL :
"t";
}
0 -> A 1 -> B 2 -> C 3 -> D 4 -> E
5 -> F 6 -> G 7 -> H 8 -> I 9 -> J
10 -> K 11 -> L 12 -> M 13 -> N 14 -> O
15 -> P 16 -> Q 17 -> R 18 -> S 19 -> T
20 -> U 21 -> V 22 -> W 23 -> X 24 -> Y
25 -> Z 26 -> AA 27 -> AB 28 -> AC 29 -> AD
30 -> AE 31 -> AF 32 -> AG 33 -> AH 34 -> AI
35 -> AJ 36 -> AK 37 -> AL 38 -> AM 39 -> AN
40 -> AO 41 -> AP 42 -> AQ 43 -> AR 44 -> AS
45 -> AT 46 -> AU 47 -> AV 48 -> AW 49 -> AX
50 -> AY 51 -> AZ 52 -> BA 53 -> BB 54 -> BC
55 -> BD 56 -> BE 57 -> BF 58 -> BG 59 -> BH
60 -> BI 61 -> BJ 62 -> BK 63 -> BL 64 -> BM
65 -> BN 66 -> BO 67 -> BP 68 -> BQ 69 -> BR
70 -> BS 71 -> BT 72 -> BU 73 -> BV 74 -> BW
75 -> BX 76 -> BY 77 -> BZ 78 -> CA 79 -> CB
80 -> CC 81 -> CD 82 -> CE 83 -> CF 84 -> CG
85 -> CH 86 -> CI 87 -> CJ 88 -> CK 89 -> CL
90 -> CM 91 -> CN 92 -> CO 93 -> CP 94 -> CQ
12. PHP Generators
$isEven = function ($value) {
return !($value & 1);
};
$isOdd = function ($value) {
return $value & 1;
};
function xFilter(callable $callback, array $args=array()) {
foreach ($args as $arg)
if (call_user_func($callback, $arg))
yield $arg;
}
13. PHP Generators
$data = range(1,10);
echo 'xFilter for Odd Numbers', PHP_EOL;
foreach (xFilter($isOdd, $data) as $i)
echo('num is: '.$i.PHP_EOL);
echo 'xFilter for Even Numbers', PHP_EOL;
foreach (xFilter($isEven, $data) as $i)
echo('num is: '.$i.PHP_EOL);
xFilter for Odd Numbers
num is: 1
num is: 3
num is: 5
num is: 7
num is: 9
xFilter for Even Numbers
num is: 2
num is: 4
num is: 6
num is: 8
num is: 10
14. PHP Generators
• Can return both a value and a “pseudo” key
• By default
• The key is an integer value
• Starting with 0 for the first iteration
• Incrementing by 1 each iteration
• Accessed from foreach() as:
foreach(generator() as $key => $value) {}
• or using
$key = $generatorObject->key();
15. PHP Generators
• Default key behaviour can be changed
• Syntax is:
yield $key => $value;
• Unlike array keys:
• “Pseudo” keys can be any PHP datatype
• “Pseudo” key values can be duplicated
18. PHP Generators
function duplicateKeys($string) {
$string = strtolower($string);
$length = strlen($string);
for ($i = 0; $i < $length; ++$i) {
yield strtoupper($string[$i]) => $string[$i];
}
}
foreach (duplicateKeys('badass') as $key => $value) {
echo $key , ' -> ' , $value, PHP_EOL;
}
B -> b
A -> a
D -> d
A -> a
S -> s
S -> s
20. PHP Generators
• It is possible to access generated values “by reference”
• The generator must be declared “by reference”
• The yielded value must be a variable, and cannot be an expression
22. PHP Generators
• Data can be passed to the generator
• Sometimes called a “Coroutine” when used in this way
• Not strictly accurate, they are more strictly a “Semicoroutine”
• They can form the basis for a “Coroutine” with the addition of a top-level
dispatcher routine
• Syntax is:
$value = yield;
• Calling script uses the “send()” method:
$generatorObject->send($value);
36. PHP Generators
• We can also throw an Exception into a Generator
• A Try/Catch block should be defined in the Generator
• We use the Generator’s “throw()” method from the calling code
$generatorObject->throw(new Exception(‘xyz’));
• Useful for terminating a Generator loop if we don’t want to code a
send() in every iteration
37. PHP Generators
function filteredNumbers(Callable $filter) {
$i = 1;
try {
do {
if (call_user_func($filter, $i)) {
yield $i;
}
} while ($i++ <= PHP_INT_MAX);
} catch (Exception $e) {
echo $e->getMessage(), PHP_EOL;
}
}
39. PHP Generators
• You can’t pass Generators as arguments to array functions
So you can’t call array_map() or array_reduce()with a
Generator instead of an array argument
• But by passing a Generator as an argument to another
Generator, we can chain Generators
Allowing us to simulate array_map() or array_reduce()
49. Who am I?
Mark Baker
Design and Development Manager
InnovEd (Innovative Solutions for Education) Learning Ltd
Coordinator and Developer of:
Open Source PHPOffice library
PHPExcel, PHPWord, PHPPowerPoint, PHPProject, PHPVisio
Minor contributor to PHP core
Other small open source libraries available on github
@Mark_Baker
https://github.com/MarkBaker
http://uk.linkedin.com/pub/mark-baker/b/572/171
http://markbakeruk.net