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.

061e3bae4ce4234a2194d20a382e5d19?s=128

Christopher Pitt

April 22, 2016
Tweet

Transcript

  1. None
  2. None
  3. None
  4. TURN ON THE GENERATOR

  5. 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);
  6. 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";
  7. THINGS LIKE ARRAYS is_array($coordinates) // 㱺 true is_array($steps) // 㱺

    false $coordinates instanceof Traversable // 㱺 error $steps instanceof Traversable // 㱺 true
  8. THINGS LIKE ARRAYS class ContainmentProtocols implements Traversable { } //

    㱺 error
  9. ITERATORS class EvacuationProcedures implements Iterator { function current(); function key();

    function next(); function rewind(); function valid(); }
  10. ITERATORS class EvacuationProcedures implements Iterator { /** * Returns the

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

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

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

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

    function next(); function rewind(); /** * Indicates whether there * are more values to go */ function valid(); }
  15. ABSTRACTION

  16. 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] ); } }
  17. ABSTRACTION class Patients implements Iterator { // ... function key()

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

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

    { return md5( $this->patients->key() ); } }
  20. ABSTRACTION $intake = [ "Cathy", "Cal", "Joe", ]; $patients =

    new ClassifiedPatients( new Patients($intake) ); foreach ($patients as $patient => $number) { Station::assignPatientToRoom( $number, $patient ); }
  21. 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++; } } }
  22. ABSTRACTION class CountablePatients implements IteratorAggregate, Countable { // ...construct($patients) function

    count() { return iterator_count($this->patients); } } $countable = new CountablePatients( new Patients($intake) ); count($countable); // 㱺 3
  23. 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"
  24. 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"
  25. ABSTRACTION • AppendIterator • ArrayIterator • CachingIterator • CallbackFilterIterator •

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

    NoRewindIterator • ParentIterator • RecursiveArrayIterator • RecursiveCachingIterator • RecursiveCallbackFilterIterator
  27. ABSTRACTION • RecursiveDirectoryIterator • RecursiveFilterIterator • RecursiveIteratorIterator • RecursiveRegexIterator •

    RecursiveTreeIterator • RegexIterator
  28. PROBLEMS $patients = new Patients($intake); array_filter($patients, function($number) { // 㱺

    error });
  29. PROBLEMS // github.com/nikic/iter $patients = new Patients($intake); iter\filter(function($number) { //

    㱺 ok }, $patients);
  30. PROBLEMS // github.com/nikic/iter $patients = new Patients($intake); iter\filter(function($number) { //

    㱺 ok }, $patients);
  31. None
  32. None
  33. ABSTRACTION

  34. 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!";
  35. ABSTRACTION class OperatingManual implements Iterator { private $chapters; function __construct($file)

    { $this->chapters = file_get_contents( $file ); } }
  36. ABSTRACTION class OperatingManual implements Iterator { // ... private $chapter

    = 0; function rewind() { $this->chapter = 0; } function next() { $this->chapter++; } }
  37. 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 ] ); } }
  38. ABSTRACTION $manual = new OperatingManual( "delta/operating-manual.txt" ); foreach ($manual as

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

    $chapter) { print "next chapter: \n\n" . $chapter; } print "you've escaped!";
  43. 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" );
  44. 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 // )
  45. None
  46. ARRAYS ARE LIKE ROLODEXES

  47. ASK FOR ITEM #3, AND YOU'LL GET ITEM #3

  48. IT'S PREDICTABLE

  49. FUNCTIONS ACT LIKE DATA TABLES

  50. PUT THE SAME THING IN...

  51. ...YOU GET THE SAME THING OUT

  52. IT'S PREDICTABLE

  53. NULL OBJECT PATTERN

  54. NULL OBJECT PATTERN function arrayToObject(array $data) { $encoded = json_encode($data);

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

    ->safe ->weapons; } print searchForWeapons( arrayToObject(["station" => null]) ); // 㱺 error: accessing $armory on null
  56. NULL OBJECT PATTERN function searchForWeapons($report) { if ($station = $report->station)

    { if ($armory = $station->armory) { // ... } } }
  57. 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; } }
  58. NULL OBJECT PATTERN function searchForWeapons($report) { return $report ->station ->armory

    ->safe ->weapons ->value(); } print searchForWeapons( arrayToObject(["station" => null]); ); // 㱺 null
  59. 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(); }
  60. 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;
  61. COROUTINES

  62. COROUTINES $people = [ "Lorna", "Rob", "Chris", "Aaron", "Sara", ];

    $fences = [ "next to the old car", "behind the kennel", "out back", ];
  63. 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); } }
  64. COROUTINES class Task { private $generator; function __construct($generator) { $this->generator

    = $generator; } function run() { $this->generator->next(); } function valid() { return $this->generator->valid(); } }
  65. 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); } } } }
  66. 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
  67. 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
  68. None
  69. None