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.
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;
}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.
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;{"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.
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;[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 aTraversable, so wrap the array inArrayIterator. - Forgetting
implements IteratorAggregate. A method namedgetIteratoralone does not make the object work inforeach. - Filtering with truthiness. Values such as
0,false,nulland''are valid data and should survive collection. - Assuming keys are a list. Collect with
$values[] = $valueoriterator_to_array($it, false)when you want positions0..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().
Functions · Arrays · Types
- Expose a local iterable and return its collected values
- Implement and test a deterministic PHP 8.4 function