Iterators & IteratorAggregate
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches' (d5). PHPStan can detect some iterator misuse patterns like type mismatches, but common mistakes like forgetting rewind() or using Iterator instead of simpler IteratorAggregate are not caught by default static analysis — requires careful code review or runtime testing to notice iterator exhaustion issues.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates switching from full Iterator implementation to IteratorAggregate with a generator is a localized refactor within one class. Fixing rewind() issues or switching to generators touches the class implementing the iterator but doesn't spread across the codebase.
Closest to 'localised tax' (b3). Iterator implementations are typically confined to specific classes wrapping data sources. The choice applies broadly (web, cli, queue-worker) but the structural impact is localized — a poorly implemented iterator affects code consuming that specific class, not system-wide architecture. It doesn't define the system's shape.
Closest to 'notable trap' (t5). The misconception field explicitly states developers wrongly believe iterators are 'only useful for custom data structures' when they're valuable for memory-efficient streaming. Common mistakes like forgetting rewind() causing silent exhaustion after first use, or not knowing generators auto-implement Iterator, are documented gotchas that most PHP devs eventually learn but initially trip over.
Also Known As
TL;DR
Explanation
PHP's Iterator interface requires five methods: current(), key(), next(), rewind(), valid(). IteratorAggregate requires just getIterator(), which returns an Iterator or Traversable — simpler for delegation. Custom iterators enable foreach over database result sets (fetching row by row), API paginated responses, filesystem trees, and infinite sequences — all with O(1) memory. PHP's SPL provides useful built-in iterators: DirectoryIterator, FilesystemIterator, RecursiveIteratorIterator, ArrayIterator, and LimitIterator. Generators (yield) are often a simpler alternative to manual Iterator implementation.
Common Misconception
Why It Matters
Common Mistakes
- Implementing Iterator but forgetting rewind() — the iterator works once then is silently exhausted.
- Not implementing IteratorAggregate when you just need to wrap an existing array or Traversable — it's simpler.
- Returning false from current() on an empty iterator instead of returning null — both work but false is less predictable.
- Not using generators (yield) for lazy sequences — they automatically implement the Iterator protocol.
Code Examples
// Broken Iterator — missing rewind:
class NumberRange implements Iterator {
private int $current = 0;
public function __construct(private int $start, private int $end) {
$this->current = $start;
}
public function current(): int { return $this->current; }
public function key(): int { return $this->current - $this->start; }
public function next(): void { $this->current++; }
public function valid(): bool { return $this->current <= $this->end; }
// Missing: public function rewind(): void { $this->current = $this->start; }
}
// Implement Iterator to make a class foreach-able
class NumberRange implements Iterator {
private int $current;
public function __construct(private int $start, private int $end) {
$this->current = $start;
}
public function current(): int { return $this->current; }
public function key(): int { return $this->current - $this->start; }
public function next(): void { $this->current++; }
public function rewind(): void { $this->current = $this->start; }
public function valid(): bool { return $this->current <= $this->end; }
}
foreach (new NumberRange(1, 3) as $i) { echo $i; } // 1 2 3
// IteratorAggregate — simpler approach
class UserCollection implements IteratorAggregate {
public function getIterator(): ArrayIterator {
return new ArrayIterator($this->users);
}
}