Quick Links

PHP allows you to create iterable objects. These can be used within loops instead of scalar arrays. Iterables are commonly used as object collections. They allow you to typehint that object while retaining support for looping.

Simple Iteration

To iterate over an array in PHP, you use a

        foreach
    

loop:

foreach (["1", "2", "3"] as $i) {
    

echo ($i . " ");

}

This example would emit

        1 2 3
    

.

You can also iterate over an object:

$cls = new StdClass();
    

$cls -> foo = "bar";

foreach ($cls as $i) {

echo $i;

}

This example would emit

        bar
    

.

The Collection Problem

For basic classes with public properties, a plain

        foreach
    

works well. Let's now consider another class:

class UserCollection {
    

protected array $items = [];

public function add(UserDomain $user) : void {

$this -> items[] = $user;

}

public function containsAnAdmin() : bool {

return (count(array_filter(

$this -> items,

fn (UserDomain $i) : bool => $i -> isAdmin()

)) > 0);

}

}

This class represents a collection of

        UserDomain
    

instances. As PHP doesn't support typed arrays, classes like this are necessary when you want to typehint an array which can only hold one type of value. Collections also help you create utility methods, like

        containsWithAdmin
    

, that facilitate natural interaction with array items.

Unfortunately, trying to iterate over this collection won't give the desired results. Ideally, iterating should operate on the

        $items
    

array, not the class itself.

Implementing Iterator

Natural iteration can be added using the

        Iterator
    

interface. By implementing

        Iterator
    

, you can define PHP's behaviour when instances of your class are used with

        foreach
    

.

        Iterator
    

has five methods which you'll need to implement:

  •         current() : mixed
        
    - Get the item at the current position in the iteration.
  •         key() : scalar
        
    - Get the key at the current position in the iteration.
  •         next() : void
        
    - Move to the next position in the iteration.
  •         rewind() : void
        
    - Rewind the position to the start of the iteration.
  •         valid() : bool
        
    - Get whether the current position has a value.

These methods might be confusing at first. Implementing them is straightforward though - you're specifying what to do at each stage of a

        foreach
    

execution.

Each time your object is used with

        foreach
    

,

        rewind()
    

will be called. The

        valid()
    

method is called next, informing PHP whether there's a value at the current position. If there is,

        current()
    

and

        key()
    

are called to get the value and key at that position. Finally, the

        next()
    

method is called to advance the position pointer. The loop returns to calling

        valid()
    

to see if there's another item available.

Here's a typical implementation of

        Iterator
    

:

class DemoIterator {
    

protected int $position = 0;

protected array $items = ["cloud", "savvy"];

public function rewind() : void {

echo "Rewinding";

$this -> position = 0;

}

public function current() : string {

echo "Current";

return $this -> items[$this -> position];

}

public function key() : int {

echo "Key";

return $this -> position;

}

public function next() : void {

echo "Next";

++$this -> position;

}

public function valid() : void {

echo "Valid";

return isset($this -> items[$this -> position]);

}

}

Here's what would happen when iterating

        DemoIterator
    

:

$i = new DemoIterator();
    

foreach ($i as $key => $value) {

echo "$key $value";

}

// EMITS:

//

// Rewind

// Valid Current Key

// 0 cloud

// Next

//

// Valid Current Key

// 1 savvy

// Next

//

// Valid

Your iterator needs to maintain a record of the loop position, check whether there's an element at the current loop position (via

        valid()
    

) and return the key and value at the current position.

PHP won't try to access the key or value when the loop position is invalid. Returning

        false
    

from

        valid()
    

immediately terminates the

        foreach
    

loop. Typically, this will be when you get to the end of the array.

IteratorAggregate

Writing iterators quickly gets repetitive. Most follow the exact recipe shown above.

        IteratorAggregate
    

is an interface which helps you quickly create iterable objects.

Implement the

        getIterator()
    

method and return a

        Traversable
    

(the base interface of

        Iterator
    

). It will be used as the iterator when your object is used with

        foreach
    

. This is usually the easiest way to add iteration to a collection class:

class UserCollection implements IteratorAggregate {
    

protected array $items = [];

public function add(UserDomain $User) : void {

$this -> items[] = $user;

}

public function getIterator() : Traversable {

return new ArrayIterator($this -> items);

}

}

$users = new UserCollection();

$users -> add(new UserDomain("James"));

$users -> add(new UserDomain("Demo"));

foreach ($users as $user) {

echo $user -> Name;

}

When working with collections, three lines of code are usually all you need to setup iteration! An

        ArrayIterator
    

is returned as the

        Traversable
    

. This is a class which automatically creates an

        Iterator
    

out of an array. You can now iterate over the logical values within your object, instead of the object's direct properties.

Using Prebuilt PHP Iterators

You'll need to write your own iterators if you have complex logic. It's rarely necessary to start from scratch as PHP ships with several advanced iterators provided by SPL.

The built-in classes include

        DirectoryIterator
    

,

        FilesystemIterator
    

,

        GlobIterator
    

and various recursive iterators. Here's some of the most useful generic iterators.

LimitIterator

The

        LimitIterator
    

lets you iterate over a subset of an array. You don't need to splice the array first or manually keep track of position inside your

        foreach
    

.

$arr = new ArrayIterator(["a", "b", "c", "d"]);
    

foreach (new LimitIterator($arr, 0, 2) as $val) {

echo $val;

}

This example would emit

        a b
    

. Note that

        LimitIterator
    

accepts another

        Iterator
    

, not an array. The example uses

        ArrayIterator
    

, the built-in class which constructs an

        Iterator
    

from an array.

InfiniteIterator

        InfiniteIterator
    

never terminates the loop, so you'll need to

        break
    

from it manually. Otherwise, the iterator automatically wraps back to the start of the array when it reaches the end.

This iterator is particularly useful when working with time-based values. Here's an easy way of building a three-year calendar, which also uses the

        LimitIterator
    

described above:

$months = [
    

"Jan", "Feb", "Mar", "Apr", "May", "Jun",

"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"

];

$infinite = new InfiniteIterator(new ArrayIterator($months));

foreach (new LimitIterator($infinite, 0, 36) as $month) {

echo $month;

}

This emits three years' worth of months.

FilterIterator

        FilterIterator
    

is an abstract class which you must extend. Implement the

        accept
    

method to filter out unwanted values which should be skipped during iteration.

class DemoFilterIterator extends FilterIterator {
    

public function __construct() {

parent::__construct(new ArrayIterator([1, 10, 4, 6, 3]));

}

public function accept() {

return ($this -> getInnerIterator() -> current() < 5);

}

}

$demo = new DemoFilterIterator();

foreach ($demo as $val) {

echo $val;

}

This example would emit

        1 4 3
    

.

The array values which are higher than 5 are filtered out and do not appear in the

        foreach
    

.

The "iterable" type

Sometimes you might write a generic function that uses a

        foreach
    

loop but knows nothing about the values it'll be iterating over. One example is an abstract error handler which simply dumps the values it receives.

You can typehint

        iterable
    

in these scenarios. This is a pseudo-type which will accept either an

        array
    

or any object implementing

        Traversable
    

:

function handleBadValues(iterable $values) : void {
    

foreach ($values as $value) {

var_dump($value);

}

}

The

        iterable
    

type is so vague that you should think carefully before using it. Nonetheless, it can be useful as a last-resort type if you need to guarantee an input value will work with

        foreach
    

.

Conclusion

Making use of iterators helps you write clean code that's more modular. You can move methods that act on arrays of objects into dedicated collection classes. These can then be typehinted individually while remaining fully compatible with

        foreach
    

.

Most of the time, adding iteration support can be achieved by implementing

        IteratorAggregate
    

and returning an

        ArrayIterator
    

configured with the items in your collection. PHP's other iterator types, which tend to go unused by developers, can greatly simplify more specific loops. They offer complex logical behaviours without requiring manual pointer tracking in your

        foreach
    

statement.