Reading — step 1 of 6
Learn
Implement these interfaces and your custom objects gain foreach / [] syntax — the same as built-in arrays.
Iterator interface
Five methods. Implement them and foreach knows how to walk your object:
class Range implements \Iterator {
private int $pos;
public function __construct(
private int $start,
private int $end,
) { $this->pos = $start; }
public function current(): mixed { return $this->pos; }
public function key(): mixed { return $this->pos - $this->start; }
public function next(): void { $this->pos++; }
public function rewind(): void { $this->pos = $this->start; }
public function valid(): bool { return $this->pos < $this->end; }
}
foreach (new Range(5, 10) as $key => $value) {
echo "$key => $value\n";
}
// 0 => 5
// 1 => 6 ... 4 => 9
The protocol:
rewind()— reset to the beginningvalid()— is there a current element?current()/key()— the element and its keynext()— advance- back to step 2
In practice, use \Generator (the previous lesson) — it gives you an Iterator with one yielded value per iteration.
IteratorAggregate — the easy way
For most cases, implement the simpler IteratorAggregate:
class UserCollection implements \IteratorAggregate {
public function __construct(private array $users) {}
public function getIterator(): \Generator {
foreach ($this->users as $u) yield $u;
}
}
Return anything that's already iterable — including a generator. Much less code than the 5-method Iterator protocol.
ArrayAccess — bracket syntax
Make $obj[$key] work:
class Config implements \ArrayAccess {
public function __construct(private array $data = []) {}
public function offsetExists(mixed $key): bool {
return isset($this->data[$key]);
}
public function offsetGet(mixed $key): mixed {
return $this->data[$key] ?? null;
}
public function offsetSet(mixed $key, mixed $value): void {
if ($key === null) $this->data[] = $value;
else $this->data[$key] = $value;
}
public function offsetUnset(mixed $key): void {
unset($this->data[$key]);
}
}
$c = new Config(['theme' => 'dark']);
echo $c['theme']; // dark — offsetGet
$c['lang'] = 'en'; // offsetSet
isset($c['theme']); // offsetExists
unset($c['lang']); // offsetUnset
Countable — count() support
class Cart implements \Countable {
public function __construct(private array $items = []) {}
public function count(): int { return count($this->items); }
}
echo count(new Cart(['a', 'b', 'c'])); // 3
Combining them
A real collection class often implements all three: IteratorAggregate, ArrayAccess, Countable. Then it walks like an array, indexes like an array, counts like an array.
Common mistakes
- Implementing Iterator from scratch when IteratorAggregate + a generator would be simpler.
- Forgetting
rewind()must reset state —foreachrewinds at the start. If state isn't reset, the secondforeachsees nothing. - ArrayAccess offsetSet receiving null key — that's
$obj[] = $value— append. Handle it. - Mutating during iteration — same gotcha as arrays. Snapshot if you need to mutate.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…