Foundations and Extension Points
Most users only instantiate the concrete iterators documented elsewhere. However, this package also exposes a small set of base abstractions that make custom iterator code easier to write and count correctly.
Foundation classes
| Class | Use it when... | Notes |
|---|---|---|
IterableIterator
|
you want to accept any iterable and normalize it to an Iterator
|
arrays become ArrayIterator
automatically; existing Traversable
objects are wrapped as needed |
CountableIterator
|
you are implementing the full iterator lifecycle yourself | extends Iterator
plus Countable
semantics |
CountableIteratorAggregate
|
your object can expose iteration through getIterator()
|
usually the simplest base class for custom iterator aggregates |
CountableIteratorIterator
|
you want to decorate an existing iterator and tweak its behavior | ideal for wrappers that transform the current value or key |
CountableFilterIterator
|
you want to implement accept()
-based filtering |
useful for filesystem-style filters or rule-based filtering wrappers |
How counting works
The countable base classes share a consistent goal: calling count()
should
reflect the visible sequence without forcing every consumer to materialize the
iterator manually.
In practice:
- if the wrapped iterator already implements
Countable, that implementation is reused; - otherwise the package counts by iterating over a clone when possible;
- if cloning is not possible, the wrapper counts by traversing a safe adapter.
This behavior is implemented by CountableIteratorIteratorTrait
. You normally
do not need to interact with the trait directly, but it explains why the base
classes exist and why they are worth reusing.
Which base class should you extend?
- Extend
CountableIteratorAggregatewhen your class can simplyyieldvalues or return another traversable object. - Extend
CountableIteratorIteratorwhen your class mostly delegates to an inner iterator but customizescurrent(),key(),next(), orrewind(). - Extend
CountableFilterIteratorwhen a booleanaccept()rule is the main customization point. - Extend
CountableIteratoronly when you truly need full control over iterator state and navigation.
Minimal custom aggregate
If your logic can be expressed as a generator, CountableIteratorAggregate
is
usually the least error-prone option:
use FastForward\Iterator\CountableIteratorAggregate;
final class EvenNumbers extends CountableIteratorAggregate
{
public function __construct(private readonly iterable $values)
{
}
public function getIterator(): Traversable
{
foreach ($this->values as $value) {
if ($value % 2 === 0) {
yield $value;
}
}
}
}
Minimal filter wrapper
Use CountableFilterIterator
when your iterator already fits the SPL filter
pattern:
use FastForward\Iterator\CountableFilterIterator;
final class PositiveOnlyIterator extends CountableFilterIterator
{
public function accept(): bool
{
return $this->current() > 0;
}
}