20
PHP iterators and generators: get started with the `ArrayIterator` and the `FilterIterator` classes
There are other ways to loop over an array of objects in plain PHP to perform data transformation and/or filtering than using the built-in array functions provided by the language.
Although these functions are numerous and will probably help you achieve the operations you had in mind while looping through a list of elements, they are limited by the fact that PHP arrays are not typed. That means you'll almost every time have to do some kind of type checking before proceeding with the logic of your code. This can lead to very verbose and, let's face it, not very maintainable and readable code.
There is a way to gain in readability and efficiency in your foreach
loops that go over a collection of objects: iterators.
The Iterator
interface allows you to define a custom PHP behavior when props of a class instance are iterated over at every stage of a foreach
loop.
Implementing that interface on a class will result in an internal iterator, as the iteration logic is implemented by methods of the class itself.
When using this interface, you'll need to implement:
- current()
, which gets the currently iterated item
- key()
, which gets the currently iterated item key
- next()
, which is used to move to the next position in the iteration
- rewind()
, which rewinds the position to the start of the iteration
- valid()
, which tells you if the current position has a value
Oftentimes, you won't need to implement the Iterator
interface yourself for common day-to-day tasks that are not too business logic specific (for instance, iterating over files in a directory). For this kind of generic tasks, PHP provides predefined iterators that will make your life easier.
In this post, we will consider two PHP built-in iterators:
With ArrayIterator
, you can easily unset or modify values and keys when iterating over arrays and objects, as in =>
// we assume that instances 1 to 4 were created earlier
$myList = new \ArrayIterator();
$myList->append($instance1);
$myList->append($instance2);
$myList->append($instance3);
$myList->append($instance4);
echo $myList->count(); // gives you the size of your list
while($myList->valid()) { // you'll recognize here one of the implemented methods from `Iterator` interface
$instanceItem = $myList->current();
// do something with instance keys/values here
$myList->next();
}
While this is pretty neat, ArrayIterator
shows more capabilities when used with other iterators, like the FilterIterator
.
The FilterIterator
abstract class allows to create filter classes that can be then used in foreach
loops to conveniently retrieve values based on a condition during an iteration; this condition is defined in the implemented accept
method of the class that extends this iterator (cf https://www.php.net/manual/en/class.filteriterator.php).
See example below =>
Let's break this down:
- we want to create an example filter class that will get only numbers that are multiples of a dynamic int variable in an
ArrayIterator
list; here we are using a simple list of numbers, but it can of course be an array of more complex objects on which you need to perform some operations - this filter class extends our PHP built-in
FilterIterator
- it has a
$_filter
member that will be the variable that we will check against in our filtering - it calls the parent
FilterIterator
constructor and it implements theaccept
method as required
class IsMultipleOfFilter extends \FilterIterator {
private int $_filter;
public function __construct(\Iterator $iterator, int $filter) {
parent::__construct($iterator);
$this->_filter = $filter;
}
public function accept(): bool {
return $this->current() % $this->_filter === 0;
}
}
- Now that we have our filter ready, let's create a list (of numbers in our case) that is to be filtered =>
$myList = new \ArrayIterator();
$myList->append(1);
$myList->append(2);
$myList->append(3);
$myList->append(4);
$myList->append(5);
$myList->append(6);
$myList->append(7);
$myList->append(8);
$myList->append(9);
$myList->append(10);
- Then, let's create an instance of our filter, this will result in a filtered list, by passing in our
ArrayIterator
list and the filter to get only even numbers, for instance (the number 2) =>
$myFilteredList = new IsMultipleOfFilter($myList, 2);
- Let's check that everything works as expected by echoing the elements in our filtered list =>
foreach ($myFilteredList as $item) {
echo $item.PHP_EOL;
}
That's it ! you gained new powerful iteration options just by understanding this simple use-case; you can now apply this to lists that hold more complex values, but the principle will remain the same.
Iterators and generators can be intimidating at first, so I hope this example will allow you to gain more confidence in using these constructs.
I will post more content about this in a dedicated series, until then, have a nice holiday !
20