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