DEV Community

Cover image for PHP iterators and generators: get started with the `ArrayIterator` and the `FilterIterator` classes
yactouat
yactouat

Posted on • Updated on

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 given 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();
}
Enter fullscreen mode Exit fullscreen mode

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 the accept 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;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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);
Enter fullscreen mode Exit fullscreen mode
  • 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);
Enter fullscreen mode Exit fullscreen mode
  • Let's check that everything works as expected by echoing the elements in our filtered list =>
foreach ($myFilteredList as $item) {
    echo $item.PHP_EOL;
}
Enter fullscreen mode Exit fullscreen mode

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 !

Discussion (0)