Library / SDK
nikic/iter avatar
nikic/iter

iter: lazy iteration primitives for PHP, built on generators

Iteration primitives using generators

1,146 stars71 forksPHPNOASSERTION

At a glance

What is it?
iter is a PHP library that reimplements map, filter and a few dozen other iteration helpers on top of generators, and its distinguishing feature is that every function accepts any iterable, including aggregates, which array_map and the SPL iterators do not. The price of generators is single pass, and the library ships a rewindable variant for the cases where you need a second one.
Who is it for?
iter fits a PHP codebase that mixes arrays, traversables, iterators and aggregates in the same pipeline and would otherwise write a defensive adapter for each combination, and that wants the operations to be lazy so a large input is not materialised. It does not fit a codebase that needs the same iterator twice, unless you are willing to use the rewindable variants, which exist for exactly that and are documented separately.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 30 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

map and filter, written with generators

iter is a library of iteration primitives like map and filter implemented using generators, and the README is upfront about the double purpose. To a large part it serves as a repository for small examples of generator usage, but of course the functions are also practically quite useful.

The usage example is the shortest possible demonstration of what the library feels like in code.

php
<?php

use iter\func;

require 'path/to/vendor/autoload.php';

$nums = iter\range(1, 10);
$numsTimesTen = iter\map(func\operator('*', 10), $nums);
// => iter(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)

Everything is in the iter namespace, the operator helper builds a callable from an operator and a value, and the result of a map is itself an iterable, so it composes with the next call rather than being materialised into an array.

The operations are lazy, which the README states as a property rather than a feature. A map over a million rows produces a generator that does the work when it is consumed, so the memory profile is one row at a time rather than an array in memory.

Installation is a single Composer command.

bash
composer require nikic/iter

The project is written in PHP, the repository is nikic's, and the last push was on 2026-09-23. The most recent release is v2.4.1 from 2024-06-11, after v2.4.0 in December 2023 and v2.3.0 in July 2023, and the licence is a custom one that GitHub cannot classify, in the LICENSE file at the root.

Any iterable, including the ones other libraries reject

The one technical claim in the README that sets this library apart from a generic helper collection is about what it accepts.

All the functions accept arbitrary iterables, meaning arrays, traversables, iterators and aggregates, which the README says makes it quite different from functions like array_map, which only accept arrays, and from the SPL iterators, which usually only accept iterators and not even aggregates.

That is a real difference in practice. An aggregate is an object that holds a collection and can be traversed, and the SPL's own iterator functions are narrow enough that working with one usually means converting it first. In a codebase that receives data from a database layer, an ORM, a JSON decoder and a config parser, the four sources often arrive in four different shapes, and a library that takes all of them removes a category of defensive code.

The corollary is that the functions are not just for collections. isIterable, toIter and the keys and values helpers exist to bridge the gap between a plain array and the iterator world, so you can convert once at the boundary and then work in one vocabulary.

The laziness and the wide input are related decisions. A function that accepts anything iterable and returns a lazy generator is a pipeline component rather than a collection utility, and the API reads that way, with functions like tap that exist purely to let you observe a stream without changing it.

What is in the box, grouped by what it does

The README lists the function signatures as an overview and points to src/iter.php for the documentation and usage examples of each one, and the list is more useful as a map of the API than as a reference.

The transformation family covers map, mapWithKeys, mapKeys, flatMap and reindex. The filtering and pairing family covers filter, enumerate, toPairs and fromPairs. Folding is reductions, with the matching eager consumer reduce. Combining is zip, zipKeyValue, chain and product. Slicing is slice, take, drop, takeWhile and dropWhile. Shape manipulation is keys, values, flatten, flip, chunk and chunkWithKeys, and tap sits alongside them as an observer.

Then there is a separate group of generators that produce input rather than transform it: range, repeat and split, where split takes a separator and a string. The eager consumers at the bottom are the ones that collapse a stream into a value: reduce, any, all, search, apply, join, count, isEmpty, recurse, toArray, toArrayWithKeys and isIterable.

Two details are worth noticing. The eager functions are clearly separated from the lazy ones, so the point at which you materialise is visible in the code rather than buried in a call. And the variadic functions, zip, chain and product, accept any number of iterables, which is where the wide input type pays off again, because you can zip a generator, an array and an aggregate without converting any of them.

Rewindable generators, and the pass you get only once

There is a constraint that comes with the implementation, and the README states it plainly: as the functionality is implemented using generators, the resulting iterators are by default not rewindable.

That is the classic generator trade-off. A generator has state, it has been started, and it has run out, and there is no going back to the beginning without recreating it. A lazy pipeline that you want to walk twice therefore needs a different object.

The library provides that, and documents it separately in src/iter.rewindable.php, with two main functions.

php
<?php

use iter\func;

require 'path/to/vendor/autoload.php';

/* Create a rewindable map function which can be used multiple times */
$rewindableMap = iter\makeRewindable('iter\\map');
$res = $rewindableMap(func\operator('*', 3), [1, 2, 3]);

/* Do a rewindable call to map, just once */
$res = iter\callRewindable('iter\\map', func\operator('*', 3), [1, 2, 3]);

The difference between the two is the caching. makeRewindable returns a function you can call repeatedly, and each call replays the same results, while callRewindable performs a single rewindable call.

For the iter iterators themselves, rewindable variants are provided directly with an iter\rewindable prefix, so the ordinary map has a rewindable\map counterpart and the others follow the same pattern. That is a tidy way to solve the problem, because the alternative is every caller having to remember to buffer results before looping twice.

The toolchain, and what a quiet release history means

The repository is small and well instrumented for its size. There is a src/ directory and a test/ directory, a composer.json, and three separate static analysis and test configuration files: phpstan.neon.dist, psalm.xml and phpunit.xml.dist. Running both PHPStan and Psalm is unusual and slightly redundant, and it is a good sign about how much the author cared about a library that other people call from unfamiliar code.

The release history is the other thing to look at. The tags are v2.3.0 in July 2023, v2.4.0 in December 2023 and v2.4.1 in June 2024, and the repository was last pushed to on 2026-09-23. So the code is being touched more recently than the last release, which for a library whose API surface is a set of function signatures is a reasonable place to be, but it does mean there is no tagged version that matches the current state of the source.

For an adopter, that has one practical consequence. If you want a versioned dependency, you are pinning 2.4.1, and the fixes or improvements that landed after that are only available from the repository. If you are comfortable tracking a branch, the newer work is there, and the risk of a breaking change in a library of this shape is lower than it would be elsewhere, because the functions are small and their behaviour is documented in one file.

The licence is worth a look before you depend on it, because the file is a custom one and the project is filed under a licence GitHub cannot classify, which is different from the MIT and Apache texts most PHP libraries use.

Editorial conclusion

iter fits a PHP codebase that mixes arrays, traversables, iterators and aggregates in the same pipeline and would otherwise write a defensive adapter for each combination, and that wants the operations to be lazy so a large input is not materialised. It does not fit a codebase that needs the same iterator twice, unless you are willing to use the rewindable variants, which exist for exactly that and are documented separately. Verify three things before you adopt it: that your PHP version supports the generator features the library relies on, since it is a modern library with a modern runtime expectation, that the laziness does not surprise your error handling, because a failing element may only be reached when the generator is consumed rather than when it is created, and that the licence in the repository suits you, since the project is filed under a custom licence rather than a standard permissive one.

Frequently asked questions

What is different about iter compared with array_map and the SPL iterators?

Every function accepts arbitrary iterables, meaning arrays, traversables, iterators and aggregates, where array_map only accepts arrays and the SPL iterators usually only accept iterators and not even aggregates. The operations are lazy, so a map produces a generator that does its work as it is consumed.

How do I install iter?

With Composer, by running composer require nikic/iter. The functions live in the iter namespace, and the operator helper builds a callable from an operator and a value, so a typical pipeline looks like iter\map(func\operator('*', 10), iter\range(1, 10)).

Why can I not loop over an iter result twice?

Because the implementation uses generators, and the resulting iterators are by default not rewindable. The library provides rewindable variants: iter\makeRewindable returns a function that can be called repeatedly, iter\callRewindable performs a single rewindable call, and the iter iterators have direct rewindable counterparts under the iter\rewindable prefix.

Which functions in iter are eager rather than lazy?

The ones that collapse a stream into a value: reduce, any, all, search, apply, join, count, isEmpty, recurse, toArray, toArrayWithKeys and isIterable. The rest, including map, filter, zip, chain, slice, chunk and the generators range, repeat and split, return iterables you consume as you go.

What are the current versions of iter and how is it tested?

The recent releases are v2.3.0 from 2023-07-29, v2.4.0 from 2023-12-10 and v2.4.1 from 2024-06-11, and the repository was last pushed to on 2026-09-23. The project ships PHPUnit configuration alongside PHPStan and Psalm configuration, so a library of this size is checked by three separate tools.

Official sources

  1. Issues
  2. nikic/iter on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nikic-iter.svg)](https://hysenlabs.com/projects/nikic-iter)

Was this useful?