Slide 1

Slide 1 text

Iterators and Decorators and Generators, Oh My!

Slide 2

Slide 2 text

Hello, World! I am Jeremy Lindblom Software Architect at McGraw-Hill Education @jeremeamia • I ♥ PHP, OSS, APIs, & AWS 2

Slide 3

Slide 3 text

“ An investment in knowledge pays the best interest. — Benjamin Franklin 3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

But first... 5

Slide 6

Slide 6 text

6 [ ] The Array!

Slide 7

Slide 7 text

7 [ ] ♥ There’s no place like home. ♥

Slide 8

Slide 8 text

8 foreach ($data as $value) { echo "{$value}\n"; } foreach ($data as $key => $value) { echo "{$key}: {$value}\n"; }

Slide 9

Slide 9 text

9 $dir = new DirectoryIterator(__DIR__); foreach ($dir as $file) { echo "{$file->getFilename()}\n"; } foreach-able objects?

Slide 10

Slide 10 text

Iterators The Scarecrow doesn’t have a brain, but you do. You will understand why Iterators are awesome. 1

Slide 11

Slide 11 text

11 $dir = new DirectoryIterator(__DIR__); foreach ($dir as $file) { echo "{$file->getFilename()}\n"; }

Slide 12

Slide 12 text

12 $dir = new DirectoryIterator(__DIR__); foreach ($dir as $file) { echo "{$file->getFilename()}\n"; } var_dump($dir instanceof \Traversable); #> bool(true)

Slide 13

Slide 13 text

13 Traversable MyCoolIterator ?

Slide 14

Slide 14 text

14 Traversable MyCoolIterator

Slide 15

Slide 15 text

15 Traversable MyCoolIterator PHP Fatal error: Class MyCoolIterator cannot extend from interface Traversable

Slide 16

Slide 16 text

16 Traversable Iterator IteratorAggregate

Slide 17

Slide 17 text

17 Traversable Iterator IteratorAggregate Can’t directly extend this You extend one of these instead

Slide 18

Slide 18 text

18 Traversable Iterator IteratorAggregate DirectoryIterator

Slide 19

Slide 19 text

19 Traversable Iterator IteratorAggregate current(); key(); valid(); next(); rewind(); getIterator();

Slide 20

Slide 20 text

20 Traversable Iterator IteratorAggregate current(); key(); valid(); next(); rewind(); getIterator();

Slide 21

Slide 21 text

21 foreach ($iter as $key => $value) { echo "{$key}: {$value}\n"; }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

24 The following code is not PSR-2 compliant

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

33 Why?

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

35 $data = range(1, 10);

Slide 36

Slide 36 text

36 $data = range(1, 1000000000);

Slide 37

Slide 37 text

37 $data = range(1, 1000000000); PHP Warning: range(): The supplied range exceeds the maximum array size

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

39 What are the benefits?

Slide 40

Slide 40 text

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.

Slide 41

Slide 41 text

41 Traversable Iterator IteratorAggregate SPL Iterators

Slide 42

Slide 42 text

42 Traversable Iterator IteratorAggregate current(); key(); valid(); next(); rewind(); getIterator();

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Decorators The Tinman is heartless, but your heart will be be warmed as you see objects collaborating. 2

Slide 45

Slide 45 text

The Decorator Design Pattern Dynamically add behavior to an object instance by composing (or “wrapping”) it with another object of the same interface. 45

Slide 46

Slide 46 text

46 ThingInterface ConcreteThing ThingDecorator

Slide 47

Slide 47 text

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 { // … } }

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

50 SPL Iterators CallbackFilterIterator AppendIterator NoRewindIterator IteratorIterator RegexIterator MultipleIterator ArrayIterator DirectoryIterator GlobIterator InfiniteIterator LimitIterator RecursiveIteratorIterator

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

52 $notes = $notesRepository->getNotes(); foreach ($notes as $note) { // ... }

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

54 SPL Iterators CallbackFilterIterator AppendIterator NoRewindIterator IteratorIterator RegexIterator MultipleIterator ArrayIterator DirectoryIterator GlobIterator InfiniteIterator LimitIterator RecursiveIteratorIterator

Slide 55

Slide 55 text

55 $notes = $notesRepository->getNotes(); $notes = new LimitIterator($notes, 0, 50); foreach ($notes as $note) { // ... }

Slide 56

Slide 56 text

56 And now… an epic poem by the phpbard

Slide 57

Slide 57 text

Generators The Lion lacks the courage, but you will be brave enough to use Generators in your code. 3

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

● An interruptible function ● A mechanism for cooperative multitasking ● An Iterator, but easier 59 What is a Generator?

Slide 60

Slide 60 text

60 Traversable Iterator IteratorAggregate SPL Iterators Generator

Slide 61

Slide 61 text

61 function names() { yield 'Joey'; yield 'Izzy'; yield 'Livy'; }

Slide 62

Slide 62 text

62 function names(): \Generator { yield 'Joey'; yield 'Izzy'; yield 'Livy'; } $generator = names();

Slide 63

Slide 63 text

63 function names(): \Generator { yield 'Joey'; yield 'Izzy'; yield 'Livy'; } foreach (names() as $name) { echo "{$name}\n"; } #> Joey #> Izzy #> Livy

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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?

Slide 68

Slide 68 text

68 function iter_range(int $low, int $high) { for ($n = $low; $n <= $high; $n++) { yield $n; } }

Slide 69

Slide 69 text

69 Achievement Unlocked You’ve struck gold on simplicity and robustness

Slide 70

Slide 70 text

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?

Slide 71

Slide 71 text

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?

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

75 class NotesRepository { // … public function getNotes(): iterable { foreach ($this->db->fetchNotes() as $note) { yield new Note($note); } } } The “iterable” pseudo-type!

Slide 76

Slide 76 text

76 iterable array Traversable Iterator IteratorAggregate SPL Iterators Generator

Slide 77

Slide 77 text

Let’s get functional... 77

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

80 function iter_flatmap(iterable $iter, callable $fn) { foreach ($iter as $value) { yield from $fn($value); } }

Slide 81

Slide 81 text

81 function iter_flatmap(iterable $iter, callable $fn) { foreach ($iter as $value) { yield from $fn($value); } }

Slide 82

Slide 82 text

It works IRL! 82

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

● 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?

Slide 89

Slide 89 text

89 iterable array Traversable Iterator IteratorAggregate SPL Iterators Generator All of them!

Slide 90

Slide 90 text

90 Thanks! Any questions? You can find me on Twitter & GitHub as @jeremeamia Give me feedback: https://joind.in/talk/ec8d0 Yay! Iterables!

Slide 91

Slide 91 text

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