Lesson 98 of 100
Iteration15 min5 checksPHP 8.5

Implement a Simple IteratorAggregate

Learn how IteratorAggregate lets an object hand PHP a separate iterator, so it works in foreach and can be collected into an array.

01 · THE IDEAREAD → TRY → LEARN

Why IteratorAggregate?

An array works in foreach automatically. An object of your own class does not iterate in a useful way unless it says how. PHP's Traversable is the marker for "this can be looped over", and you do not implement it directly. You implement either Iterator or IteratorAggregate.

Iterator makes the object track its own position and requires five methods. IteratorAggregate is simpler. It requires one method, getIterator(): Traversable, which returns some other traversable object that does the real walking. The object is an aggregate: it holds data and delegates iteration.

The contract

public function getIterator(): Traversable

The method takes no arguments. It must return a Traversable, such as an ArrayIterator or a Generator. foreach calls it once when the loop starts and iterates over whatever it returns.

A first worked example

The class below stores an array and exposes it through an ArrayIterator. The array's keys come through the loop unchanged.

Example
<?php
class Basket implements IteratorAggregate
{
    public function __construct(private array $items)
    {
    }

    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->items);
    }
}

$basket = new Basket(['apple' => 3, 'pear' => 5]);

foreach ($basket as $name => $quantity) {
    echo $name . '=' . $quantity . PHP_EOL;
}
Output
apple=3
pear=5

The Basket object knows nothing about positions or cursors. The ArrayIterator it returns carries that state, so each loop gets a fresh iterator.

Collecting values

Once an object is traversable, iterator_to_array() can turn it into an array. By default it keeps keys. Pass false as the second argument to get a plain list.

Example
<?php
class Basket implements IteratorAggregate
{
    public function __construct(private array $items)
    {
    }

    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->items);
    }
}

$basket = new Basket(['apple' => 3, 'pear' => 5]);

echo json_encode(iterator_to_array($basket), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
echo json_encode(iterator_to_array($basket, false), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
Output
{"apple":3,"pear":5}
[3,5]

This distinction matters when keys can repeat. If an iterator yields the same key twice, the keyed form overwrites earlier values. The false form keeps every value.

Using a generator

getIterator() may be a generator method. The declared return type can stay Traversable, because a generator is one. Every call creates a new generator, so the object can be looped repeatedly.

Example
<?php
class Countdown implements IteratorAggregate
{
    public function __construct(private int $start)
    {
    }

    public function getIterator(): Traversable
    {
        for ($i = $this->start; $i >= 1; $i--) {
            yield $i;
        }
    }
}

$countdown = new Countdown(3);

echo json_encode([
    ...iterator_to_array($countdown, false),
    ...iterator_to_array($countdown, false),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
Output
[3,2,1,3,2,1]

A bare generator can only be consumed once. Wrapping it in an aggregate makes the collection reusable.

Common mistakes

  • Returning an array from getIterator(). It must return a Traversable, so wrap the array in ArrayIterator.
  • Forgetting implements IteratorAggregate. A method named getIterator alone does not make the object work in foreach.
  • Filtering with truthiness. Values such as 0, false, null and '' are valid data and should survive collection.
  • Assuming keys are a list. Collect with $values[] = $value or iterator_to_array($it, false) when you want positions 0..n.

Exercise contract

Write collectValues(array $items): array. Inside the function, create a small local object that implements IteratorAggregate and wraps $items. Its getIterator() returns an ArrayIterator. Loop over that object with foreach and return the values in their original order as a zero-indexed list. Discard the original keys and keep every value, including falsy ones and nested arrays.

For example, ['x' => 1, 'y' => 2] becomes [1, 2], and [] stays []. The tests check the returned JSON-compatible value, not printed text.

Recap

IteratorAggregate separates the data holder from the iteration state. Implement getIterator(): Traversable, return an ArrayIterator or generator, and the object works in foreach and with iterator_to_array().

Before you start

Functions · Arrays · Types

You’ll practice
  • Expose a local iterable and return its collected values
  • Implement and test a deterministic PHP 8.4 function
Your task

Make collectValues() pass all 5 checks. Run your code as often as you like, then check your answer.

exercise.phpSaved in this browserPHP 8.5
CONSOLE & CHECKS● Ready

Ready when you are

Your output will appear here.
Make it work. Then make it pass.

PHP loads on your first run. Each check starts fresh. Your code stays in this browser; clearing site data removes your progress. A check or the task looks wrong? Report a problem with this exercise.