Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Turn on the Generator!

Turn on the Generator!

Zombie apocalypses are the worst. Low on food, low on water, low on power. The worst part about zombie apocalypses are the waiting. But we can prepare for that…

This is a talk about the new yield keyword. How it works, what it’s good for, and how to make the most of it. We’ll unpack the structure of a generator, and replace some old parts of our application with new, efficient and wonderfully readable code.

Join me as we fight off the hungry hordes, and unlock the true power hidden in PHP generators.

Christopher Pitt

April 22, 2016
Tweet

More Decks by Christopher Pitt

Other Decks in Technology

Transcript

  1. ARRAYS $coordinates = [ [039.842286, -108.457031], [032.916485, -113.906250], [033.431441, -099.404297],

    [042.032974, -097.031250], [050.064192, -106.962891], ]; foreach ($coordinates as $coordinate) { CDC::innoculate($coordinate); } print "innoculate " . count($coordinates) . " places"; $filter = function($coordinate) { return $coordinate[1] < -98; }; $breakouts = array_filter($coordinates, $filter);
  2. THINGS LIKE ARRAYS $secret = new DomDocument(); $secret->loadHTMLFile( "secure/2021/lockdown-protocols.html" );

    $steps = $secret->getElementsByTagName("h1"); foreach ($steps as $step) { Military::follow($step); } print "followed " . $steps->length . " steps";
  3. THINGS LIKE ARRAYS is_array($coordinates) // 㱺 true is_array($steps) // 㱺

    false $coordinates instanceof Traversable // 㱺 error $steps instanceof Traversable // 㱺 true
  4. ITERATORS class EvacuationProcedures implements Iterator { /** * Returns the

    current value * where the array is at */ function current(); function key(); function next(); function rewind(); function valid(); }
  5. ITERATORS class EvacuationProcedures implements Iterator { function current(); /** *

    Returns the pointer to * the current position */ function key(); function next(); function rewind(); function valid(); }
  6. ITERATORS class EvacuationProcedures implements Iterator { function current(); function key();

    /** * Advances the pointer to * the next position */ function next(); function rewind(); function valid(); }
  7. ITERATORS class EvacuationProcedures implements Iterator { function current(); function key();

    function next(); /** * Moves the pointer back * to the beginning */ function rewind(); function valid(); }
  8. ITERATORS class EvacuationProcedures implements Iterator { function current(); function key();

    function next(); function rewind(); /** * Indicates whether there * are more values to go */ function valid(); }
  9. ABSTRACTION function numberForPatient($patient) { return Registry::getNewNumber($patient); } function admitPatients(array $patients)

    { $numbers = array_map( "numberForPatient", $patients ); for ($i = 0; $i < count($patients); $i++) { Station::assignPatientToRoom( $numbers[$i], $patients[$i] ); } }
  10. ABSTRACTION class Patients implements Iterator { // ... function key()

    { return $this->patients[ $this->pointer ]; } function current() { return numberForPatient( $this->key() ); } }
  11. ABSTRACTION $intake = [ "Cathy", "Cal", "Joe", ]; $patients =

    new Patients($intake); foreach ($patients as $patient => $number) { Station::assignPatientToRoom( $number, $patient ); }
  12. ABSTRACTION $intake = [ "Cathy", "Cal", "Joe", ]; $patients =

    new ClassifiedPatients( new Patients($intake) ); foreach ($patients as $patient => $number) { Station::assignPatientToRoom( $number, $patient ); }
  13. ABSTRACTION class SearchablePatients implements IteratorAggregate { // ...construct($patients) function getIterator()

    { return $this->patients; } function searchFor($patient) { $i = 0; for ($this->patients as $next => $number) { if ($patient == $next) { return $i; } $i++; } } }
  14. ABSTRACTION class CountablePatients implements IteratorAggregate, Countable { // ...construct($patients) function

    count() { return iterator_count($this->patients); } } $countable = new CountablePatients( new Patients($intake) ); count($countable); // 㱺 3
  15. ABSTRACTION class SeekablePatients implements IteratorAggregate, SeekableIterator { // ...construct($patients) function

    seek($position) { $i = 0; foreach ($this->patients as $patient) { if ($i++ == $position) { return; } } } } $seekable = new SeekablePatients( new Patients($intake) ); $seekable ->seek(1) ->current(); // 㱺 "Cal"
  16. ABSTRACTION class ArrayablePatients implements IteratorAggregate, ArrayAccess { // ...construct($patients) function

    offsetExists($offset); function offsetGet($offset); function offsetSet($offset, $value); function offsetUnset($offset); } $array = new ArrayablePatients( new Patients($intake) ); $array[1]; // 㱺 "Cal"
  17. ABSTRACTION • AppendIterator • ArrayIterator • CachingIterator • CallbackFilterIterator •

    DirectoryIterator • EmptyIterator • FilesystemIterator • FilterIterator • GlobIterator
  18. ABSTRACTION • InfiniteIterator • IteratorIterator • LimitIterator • Mul0pleIterator •

    NoRewindIterator • ParentIterator • RecursiveArrayIterator • RecursiveCachingIterator • RecursiveCallbackFilterIterator
  19. ABSTRACTION $instructions = ""; $chapters = file_get_contents( "delta/operating-manual.txt" ); foreach

    ($chapters as $chapter) { $instructions .= file_get_contents( "delta/operating-manual/" . $chapter ); } print "instructions: \n\n" . $instructions; print "you've escaped!";
  20. ABSTRACTION class OperatingManual implements Iterator { // ... private $chapter

    = 0; function rewind() { $this->chapter = 0; } function next() { $this->chapter++; } }
  21. ABSTRACTION class OperatingManual implements Iterator { // ... function key()

    { return $this->chapter[ $this->chapter ]; } function current() { return file_get_contents( $this->key() ); } function valid() { isset( $this->chapters[ $this->chapter ] ); } }
  22. ABSTRACTION $manual = new OperatingManual( "delta/operating-manual.txt" ); foreach ($manual as

    $chapter) { print "next chapter: \n\n" . $chapter; } print "you've escaped!";
  23. ABSTRACTION $manual = new OperatingManual( "delta/operating-manual.txt" ); foreach ($manual as

    $chapter) { print "next chapter: \n\n" . $chapter; } print "you've escaped!";
  24. ITERATOR LIKE THINGS function OperatingManual($file) { $handle = fopen($file, "r");

    while (!feof($handle)) { yield file_get_contents( trim(fgets($handle)) ); } fclose($handle); } $manual = OperatingManual( "delta/operating-manual.txt" );
  25. ITERATOR LIKE THINGS print_r(get_class_methods($manual)); // 㱺 Array // ( //

    [0] => rewind // [1] => valid // [2] => current // [3] => key // [4] => next // [5] => send // [6] => throw // [7] => getReturn // [8] => __wakeup // )
  26. NULL OBJECT PATTERN function arrayToObject(array $data) { $encoded = json_encode($data);

    return json_decode($encoded); } $report = arrayToObject([ "station" => [ "armory" => [ "safe" => [ "weapons" => "one rifle", ], ], ], ]);
  27. NULL OBJECT PATTERN function searchForWeapons($report) { return $report ->station ->armory

    ->safe ->weapons; } print searchForWeapons( arrayToObject(["station" => null]) ); // 㱺 error: accessing $armory on null
  28. NULL OBJECT PATTERN class Maybe { private $value; function __construct($value)

    { $this->value = $value; } function __get($property) { if (is_object($this->value)) { return new static( $this->value->$property ); } } function value() { return $this->value; } }
  29. NULL OBJECT PATTERN function searchForWeapons($report) { return $report ->station ->armory

    ->safe ->weapons ->value(); } print searchForWeapons( arrayToObject(["station" => null]); ); // 㱺 null
  30. NULL OBJECT PATTERN static function from($value) { if ($value instanceof

    static) { return $value; } return new static($value); } static function generator($starting, $generator) { $monad = static::from($starting); $generator = $generator($monad); $next = $generator->current(); while ($generator->valid()) { $monad = static::from($next); $next = $generator->send($monad); } return $generator->getReturn(); }
  31. NULL OBJECT PATTERN $weapons = Maybe::generator( $report, function($report) { $station

    = yield $report->station; $armory = yield $station->armory; $safe = yield $armory->safe; return $safe->weapons; } ); print $weapons;
  32. COROUTINES $people = [ "Lorna", "Rob", "Chris", "Aaron", "Sara", ];

    $fences = [ "next to the old car", "behind the kennel", "out back", ];
  33. COROUTINES function checkRations(array $people) { foreach ($people as $person) {

    print "checking rations for " . $person; yield showRationsFor($person); } } function watchFences(array $people, array $fences) { foreach ($fences as $fence) { $next = next($people); if ($next) { $next = reset($people); } print $next . " is watching " . $fence; yield watchFence($next, $fence); } }
  34. COROUTINES class Task { private $generator; function __construct($generator) { $this->generator

    = $generator; } function run() { $this->generator->next(); } function valid() { return $this->generator->valid(); } }
  35. COROUTINES class Manager { private $tasks = []; function addTask(Task

    $task) { array_unshift($this->tasks, $task); } function run() { while (count($this->tasks)) { $next = array_pop($this->tasks); $next->run(); if ($next->valid()) { $this->addTask($next); } } } }
  36. COROUTINES $manager = new Manager(); $manager->addTask( new Task(checkRations($people) ); $manager->addTask(

    new Task(watchFences($people, $fences) ); $manager->run(); // 㱺 checking rations for Lorna // 㱺 Lorna is watching next to the old car // 㱺 checking rations for Rob // 㱺 Rob is watching behind the kennel // 㱺 checking rations for Chris // 㱺 Chris is watching out back // 㱺 checking rations for Aaron // 㱺 checking rations for Sara
  37. COROUTINES $manager = new Manager(); $manager->addTask( new Task(checkRations($people) ); $manager->addTask(

    new Task(watchFences($people, $fences) ); $manager->run(); // 㱺 checking rations for Lorna // 㱺 Lorna is watching next to the old car // 㱺 checking rations for Rob // 㱺 Rob is watching behind the kennel // 㱺 checking rations for Chris // 㱺 Chris is watching out back // 㱺 checking rations for Aaron // 㱺 checking rations for Sara