Upload
mark-baker
View
4.858
Download
2
Tags:
Embed Size (px)
DESCRIPTION
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
Citation preview
Generated Power
PHP 5.5 – Generators
Who am I?
Mark BakerDesign and Development ManagerInnovEd (Innovative Solutions for Education) Learning Ltd
Coordinator and Developer of:Open Source PHPOffice library
PHPExcel, PHPWord,PHPPowerPoint, PHPProject, PHPVisioMinor contributor to PHP coreOther small open source libraries available on github
@Mark_Baker
https://github.com/MarkBaker
http://uk.linkedin.com/pub/mark-baker/b/572/171
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
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
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) { }
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
PHP 5.5 – Generators
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; } }
$rangeGenerator = xrange(0,10);
while ($rangeGenerator->valid()) { $key = $rangeGenerator->key(); $value = $rangeGenerator->current(); echo $key , ' -> ' , $value, PHP_EOL; $rangeGenerator->next(); }
PHP 5.5 – Generators
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; } }
foreach (xrange(0,10) as $key => $value) { echo $key , ' -> ' , $value, PHP_EOL; }
PHP 5.5 – 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
PHP 5.5 – Generators
function fibonacci($count) { $prev = 0; $current = 1;
for ($i = 0; $i < $count; ++$i) { yield $prev; $current = $prev + $current; $prev = $current - $prev; }}
foreach (fibonacci(15) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
0 -> 0
1 -> 1
2 -> 1
3 -> 2
4 -> 3
5 -> 5
6 -> 8
7 -> 13
8 -> 21
9 -> 34
10 -> 55
11 -> 89
12 -> 144
13 -> 233
14 -> 377
15 -> 610
PHP 5.5 – 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
PHP 5.5 – 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;
}
$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
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;
}
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();
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
PHP 5.5 – Generators
function xrange($lower, $upper) { $k = 0; for ($i = $lower; $i <= $upper; ++$i) { yield ++$k => $i; }}
foreach (xrange(0,10) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
PHP 5.5 – Generators
function duplicateKeys($lower, $upper) {
for ($i = $lower; $i <= $upper; ++$i) {
yield (($i-1) % 3) + 1 => $i;
}
}
foreach (duplicateKeys(1,15) as $i => $value)
{
echo $i , ' -> ' , $value, PHP_EOL;
}
1 -> 1
2 -> 2
3 -> 3
1 -> 4
2 -> 5
3 -> 6
1 -> 7
2 -> 8
3 -> 9
1 -> 10
2 -> 11
3 -> 12
1 -> 13
2 -> 14
3 -> 15
PHP 5.5 – Generators
function duplicateKeys($string) {
$string = strtolower($string);
$length = strlen($string);
for ($i = 0; $i < $length; ++$i) {
yield
strpos($string, $string[$i]) =>
$string[$i];
}
}
foreach (duplicateKeys('badass') as $i => $value) {
echo $i , ' -> ' , $value, PHP_EOL;
}
0 -> b
1 -> a
2 -> d
1 -> a
4 -> s
4 -> s
PHP 5.5 – Generators
function floatKeys($lower, $upper) {
for ($i = $lower; $i <= $upper; ++$i) {
yield ($i / 5) => $i;
}
}
foreach (floatKeys(1,16) as $i => $value) {
printf(
'%0.2f -> %2d' . PHP_EOL,
$i,
$value
);
}
0.20 -> 1
0.40 -> 2
0.60 -> 3
0.80 -> 4
1.00 -> 5
1.20 -> 6
1.40 -> 7
1.60 -> 8
1.80 -> 9
2.00 -> 10
2.20 -> 11
2.40 -> 12
2.60 -> 13
2.80 -> 14
3.00 -> 15
3.20 -> 16
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;
}
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);
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);
}
LondonNew YorkParisMunich
PHP 5.5 – Generators$logFileName = __DIR__ . '/error.log';
function logger($logFileName) {
$f = fopen($logFileName, 'a');
while ($logentry = yield) {
fwrite(
$f,
(new DateTime())->format('Y-m-d H:i:s ') .
$logentry .
PHP_EOL
);
}
}
$logger = logger($logFileName);
for($i = 0; $i < 12; ++$i) {
$logger->send('Message #' . $i );
}
PHP 5.5 – Generators
• It is possible to combine a Generator to both send and accept data
PHP 5.5 – Generators
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
yield $i => pow($i, $i);
$continue = yield;
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while ($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->next();
$generatorObject->send($carryOnRegardless);
echo $key, ' -> ', $value, PHP_EOL;
}
1 -> 1
2 -> 4
3 -> 27
4 -> 256
5 -> 3125
6 -> 46656
7 -> 823543
8 -> 16777216
9 -> 387420489
10 -> 10000000000
PHP 5.5 – Generators (Gotcha)
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
yield pow($i, $i);
$continue = yield;
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while ($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->next();
$generatorObject->send($carryOnRegardless);
echo $key, ' -> ', $value, PHP_EOL;
}
0 -> 1
2 -> 4
4 -> 27
6 -> 256
8 -> 3125
10 -> 46656
PHP 5.5 – Generators
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
$continue = (yield pow($i, $i));
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
echo $key, ' -> ', $value, PHP_EOL;
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->send($carryOnRegardless);
}
0 -> 1
1 -> 4
2 -> 27
3 -> 256
4 -> 3125
5 -> 46656
6 -> 823543
7 -> 16777216
8 -> 387420489
9 -> 10000000000
PHP 5.5 – Generatorsfunction bingo($card) {
$card = array_flip($card);
while (!empty($card)) {
$number = yield;
echo 'Checking card';
if (isset($card[$number])) {
echo ' - Match';
unset($card[$number]);
}
if (empty($card)) {
echo ' *** HOUSE ***';
} else {
echo ' - ', count($card),
' number',
(count($card) == 1 ? '' : 's'),
' left';
}
yield empty($card);
}
}
PHP 5.5 – Generators$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;
}
$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;
}
}
Matthew has 3,8,11
Mark has 6,7,11
Luke has 3,9,12
John has 3,7,12
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
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
PHP 5.5 – Generatorspublic 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;
}
}
}
PHP 5.5 – Generators$files = glob('server*.log');
$domain = 'innovedtest.co.uk';
$pregDomain = '/' . preg_quote($domain) . '/';
function searchLogData($fileName, $searchDomain) {
$file = new \SplFileObject($fileName);
while (!$file->eof()) {
$logData = $file->fgets();
if ($logData !== NULL &&
preg_match($searchDomain, $logData)) {
yield $logData;
}
}
}
$fileGeneratorArray = array();
foreach($files as $filename) {
$fileGeneratorArray[] = searchLogData($filename, $pregDomain);
}
PHP 5.5 – Generators$output = fopen('php://output', 'w');
while (!empty($fileGeneratorArray)) {
foreach($fileGeneratorArray as $key => $fileGenerator) {
$result = $fileGenerator->current();
$fileGenerator->next();
if (!$fileGenerator->valid()) {
unset($fileGeneratorArray[$key]);
}
fwrite($output, $result);
}
}
PHP 5.5 – Generators
• Additional Reading:
• http://blog.ircmaxell.com/2012/07/what-generators-can-do-for-you.html• http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
PHP 5.5 – Generators
?Questions