34
Generated Power PHP 5.5 – Generators

Generated Power: PHP 5.5 Generators

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

Page 1: Generated Power: PHP 5.5 Generators

Generated Power

PHP 5.5 – Generators

Page 2: 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

Page 3: Generated Power: PHP 5.5 Generators

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

Page 4: Generated Power: PHP 5.5 Generators

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

Page 5: Generated Power: PHP 5.5 Generators

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) { }

Page 6: Generated Power: PHP 5.5 Generators

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

Page 7: Generated Power: PHP 5.5 Generators

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(); }

Page 8: Generated Power: PHP 5.5 Generators

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; }

Page 9: Generated Power: PHP 5.5 Generators

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

Page 10: Generated Power: PHP 5.5 Generators

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

Page 11: Generated Power: PHP 5.5 Generators

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

Page 12: Generated Power: PHP 5.5 Generators

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

Page 13: Generated Power: PHP 5.5 Generators

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;

}

Page 14: Generated Power: PHP 5.5 Generators

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();

Page 15: Generated Power: PHP 5.5 Generators

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

Page 16: Generated Power: PHP 5.5 Generators

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;}

Page 17: Generated Power: PHP 5.5 Generators

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

Page 18: Generated Power: PHP 5.5 Generators

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

Page 19: Generated Power: PHP 5.5 Generators

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

Page 20: Generated Power: PHP 5.5 Generators

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;

}

Page 21: Generated Power: PHP 5.5 Generators

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);

Page 22: Generated Power: PHP 5.5 Generators

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

Page 23: Generated Power: PHP 5.5 Generators

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 );

}

Page 24: Generated Power: PHP 5.5 Generators

PHP 5.5 – Generators

• It is possible to combine a Generator to both send and accept data

Page 25: Generated Power: PHP 5.5 Generators

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

Page 26: Generated Power: PHP 5.5 Generators

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

Page 27: Generated Power: PHP 5.5 Generators

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

Page 28: Generated Power: PHP 5.5 Generators

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);

    }

}

Page 29: Generated Power: PHP 5.5 Generators

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

Page 30: Generated Power: PHP 5.5 Generators

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;

        }

    }

}

Page 31: Generated Power: PHP 5.5 Generators

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);

}

Page 32: Generated Power: PHP 5.5 Generators

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);

    }

}

Page 33: Generated Power: PHP 5.5 Generators

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

Page 34: Generated Power: PHP 5.5 Generators

PHP 5.5 – Generators

?Questions