hoa/iterator
Hoa\Iterator is a PHP library offering a comprehensive collection of iterator classes and helpers, including filters, maps, limits, aggregates, and recursive traversals, for composing elegant, reusable iteration logic over arrays and other traversable data.
This is not a Laravel package — it is a standalone SPL iterator toolkit (lazy Map, Filter, Limit, Lookahead, Mock, Infinite, plus IteratorIterator/IteratorAggregate bridges). Before writing any code, decide whether Laravel's built-in LazyCollection already covers the job. It usually does; you reach for hoa/iterator only for the few primitives SPL lacks (notably a first-class lookahead/peek iterator and a deterministic Mock sequence).
Install (with eyes open)
composer require hoa/iterator
composer show hoa/iterator # expect 3.x, last release 2017-01-10
composer why hoa/consistency # pulls in Hoa's runtime helper package
The package is archived and unmaintained. Pin the exact version in composer.json, commit the lock file, and treat any transitive hoa/* dependency as a long-term liability. If you cannot accept vendor code that will never receive a PHP-version fix, stop here and use LazyCollection.
Where to look first
The repo is flat and tiny — one class per file at the root (Map.php, Filter.php, Limit.php, Lookahead.php, Mock.php, Infinite.php). Read those six before anything else; each is ~50–120 lines and just decorates an inner Iterator. Confirm constructor argument order in the source of the version you installed, because it changed between major versions.
First use case: streaming a large export lazily
The realistic day-to-day win is transforming rows from a file or DB::cursor() without materialising the whole set.
use Hoa\Iterator\Map;
use Hoa\Iterator\Limit;
$rows = (function () use ($path) {
$h = fopen($path, 'rb');
try {
while (($row = fgetcsv($h)) !== false) {
yield $row;
}
} finally {
fclose($h);
}
})();
$emails = new Map($rows, fn (array $row) => strtolower(trim($row[0] ?? '')));
$page = new Limit($emails, 0, 500);
foreach ($page as $i => $email) {
// one row in memory at a time
}
1. Keep Hoa types out of your public API. Expose Iterator/IteratorAggregate from your own classes and keep the Hoa\Iterator\* construction private. That gives you a one-file migration to LazyCollection or plain generators when the dependency is dropped.
final class CsvRowStream implements IteratorAggregate
{
public function __construct(private string $path) {}
public function getIterator(): Iterator
{
return new Map($this->rawRows(), fn (array $r) => array_combine($this->header, $r));
}
}
2. Bridge Laravel collections both ways. LazyCollection already satisfies IteratorAggregate, so $lazy->getIterator() returns a Generator you can feed into a Hoa decorator. Going back: LazyCollection::make($hoaIterator) or, for finite results, collect(iterator_to_array($it, false)) — always pass false to avoid key collisions.
3. Compose as a decorator stack, outermost-limit first. new Limit(new Map(new Filter($source, $f), $g), 0, 100) means only 100 items ever traverse the filter; reversing the order filters everything first. Order is a performance decision, not a style one.
4. Use lookahead for parsing and tailing. Lookahead lets you peek at the next element without consuming it — the clean fit for tokenizers, log tailers, and "does the next line continue this record" CSV repair, which is awkward with plain generators.
5. Use Mock as a test double. For collaborator code that type-hints Iterator, Mock gives a deterministic, rewindable sequence without hand-rolling an anonymous class. Do not leak it into production paths.
6. Prefer DB::cursor() over wrapping a query builder yourself. Eloquent's cursor() is already a lazy LazyCollection; wrapping it in Map/Filter is the idiomatic integration point, and it sidesteps the package's own database-free design entirely.
7. No service provider required. These are plain value-less classes. If you want container injection, bind your own adapter interfaces (RowStream), never Hoa\Iterator\Map directly — resolving an iterator from the container is almost always a design smell.
Iterator/OuterIterator without typed return declarations, so you will see Deprecated: Return type of ... should either be compatible with ... or #[\ReturnTypeWillChange] flood the logs. You cannot patch vendor code safely. Mitigations: suppress that deprecation in config/logging.php or ErrorException handling, wrap in classes you own and annotate those with #[\ReturnTypeWillChange], or avoid the package on 8.1+.Hoa\Consistency autoloader alongside Composer. In a Laravel app with optimized autoloading (composer dump-autoload -o), verify classes still resolve and check for duplicate/conflicting registration in bootstrap/app.php era boots; do not let it register global state at boot.rewind() after a partial pass throws Exception: Cannot rewind a generator that was already run. Never hand the same instance to two consumers; hand out a factory or a fresh getIterator().Infinite, Repeater, and any unbounded source are fatal with iterator_to_array(), iterator_count(), or collect(). Always pair with Limit and an explicit break; unit-test the bound.finally only runs if it is unwound. Write source generators with try/finally around fclose/unset, as in the example above.key() explicitly if downstream code cares about the original keys — decorators may re-index. iterator_to_array($it) silently overwrites duplicate keys; that is a data-loss bug that looks like "some rows vanished."iterator_count($it) and iterator_to_array($it) consume the iterator — debugging with them changes behaviour. Use foreach with dump([$it->key(), $it->current()]) on a throwaway instance, and step with Xdebug through valid()/next().CallbackFilterIterator, LimitIterator, MultipleIterator, and RecursiveIteratorIterator cover most of Filter/Limit/Aggregate with native support and correct return types. hoa/iterator's genuine differentiators are Lookahead, Mock, and the Buffer-style classes — justify the dependency on one of those specifically.Map/Filter or extend FilterIterator if you need custom behaviour; otherwise implement OuterIterator yourself in a small class rather than inheriting from an archived base.How can I help you explore Laravel packages today?