Slides from presentation on PHP 5.5 Generators given to PHP Brighton group on 16th December 2013, and subsequently to the PHP Cambridge group on 22nd September 2014
2. 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
3. PHP 5.5 – Generators
• Introduced in PHP 5.5
• Iterable Objects
• Can return a series of values, one at a time
• Can accept values when sent to the generator
• Maintain state between iterations
• Similar to enumerators in Ruby
4. PHP 5.5 – 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
Potentially cleaner and shorter code
5. PHP 5.5 – Generators
• Automatically created when PHP identifies a function or method
containing the “yield” keyword
function myGenerator() {
yield 1;
}
$generator = myGenerator();
var_dump($generator);
object(Generator)#1 (0) { }
6. PHP 5.5 – Generators
• Implemented as an Object
final class Generator implements Iterator {
void rewind();
bool valid();
mixed current();
mixed key();
void next();
mixed send(mixed $value);
}
• Can’t be extended
11. PHP 5.5 – Generators
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 ->
function xlColumnRange($lower, $upper) {
++$upper;
for ($i = $lower; $i != $upper; ++$i) {
yield $i;
}
}
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 5.5 – Generators
$isEven = function ($value) {
return !($value & 1);
};
xFilter for Odd Numbers
num is: 1
num is: 3
$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;
}
$data = range(1,10);
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
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);
num is: 10
13. PHP 5.5 – Generators
// Endpoint is Brighton
$endPoint = new DistanceCalculator(
50.8429,
0.1313
);
function retrieveCityData(DistanceCalculator $endPoint) {
$file = new SplFileObject("cities.csv");
$file->setFlags(
SplFileObject::DROP_NEW_LINE |
SplFileObject::SKIP_EMPTY
);
while (!$file->eof()) {
$cityData = $file->fgetcsv();
if ($cityData !== NULL) {
$city = new StdClass;
$city->name = $cityData[0];
$city->latitude = $cityData[1];
$city->longitude = $cityData[2];
$city->distance = $endPoint->calculateDistance($city);
yield $city;
}
}
}
foreach (retrieveCityData($endPoint) as $city) {
echo $city->name, ' is ', sprintf('%.2f', $city->distance), ' miles from Brighton', PHP_EOL;
}
14. PHP 5.5 – 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 5.5 – 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
20. PHP 5.5 – Generators
// Endpoint is Brighton
$endPoint = new DistanceCalculator(
50.8429,
0.1313
);
function retrieveCityData(DistanceCalculator $endPoint) {
$file = new SplFileObject("cities.csv");
$file->setFlags(
SplFileObject::DROP_NEW_LINE |
SplFileObject::SKIP_EMPTY
);
while (!$file->eof()) {
$cityData = $file->fgetcsv();
if ($cityData !== NULL) {
$city = new StdClass;
$city->name = $cityData[0];
$city->latitude = $cityData[1];
$city->longitude = $cityData[2];
yield $city => $endPoint->calculateDistance($city);
}
}
}
foreach (retrieveCityData($endPoint) as $city => $distance) {
echo $city->name, ' is ', sprintf('%.2f', $distance), ' miles from Brighton', PHP_EOL;
}
21. PHP 5.5 – Generators
• Data can be passed to the generator
• Called a “coroutine” when used in this way
• Syntax is:
$value = yield;
• Calling script uses the “send” method:
$generatorObject->send($value);
22. PHP 5.5 – Generators
$data = array(
'London',
'New York',
'Paris',
'Munich',
);
function generatorSend() {
while (true) {
$cityName = yield;
echo $cityName, PHP_EOL;
}
}
$generatorObject = generatorSend();
foreach($data as $value) {
$generatorObject->send($value);
}
London
New York
Paris
Munich
29. PHP 5.5 – Generators
Matthew has 3,8,11
Mark has 6,7,11
Luke has 3,9,12
John has 3,7,12
$players = array();
foreach($playerNames as $playerName) {
shuffle($numbers);
$card = array_slice($numbers,0,$cardSize);
$player = new StdClass();
$player->name = $playerName;
$player->checknumbers = bingo($card);
$players[] = $player;
}
Brian has 1,7,9
Caller Draws 9
Matthew: Checking card - 3 numbers left
Mark: Checking card - 3 numbers left
Luke: Checking card - Match - 2 numbers left
John: Checking card - 3 numbers left
Brian: Checking card - Match - 2 numbers left
Caller Draws 1
Matthew: Checking card - 3 numbers left
Mark: Checking card - 3 numbers left
$houseCalled = false;
while (!$houseCalled && !empty($numbers)) {
$number = array_pop($numbers);
echo PHP_EOL, 'Caller Draws ', $number, PHP_EOL;
foreach($players as $player) {
echo $player->name, ': ';
$player->checknumbers->send($number);
$houseCalled = $player->checknumbers->current();
if ($houseCalled) {
echo PHP_EOL, PHP_EOL, $player->name, ' WINS';
break;
}
$player->checknumbers->next();
echo PHP_EOL;
}
}
Luke: Checking card - 2 numbers left
John: Checking card - 3 numbers left
Brian: Checking card - Match - 1 number left
Caller Draws 4
Matthew: Checking card - 3 numbers left
Mark: Checking card - 3 numbers left
Luke: Checking card - 2 numbers left
John: Checking card - 3 numbers left
Brian: Checking card - 1 number left
Caller Draws 7
Matthew: Checking card - 3 numbers left
Mark: Checking card - Match - 2 numbers left
Luke: Checking card - 2 numbers left
John: Checking card - Match - 2 numbers left
Brian: Checking card - Match *** HOUSE ***
Brian WINS
30. PHP 5.5 – Generators
public function search(QuadTreeBoundingBox $boundary) {
$results = array();
if ($this->boundingBox->encompasses($boundary) ||
$this->boundingBox->intersects($boundary)) {
// Test each point that falls within the current QuadTree node
foreach($this->points as $point) {
// Test each point stored in this QuadTree node in turn,
//
passing back to the caller if it falls within the bounding box
if ($boundary->containsPoint($point)) {
yield $point;
}
}
// If we have child QuadTree nodes....
if (isset($this->northWest)) {
// ... search each child node in turn, merging with any existing results
foreach($this->northWest->search($boundary) as $result)
yield $result;
foreach($this->northEast->search($boundary) as $result)
yield $result;
foreach($this->southWest->search($boundary) as $result)
yield $result;
foreach($this->southEast->search($boundary) as $result)
yield $result;
}
}
}