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

Iterators and Decorators and Generators, Oh My!

Iterators and Decorators and Generators, Oh My!

Iterators are an awesome and important feature of PHP, and PHP comes with a lot of them built in too. Let’s talk about what they are, how they’re used, and how to make your own. Then we'll talk about Generators, which were introduced in PHP 5.5, and that take Iterators to the next level. And… we can’t talk about Iterators without also discussing composition and the Decorator design pattern. After all, many of the SPL Iterator classes are Decorators too.

Jeremy Lindblom

September 21, 2018
Tweet

More Decks by Jeremy Lindblom

Other Decks in Programming

Transcript

  1. Hello, World! I am Jeremy Lindblom Software Architect at McGraw-Hill

    Education @jeremeamia • I ♥ PHP, OSS, APIs, & AWS 2
  2. Mission Objectives ★ Understand and Apply Iterators Learn how they

    work, why they’re great, and how to make them ★ Understand the Decorator Design Pattern Learn how it allows for the composition of Iterator behavior ★ Understand and Apply Generators Learn how they work and how they make Iterators easier 4
  3. 8 foreach ($data as $value) { echo "{$value}\n"; } foreach

    ($data as $key => $value) { echo "{$key}: {$value}\n"; }
  4. 9 $dir = new DirectoryIterator(__DIR__); foreach ($dir as $file) {

    echo "{$file->getFilename()}\n"; } foreach-able objects?
  5. Iterators The Scarecrow doesn’t have a brain, but you do.

    You will understand why Iterators are awesome. 1
  6. 12 $dir = new DirectoryIterator(__DIR__); foreach ($dir as $file) {

    echo "{$file->getFilename()}\n"; } var_dump($dir instanceof \Traversable); #> bool(true)
  7. 22 foreach ($iter as $key => $value) { echo "{$key}:

    {$value}\n"; } // --- THIS IS THE SAME AS --------------------- $iter->rewind(); while ($iter->valid()) { echo "{$iter->key()}: {$iter->current()}\n"; $iter->next(); }
  8. 23 $iter = new RangeIterator(3, 8); foreach ($iter as $key

    => $value) { echo "{$key}: {$value}\n"; } #> 0: 3 #> 1: 4 #> 2: 5 #> 3: 6 #> 4: 7 #> 5: 8
  9. 25 class RangeIterator implements Iterator { private $low, $high, $index;

    function __construct(int $low, int $high) { … } function rewind() { $this->index = 0; } function next() { $this->index++; } function valid() { return $this->low + $this->index <= $this->high; } function key() { return $this->index; } function current() { return $this->low + $this->index; } }
  10. 26 class RangeIterator implements Iterator { private $low, $high, $index;

    function __construct(int $low, int $high) { … } function rewind() { $this->index = 0; } function next() { $this->index++; } function valid() { return $this->low + $this->index <= $this->high; } function key() { return $this->index; } function current() { return $this->low + $this->index; } } The Data
  11. 27 class RangeIterator implements Iterator { private $low, $high, $index;

    function __construct(int $low, int $high) { … } function rewind() { $this->index = 0; } function next() { $this->index++; } function valid() { return $this->low + $this->index <= $this->high; } function key() { return $this->index; } function current() { return $this->low + $this->index; } } The Cursor
  12. 28 class RangeIterator implements Iterator { private $low, $high, $index;

    function __construct(int $low, int $high) { … } function rewind() { $this->index = 0; } function next() { $this->index++; } function valid() { return $this->low + $this->index <= $this->high; } function key() { return $this->index; } function current() { return $this->low + $this->index; } } Moves the Cursor
  13. 29 class RangeIterator implements Iterator { private $low, $high, $index;

    function __construct(int $low, int $high) { … } function rewind() { $this->index = 0; } function next() { $this->index++; } function valid() { return $this->low + $this->index <= $this->high; } function key() { return $this->index; } function current() { return $this->low + $this->index; } } Yields the data
  14. For valid(), key(), and current() • Do “the work” in

    either valid() or current(). • These methods need to be idempotent per iteration. • Avoid doing expensive work more than once (i.e., you should internally cache/memoize the current iteration’s data). 30 Implementation Tips
  15. 31 $iter = new RangeIterator(3, 8); foreach ($iter as $key

    => $value) { echo "{$key}: {$value}\n"; } #> 0: 3 #> 1: 4 #> 2: 5 #> 3: 6 #> 4: 7 #> 5: 8
  16. 32 $iter = new RangeIterator(3, 8); foreach ($iter as $key

    => $value) { echo "{$key}: {$value}\n"; } // --- THIS IS THE SAME AS --------------------- $data = range(3, 8); foreach ($data as $key => $value) { echo "{$key}: {$value}\n"; }
  17. 34 class RangeIterator implements Iterator { private $low, $high, $index;

    function __construct(int $low, int $high) { … } function rewind() { $this->index = 0; } function next() { $this->index++; } function valid() { return $this->low + $this->index <= $this->high; } function key() { return $this->index; } function current() { return $this->low + $this->index; } }
  18. 38 $iter = new RangeIterator(1, 1000000000); foreach ($iter as $key

    => $value) { echo "{$key}: {$value}\n"; } #> 0: 1 #> 1: 2 #> 2: 3 later... #> 999999998: 999999999 #> 999999999: 1000000000
  19. Benefits of Iterators vs. Arrays Lower Memory Handle data one

    iteration at a time. Don’t store the entire data set in memory at once. Allows for Infinity Because you handle data one iteration at a time, you can handle data sets that are large, have an unknown length, or are even infinitely long. Lazy/Deferred Work Defer data retrieval and transformation until the point in time it is actually needed. 40 Encapsulation Iterators are self-contained. Logic to work with a set of data lives in one class, and does not need to be repeated. Composition Using the Decorator and other composition design patterns, data transformation behavior can be composed from multiple iterator objects. SPL Implementations The Standard PHP Library comes with a plethora of iterator interfaces and classes for basic and advanced/specific use cases.
  20. 43 class RangeIterator implements IteratorAggregate { private $low, $high; function

    __construct(int $low, int $high) { … } function getIterator() { $data = range($this->low, $this->high); return new ArrayIterator($data); } }
  21. Decorators The Tinman is heartless, but your heart will be

    be warmed as you see objects collaborating. 2
  22. The Decorator Design Pattern Dynamically add behavior to an object

    instance by composing (or “wrapping”) it with another object of the same interface. 45
  23. 47 interface EventPublisher { public function publish(Event $event): bool; }

    class HttpPublisher implements EventPublisher { private $httpClient; public function __construct(Client $client) {…} public function publish(Event $event): bool { // … } }
  24. 48 class LoggingPublisher implements EventPublisher { public function __construct( EventPublisher

    $publisher, LoggerInterface $logger ) {…} public function publish(Event $event): bool { $this->logger->info('Published event!'); $this->publisher->publish($event); } }
  25. 49 $client = new Client(…); $pub1 = new HttpPublisher($client); $db

    = new Database(…); $pub2 = new DbPublisher($db); $logger = new Logger(…); $pub1 = new LoggingPublisher($pub1, $logger); $pub2 = new LoggingPublisher($pub2, $logger); $event = new Event(…); $pub1->publish($event); $pub2->publish($event);
  26. 50 SPL Iterators CallbackFilterIterator AppendIterator NoRewindIterator IteratorIterator RegexIterator MultipleIterator ArrayIterator

    DirectoryIterator GlobIterator InfiniteIterator LimitIterator RecursiveIteratorIterator
  27. 51 class NotesRepository { // … public function getNotes(): array

    { $notes = []; foreach ($this->db->fetchNotes() as $note) { $notes[] = new Note($note); } return $notes; } }
  28. 53 class NotesRepository { // … public function getNotes(): \Iterator

    { $notes = []; foreach ($this->db->fetchNotes() as $note) { $notes[] = new Note($note); } return new ArrayIterator($notes); } }
  29. 54 SPL Iterators CallbackFilterIterator AppendIterator NoRewindIterator IteratorIterator RegexIterator MultipleIterator ArrayIterator

    DirectoryIterator GlobIterator InfiniteIterator LimitIterator RecursiveIteratorIterator
  30. Generators The Lion lacks the courage, but you will be

    brave enough to use Generators in your code. 3
  31. • An interruptible function • A mechanism for cooperative multitasking

    • An Iterator, but easier 58 What is a Generator?
  32. • An interruptible function • A mechanism for cooperative multitasking

    • An Iterator, but easier 59 What is a Generator?
  33. 63 function names(): \Generator { yield 'Joey'; yield 'Izzy'; yield

    'Livy'; } foreach (names() as $name) { echo "{$name}\n"; } #> Joey #> Izzy #> Livy
  34. 64 /** @dataProvider provideTestCases */ … public function provideTestCases() {

    return [ [2, 6, 8], [0, 6, 6], [2, -6, -4], [2, 'a', null], ]; }
  35. 65 /** @dataProvider provideTestCases */ … public function provideTestCases() {

    yield [2, 6, 8]; yield [0, 6, 6]; yield [2, -6, -4]; yield [2, 'a', null]; }
  36. 66 /** @dataProvider provideTestCases */ … public function provideTestCases() {

    yield 'with positives' => [2, 6, 8]; yield 'with zeros' => [0, 6, 6]; yield 'with negatives' => [2, -6, -4]; yield 'with non-numbers' => [2, 'a', null]; }
  37. 67 class RangeIterator implements Iterator { private $low, $high, $index;

    function __construct(int $low, int $high) { … } function rewind() { $this->index = 0; } function next() { $this->index++; } function valid() { return $this->low + $this->index <= $this->high; } function key() { return $this->index; } function current() { return $this->low + $this->index; } } Remember this?
  38. 68 function iter_range(int $low, int $high) { for ($n =

    $low; $n <= $high; $n++) { yield $n; } }
  39. 70 class RangeIterator implements IteratorAggregate { private $low, $high; function

    __construct(int $low, int $high) { … } function getIterator() { $data = range($this->low, $this->high); return new ArrayIterator($data); } } Remember this?
  40. 71 class RangeIterator implements IteratorAggregate { private $low, $high; function

    __construct(int $low, int $high) { … } function getIterator() { for ($n = $this->low; $n <= $this->high; $n++) { yield $n; } } } Remember this?
  41. 72 class NotesRepository { // … public function getNotes(): \Iterator

    { $notes = []; foreach ($this->db->fetchNotes() as $note) { $notes[] = new Note($note); } return new ArrayIterator($notes); } } Remember this?
  42. 73 class NotesRepository { // … public function getNotes(): iterable

    { foreach ($this->db->fetchNotes() as $note) { yield new Note($note); } } }
  43. 74 class NotesRepository { // … public function getNotes(): iterable

    { foreach ($this->db->fetchNotes() as $note) { yield new Note($note); } } }
  44. 75 class NotesRepository { // … public function getNotes(): iterable

    { foreach ($this->db->fetchNotes() as $note) { yield new Note($note); } } } The “iterable” pseudo-type!
  45. 78 function iter_filter(iterable $iter, callable $fn): iterable { foreach ($iter

    as $value) { if ($fn($value)) { yield $value; } } }
  46. 79 function iter_filter(iterable $iter, callable $fn): iterable { foreach ($iter

    as $value) { if ($fn($value)) { yield $value; } } } function iter_map(iterable $iter, callable $fn): iterable { foreach ($iter as $value) { yield $fn($value); } }
  47. 83 class ResultPaginator implements IteratorAggregate { public function __construct( Result

    $initialResult, callable $getNextPageFn ) {…} public function getIterator() { $current = $this->initialResult; while ($current) { yield $current; $current = ($this->getNextPageFn)($current); } } }
  48. 84 class ModelPaginator extends IteratorIterator { function __construct($paginator, $key, $mapper)

    { parent::__construct(iter_flatmap( $paginator, function (Result $result) use ($mapper, $key) { return iter_map($result[$key], $mapper); } )); }}
  49. Mission Objectives ★ Understand and Apply Iterators Learn how they

    work, why they’re great, and how to make them ★ Understand the Decorator Design Pattern Learn how it allows for the composition of Iterator behavior ★ Understand and Apply Generators Learn how they work and how they make Iterators easier 85
  50. “There is no silver bullet. There are always options, and

    the options have consequences. — Ben Horowitz 86
  51. “There is no silver bullet. There are always options, and

    the options have consequences. — Ben Horowitz 87 tradeoffs
  52. • Using Arrays • Implementing an Iterator • Implementing IteratorAggregate

    • Using an existing SPL Iterator • Extending an SPL Iterator • Composing/decorating multiple Iterators • Implementing Generators • Using general Generators: https://github.com/nikic/iter 88 What are your options?
  53. 90 Thanks! Any questions? You can find me on Twitter

    & GitHub as @jeremeamia Give me feedback: https://joind.in/talk/ec8d0 Yay! Iterables!
  54. Credits Special thanks to all the people who made and

    released these awesome resources for free: • Presentation template by SlidesCarnival • Photographs from the Wizard of Oz 91