Range Over Iterators in PHP

Starting with version 1.23, the language has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example, we had an AllElements method that returned a slice of all elements in the list. With iterators, we can do it better - as shown below.

class ListNode {
    public $val;
    public $next;

    public function __construct($val) {
        $this->val = $val;
        $this->next = null;
    }
}

class ListCollection {
    private $head = null;
    private $tail = null;

    public function push($value) {
        $node = new ListNode($value);
        if ($this->tail === null) {
            $this->head = $node;
            $this->tail = $node;
        } else {
            $this->tail->next = $node;
            $this->tail = $node;
        }
    }

    public function all() {
        return function() {
            $current = $this->head;
            while ($current !== null) {
                yield $current->val;
                $current = $current->next;
            }
        };
    }
}

function genFib() {
    return function() {
        $a = 1;
        $b = 1;
        while (true) {
            yield $a;
            list($a, $b) = [$b, $a + $b];
        }
    };
}

// Usage

$list = new ListCollection();
$list->push(10);
$list->push(13);
$list->push(23);

// Since ListCollection::all returns an iterator, we can use it in a foreach loop.
foreach ($list->all()() as $value) {
    echo $value, "\n";
}

// Collect all values into an array
$allValues = iterator_to_array($list->all()());
echo "all: [" . implode(", ", $allValues) . "]\n";

// Fibonacci sequence
foreach (genFib()() as $n) {
    if ($n >= 10) break;
    echo $n, "\n";
}

All returns an iterator, which in this language is implemented using the yield keyword within a generator function.

The iterator function takes another function (the generator) as parameter, which will yield every element we want to iterate over, and allowing for a potential early termination when required.

Iteration doesn’t require an underlying data structure, and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as the generator keeps yielding values.

10
13
23
all: [10, 13, 23]
1
1
2
3
5
8

Now that we know how to work with iterators and generators in this language, let’s explore more of its advanced features.