2. Looping the Loop with SPL
Iterators
What is an Iterator?
An Iterator is an object that enables a programmer to
traverse a container, particularly lists.
A Behavioural Design Pattern (GoF)
3. Looping the Loop with SPL
Iterators
What is an Iterator?
$dataSet = ['A', 'B', 'C'];
foreach($dataSet as $key => $value) {
echo "{$key} => {$value}", PHP_EOL;
}
4. Looping the Loop with SPL
Iterators
What is an Iterator?
0 => A
1 => B
2 => C
5. Looping the Loop with SPL
Iterators
What is an Iterator?
$data = ['A', 'B', 'C'];
$dataSet = new ArrayIterator($data);
foreach($dataSet as $key => $value) {
echo "{$key} => {$value}", PHP_EOL;
}
6. Looping the Loop with SPL
Iterators
What is an Iterator?
0 => A
1 => B
2 => C
7. Looping the Loop with SPL
Iterators
What is an Iterator?
interface Iterator extends Traversable {
/* Methods */
abstract public current ( void ) : mixed
abstract public key ( void ) : scalar
abstract public next ( void ) : void
abstract public rewind ( void ) : void
abstract public valid ( void ) : bool
}
8. Looping the Loop with SPL
Iterators
What is an Iterator?
$data = ['A', 'B', 'C'];
$iterator = new ArrayIterator($data);
$iterator->rewind();
while ($iterator->valid()) {
$key = $iterator->key();
$value = $iterator->current();
echo "{$key} => {$value}", PHP_EOL;
$iterator->next();
}
9. Looping the Loop with SPL
Iterators
What is an Iterator?
0 => A
1 => B
2 => C
11. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
12. Looping the Loop with SPL
Iterators
The Iterable Tree
Not all iterables are equal
You can count the elements in an array !
Can you count the elements in an Iterator?
13. Looping the Loop with SPL
Iterators
The Iterable Tree
Not all iterables are equal
You can access the elements of an array by
their index !
Can you access the elements of an Iterator
by index ?
14. Looping the Loop with SPL
Iterators
The Iterable Tree
Not all iterables are equal
You can rewind an Iterator !
Can you rewind a Generator ?
15. Looping the Loop with SPL
Iterators
The Iterable Tree
Not all iterables are equal
Iterator_* functions (e.g. iterator_to_array)
work on Iterators ?
Can you use iterator_* functions on an
IteratorAggregate ?
16. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
Iterable is a pseudo-type
introduced in PHP 7.1. It accepts
any array, or any object that
implements the Traversable
interface. Both of these types are
iterable using foreach.
17. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
An array in PHP is an ordered map, a
type that associates values to keys. This
type is optimized for several different
uses; it can be treated as an array, list
(vector), hash table (an implementation
of a map), dictionary, collection, stack,
queue, and more.
The foreach control structure exists
specifically for arrays.
18. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
Abstract base interface to detect if a
class is traversable using foreach. It
cannot be implemented alone in
Userland code; but instead must be
implemented through either
IteratorAggregate or Iterator.
Internal (built-in) classes that implement
this interface can be used in a foreach
construct and do not need to implement
IteratorAggregate or Iterator.
19. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
Abstract base interface to detect if a
class is traversable using foreach. It
cannot be implemented alone in
Userland code; but instead must be
implemented through either
IteratorAggregate or Iterator.
PDOStatement
DatePeriod
SimpleXMLElement
20. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
An Interface for external iterators or
objects that can be iterated themselves
internally.
PHP already provides a number of
iterators for many day to day tasks
within the Standard PHP Library (SPL).
SPL Iterators
SPL DataStructures
WeakMaps (PHP 8)
21. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
Generators provide an easy way to
implement simple user-defined iterators
without the overhead or complexity of
implementing a class that implements
the Iterator interface.
This flexibility does come at a cost,
however: generators are forward-only
iterators, and cannot be rewound once
iteration has started.
22. Looping the Loop with SPL
Iterators
The Iterable Tree
iterable
array
Traversable
Iterator
Generator
IteratorAggregate
IteratorAggregate is a special interface
for implementing an abstraction layer
between user code and iterators.
Implementing an IteratorAggregate will
allow you to delegate the work of
iteration to a separate class, while still
enabling you to use the collection inside
a foreach loop.
24. Looping the Loop with SPL
Iterators
Implementing an Iterator
interface Iterator extends Traversable {
/* Methods */
abstract public current ( void ) : mixed
abstract public key ( void ) : scalar
abstract public next ( void ) : void
abstract public rewind ( void ) : void
abstract public valid ( void ) : bool
}
25. Looping the Loop with SPL
Iterators
Implementing an Iterator
class Utf8StringIterator implements Iterator {
private $string;
private $offset = 0;
private $length = 0;
public function __construct(string $string) {
$this->string = $string;
$this->length = iconv_strlen($string);
}
public function valid(): bool {
return $this->offset < $this->length;
}
...
}
26. Looping the Loop with SPL
Iterators
Implementing an Iterator
class Utf8StringIterator implements Iterator {
...
public function rewind(): void {
$this->offset = 0;
}
public function current(): string {
return iconv_substr($this->string, $this->offset, 1);
}
public function key(): int {
return $this->offset;
}
public function next(): void {
++$this->offset;
}
}
27. Looping the Loop with SPL
Iterators
Implementing an Iterator
$stringIterator = new Utf8StringIterator('Καλησπέρα σε όλους');
foreach($stringIterator as $character) {
echo "[{$character}]";
}
28. Looping the Loop with SPL
Iterators
Implementing an Iterator
[Κ][α][λ][η][σ][π][έ][ρ][α][ ][σ][ε][ ][ό][λ][ο][υ][ς]
29. Looping the Loop with SPL
Iterators
Implementing an Iterator
class FileLineIterator implements Iterator {
private $fileHandle;
private $line = 1;
public function __construct(string $fileName) {
$this->fileHandle = fopen($fileName, 'r');
}
public function __destruct() {
fclose($this->fileHandle);
}
}
30. Looping the Loop with SPL
Iterators
Implementing an Iterator
class FileLineIterator implements Iterator {
...
public function current(): string {
$fileData = fgets($this->fileHandle);
return rtrim($fileData, "n");
}
public function key(): int {
return $this->line;
}
public function next(): void {
++$this->line;
}
}
31. Looping the Loop with SPL
Iterators
Implementing an Iterator
class FileLineIterator implements Iterator {
...
public function rewind(): void {
fseek($this->fileHandle, 0);
$this->line = 1;
}
public function valid(): bool {
return !feof($this->fileHandle);
}
}
32. Looping the Loop with SPL
Iterators
Implementing an Iterator
$fileLineIterator = new FileLineIterator(__FILE__);
foreach($fileLineIterator as $lineNumber => $fileLine) {
echo "{$lineNumber}: {$fileLine}", PHP_EOL;
}
33. Looping the Loop with SPL
Iterators
Implementing an Iterator
1: <?php
2:
3: class FileLineIterator implements Iterator {
4: private $fileHandle;
5: private $line = 1;
6:
7: public function __construct(string $fileName) {
8: $this->fileHandle = fopen($fileName, 'r’);
9: }
10:
…
35. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
public function all(): array {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
$statement->execute();
$users = $statement->fetchAll();
return array_map(fn(array $user): User => User::fromArray($user), $users);
}
}
36. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
/**
* @return User[]
*/
public function all(): array {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
$statement->execute();
$users = $statement->fetchAll();
return array_map(fn(array $user): User => User::fromArray($user), $users);
}
}
37. Looping the Loop with SPL
Iterators
class UserCollection extends ArrayIterator {
}
38. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
public function all(): UserCollection {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
$statement->execute();
$users = $statement->fetchAll();
return new UserCollection(
array_map(fn(array $user): User => User::fromArray($user), $users)
);
}
}
39. Looping the Loop with SPL
Iterators
class UserCollection extends ArrayIterator {
public function current(): User {
return User::fromArray(parent::current());
}
}
40. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
public function all(): UserCollection {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
$statement->execute();
$users = $statement->fetchAll();
return new UserCollection($users);
}
}
42. Looping the Loop with SPL
Iterators
ArrayIterator
Implements:
Countable
ArrayAccess
Serializable
SeekableIterator (which entends Iterator)
43. Looping the Loop with SPL
Iterators
ArrayIterator
Implements:
Countable
44. Looping the Loop with SPL
Iterators
ArrayIterator – Countable
interface Countable {
/* Methods */
abstract public count ( void ) : int
}
45. Looping the Loop with SPL
Iterators
ArrayIterator – Countable
$userData = ['user1', 'user2', 'user3', 'user4'];
$userList = new UserList($userData);
$userCount = count($userList);
echo "There are {$userCount} users in this list", PHP_EOL;
46. Looping the Loop with SPL
Iterators
ArrayIterator – Countable
$userData = ['user1', 'user2', 'user3', 'user4'];
$userList = new UserList($userData);
echo "There are {$userList->count()} users in this list", PHP_EOL;
47. Looping the Loop with SPL
Iterators
ArrayIterator – Countable
There are 4 users in this list
48. Looping the Loop with SPL
Iterators
ArrayIterator
Implements:
Countable
ArrayAccess
49. Looping the Loop with SPL
Iterators
ArrayIterator – ArrayAccess
interface ArrayAccess {
/* Methods */
abstract public offsetExists ( mixed $offset ) : bool
abstract public offsetGet ( mixed $offset ) : mixed
abstract public offsetSet ( mixed $offset , mixed $value ) : void
abstract public offsetUnset ( mixed $offset ) : void
}
50. Looping the Loop with SPL
Iterators
ArrayIterator – ArrayAccess
$userData = ['user1', 'user2', 'user3', 'user4'];
$userList = new UserList($userData);
echo "The 3rd entry in the list is {$userList[2]}", PHP_EOL;
51. Looping the Loop with SPL
Iterators
ArrayIterator – ArrayAccess
The 3rd entry in the list is user3
52. Looping the Loop with SPL
Iterators
ArrayIterator
Implements:
Countable
ArrayAccess
Serializable
53. Looping the Loop with SPL
Iterators
ArrayIterator – Serializable
interface Serializable {
/* Methods */
abstract public serialize ( void ) : string
abstract public unserialize ( string $serialized ) : void
}
54. Looping the Loop with SPL
Iterators
ArrayIterator – Serializable
$userData = ['user1', 'user2', 'user3', 'user4'];
$userList = new UserList($userData);
var_dump(serialize($userList));
55. Looping the Loop with SPL
Iterators
ArrayIterator – Serializable
string(117)
"O:8:"UserList":4:{i:0;i:0;i:1;a:4:{i:0;s:5:"user1";
i:1;s:5:"user2";i:2;s:5:"user3";i:3;s:5:"user4";}i:2;
a:0:{}i:3;N;}"
56. Looping the Loop with SPL
Iterators
ArrayIterator
Implements:
Countable
ArrayAccess
Serializable
SeekableIterator (which entends Iterator)
57. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
SeekableIterator extends Iterator {
/* Methods */
abstract public seek ( int $position ) : void
/* Inherited methods */
abstract public Iterator::current ( void ) : mixed
abstract public Iterator::key ( void ) : scalar
abstract public Iterator::next ( void ) : void
abstract public Iterator::rewind ( void ) : void
abstract public Iterator::valid ( void ) : bool
}
58. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
$userData = ['user1', 'user2', 'user3', 'user4'];
$userList = new UserList($userData);
$userList->seek(2);
while ($userList->valid()) {
echo $userList->current(), PHP_EOL;
$userList->next();
}
59. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
user3
user4
60. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
$userData = ['user1', 'user2', 'user3', 'user4'];
$userList = new UserList($userData);
$userList->seek(2);
foreach($userList as $userName) {
echo $userName, PHP_EOL;
}
61. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
user1
user2
user3
user4
62. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
$userData = ['A' => 'user1', 'B' => 'user2', 'C' => 'user3', 'D' => 'user4'];
$userList = new UserList($userData);
$userList->seek('C');
while ($userList->valid()) {
echo $userList->current(), PHP_EOL;
$userList->next();
}
63. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
PHP7
Warning:
ArrayIterator::seek() expects parameter 1 to be int,
string given
64. Looping the Loop with SPL
Iterators
ArrayIterator – SeekableIterator
PHP8
Fatal error: Uncaught TypeError
ArrayIterator::seek(): Argument #1 ($position) must be
of type int, string given
65. Looping the Loop with SPL
Iterators
ArrayIterator
Implements:
Countable
ArrayAccess
Serializable
SeekableIterator (which entends Iterator)
Also provides methods for sorting
Doesn’t work with array_* functions
75. Looping the Loop with SPL
Iterators
FilterIterator
abstract FilterIterator extends IteratorIterator implements OuterIterator {
/* Methods */
public abstract accept ( void ) : bool
public __construct ( Iterator $iterator )
public current ( void ) : mixed
public getInnerIterator ( void ) : Iterator
public key ( void ) : mixed
public next ( void ) : void
public rewind ( void ) : void
public valid ( void ) : bool
}
76. Looping the Loop with SPL
Iterators
FilterIterator
class ImageFileIterator extends FilterIterator {
// Overwrite the constructor to automagically create an inner iterator
// to iterate the file system when given a path name.
public function __construct(string $path) {
parent::__construct(new FilesystemIterator($path));
}
// Overwrite the accept method to perform a super simple test to
// determine if the files found were images or not.
public function accept(): bool {
$fileObject = $this->getInnerIterator()->current();
return preg_match('/^(?:gif|jpe?g|png)$/i', $fileObject->getExtension());
}
}
77. Looping the Loop with SPL
Iterators
FilterIterator
class ImageFileIterator extends FilterIterator {
// Overwrite the constructor to automagically create an inner iterator
// to iterate the file system when given a path name.
public function __construct(string $path) {
parent::__construct(new FilesystemIterator($path));
}
// Overwrite the accept method to perform a super simple test to
// determine if the files found were images or not.
public function accept(): bool {
return preg_match('/^(?:gif|jpe?g|png)$/i', $this->getExtension());
}
}
78. Looping the Loop with SPL
Iterators
FilterIterator
$path = dirname(__FILE__);
foreach(new ImageFileIterator($path) as $imageObject) {
echo $imageObject->getBaseName(), PHP_EOL;
}
79. Looping the Loop with SPL
Iterators
FilterIterator
anubis.jpg
Carpet.jpg
Crazy Newspaper Headlines.jpg
elephpant.jpg
Pie Chart.jpg
PowerOfBooks.jpg
Struggling-Team.png
TeddyBears.jpg
80. Looping the Loop with SPL
Iterators
Iterator Keys
class ImageFileIterator extends FilterIterator {
...
// Overwrite the key method to return the file extension as the key
public function key(): string {
return $this->getExtension();
}
}
foreach(new ImageFileIterator($path) as $extension => $imageObject) {
echo "{$extension} => {$imageObject->getBaseName()}", PHP_EOL;
}
82. Looping the Loop with SPL
Iterators
Iterator Keys
class ImageFileIterator extends FilterIterator {
...
// Overwrite the key method to return the last modified date/time as the key
public function key(): DateTime {
return DateTime::createFromFormat('U', $this->getMTime());
}
}
foreach(new ImageFileIterator($path) as $date => $imageObject) {
echo "{$date->format('Y-m-d H:i:s')} => {$imageObject->getBaseName()}",
PHP_EOL;
}
98. Looping the Loop with SPL
Iterators
Inner and Outer Iterators
Inner Iterator
Implements Iterator, or extends a class that
implements Iterator
ArrayIterator
SeekableIterator
FilesystemIterator
MultipleIterator
99. Looping the Loop with SPL
Iterators
Inner and Outer Iterators
Outer Iterator
Implements OuterIterator, or extends an
Iterator that already implements
OuterIterator
LimitIterator
InfiniteIterator
FilterIterator
CallbackFilterIterator
100. Looping the Loop with SPL
Iterators
Recursive Iterators
A RecursiveIterator defines methods to
allow iteration over hierarchical data
structures.
Nested Arrays (and Objects if using
ArrayAccess)
Filesystem Directories/Folders
102. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
public function all(): UserCollection {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
$statement->execute();
$users = $statement->fetchAll();
return new UserCollection(
array_map(fn(array $user): User => User::fromArray($user), $users)
);
}
}
103. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
public function all(): Generator {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
$statement->execute();
while ($user = $statement->fetch()) {
yield User::fromArray($user);
}
}
}
104. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
/**
* @return Generator|User[]
*/
public function all(): Generator {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
$statement->execute();
while ($user = $statement->fetch()) {
yield User::fromArray($user);
}
}
}
105. Looping the Loop with SPL
Iterators
IteratorAggregate
IteratorAggregate extends Traversable {
/* Methods */
abstract public getIterator ( void ) : Traversable
}
106. Looping the Loop with SPL
Iterators
class UserCollection implements IteratorAggregate {
private PDOStatement $pdoStatement;
public function __construct(PDOStatement $pdoStatement) {
$this->pdoStatement = $pdoStatement;
}
public function getIterator(): Traversable {
$this->pdoStatement->execute();
while ($user = $this->pdoStatement->fetch()) {
yield User::fromArray($user);
}
}
}
107. Looping the Loop with SPL
Iterators
class UserRepository {
// ...
public function all(): UserCollection {
$userQuery = 'SELECT * FROM `users` ...';
$statement = $this->pdo->prepare($userQuery);
return new UserCollection($statement);
}
}
108. Looping the Loop with SPL
Iterators
Implementing a Fluent Nested Iterator
$weekdayNames = ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'];
$todayOffset = (int) (new DateTime('2020-09-15'))->format('w');
$workdayList = new CallbackFilterIterator(
new LimitIterator(
new InfiniteIterator( new ArrayIterator($weekdayNames) ),
$todayOffset,
14
),
fn($dayName): bool => $dayName !== 'Sat' && $dayName !== 'Sun'
);
foreach($workdayList as $dayOfWeek) {
echo $dayOfWeek, PHP_EOL;
}
109. Looping the Loop with SPL
Iterators
Implementing a Fluent Nested Iterator
class FluentNestedIterator implements IteratorAggregate {
protected iterable $iterator;
public function __construct(iterable $iterator) {
$this->iterator = $iterator;
}
public function with(string $iterable, ...$args): self {
$this->iterator = new $iterable($this->iterator, ...$args);
return $this;
}
public function getIterator() {
return $this->iterator;
}
}
110. Looping the Loop with SPL
Iterators
Implementing a Fluent Nested Iterator
$weekdayNames = ['Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat'];
$todayOffset = (int) (new DateTime('2020-10-08'))->format('w');
$workdayList = (new FluentNestedIterator(new ArrayIterator($weekdayNames)))
->with(InfiniteIterator::class)
->with(LimitIterator::class, $todayOffset, 14)
->with(
CallbackFilterIterator::class,
fn($dayName): bool => $dayName !== 'Sat' && $dayName !== 'Sun'
);
foreach($workdayList as $dayOfWeek) {
echo $dayOfWeek, PHP_EOL;
}
111. Looping the Loop with SPL
Iterators
Implementing a Fluent Nested Iterator
Thurs
Fri
Mon
Tues
Wed
Thurs
Fri
Mon
Tues
Wed
129. Looping the Loop with SPL
Iterators
A Functional Guide to Cat Herding with PHP
Generators
• https://markbakeruk.net/2016/01/19/a-functional-guide-to-cat-herding-with-php-generators/
• https://markbakeruk.net/2020/01/05/filtering-and-mapping-with-spl-iterators/
• https://markbakeruk.net/2019/12/31/parallel-looping-in-php-with-spls-multipleiterator/
130. Looping the Loop with SPL
Iterators
Iterating PHP Iterators
By Cal Evans
https://leanpub.com/iteratingphpiterators
by Joshua Thijssen
https://www.phparch.com/books/mastering-the-spl-library/
131. Who am I?
Mark Baker
Most Recently: Senior Software Engineer
MessageBird BV, Amsterdam
Coordinator and Developer of:
Open Source PHPOffice library
PHPExcel, PHPWord, PHPPowerPoint, PHPProject, PHPVisio
Minor contributor to PHP core (SPL DataStructures)
Other small open source libraries available on github
@Mark_Baker
https://github.com/MarkBaker
http://uk.linkedin.com/pub/mark-baker/b/572/171
http://markbakeruk.net
Looping the
Loop
with
SPL Iterators