dajoha/php-iter

A bunch of functional-like iterators.

0.1.5 2024-11-05 08:58 UTC

This package is not auto-updated.

Last update: 2025-06-17 11:00:52 UTC


README

Provides rust-like iterators for php.

Warning: This package is just a proof of concept, but should not be used in a production environment.

Features

  • Lazy iterators;
  • Chainable methods;
  • Carefully designed, coherent API.

Pros

  • Greatly improves code readability.

Cons

  • Has a big runtime cost compared to a classical for loop (something like 15x slower!). But for small loops (less than 1000 elements), this runtime cost may be acceptable.

Installing

In your php project, run

composer require dajoha/php-iter

Basic usage

$numbers = [1, 30, 50, 123, 3, 5, 100, 3780];

$output = iter($numbers)          // Convert the input array to an `IteratorInterface`
    ->filter(fn($n) => $n > 100)  // Keep only numbers which are > 100
    ->map(fn($n) => "Number $n")  // Map each remaining number to a formatted string
    ->join("\n");                 // Reduce the iterator to a single string

echo "$output\n";

Output:

Number 123
Number 3780

More examples

See the directory /examples in this repository for more usage examples.

API overview

The API offers a central interface: IteratorInterface, which extends the native php Iterator but gives many more abilities to it.

There are two kinds of native classes which implement IteratorInterface:

  • Generators, which create an IteratorInterface from any input source (like simple arrays, or anything which implements native php Iterator);
  • Modifiers, which transform a given IteratorInterface into another one.

Once an iterator has been created, it has to be "consumed" in a way or another, in order to give a useful result (the most basic way is to call the native php method Iterator::next()). In this doc, this is called reducing an iterator. Some methods are provided in IteratorInterface, in order to handle very common reducing operations, like converting to an array (toValues()), making the sum of numbers (sum())... All these reducing methods are implemented in AbstractIterator, which every class in this package is based on.

Generators

Generators are IteratorInterfaces which can be constructed from any kind of input parameters.

For example, a generator could be a class which provides:

  • A simple list of concrete values like [78, 'foo', 'bar'];
  • A list of famous musicians, by giving a certain time period as an input;
  • The infinite list of primary numbers.

Modifiers

A modifier is an IteratorInterface which takes an iterable as first input, and transforms the result of the inner iterable when it is consumed.

Each native modifier class has a dedicated method in IteratorInterface which allows to chain it with other modifiers (or with a final reducer method).

For example:

  • the Map modifier iterator transforms each value of its inner iterator.
  • the Filter modifier iterator only provides values which fill the given condition.

Reducers

Reducers are methods (typically implemented inside AbstractIterator) which transform the given iterator to a final value, by consuming elements of their iterator (not necessarily all the elements, even if it's often the case).

For example, AbstractIterator::sum() is a reducer method which consumes all the iterator elements (by assuming they are numbers), and which returns the whole sum of those elements.

Summary of the native generators

The Iter generator

The Iter class is the most basic generator: it simply wraps the given iterable, which can be either:

  • A php array (by using internally ArrayIterator);
  • A php Iterator;
  • A php IteratorAggregate (by trying to retrieve the inner iterator);
  • A php callable (by wrapping it into a Func generator).

The iter() global function

The Iter generator is so commonly used, that a global function is provided in order to wrap its constructor: iter().

Other native generators

ClassDescription
AlternateAlternate values of multiple iterators
AsciiCharsIterate over bytes in a string
CartesianCartesian product between several iterators
ChainChains multiple child iterators together
CharsIterate over utf-8 chars in a string
CounterGenerate a custom suite of numbers
ForeverRepeat a value forever
FuncReturn the updated result of a function's call, forever
InterleaveInterleave values of two iterators
LettersIterate over well-known sequences of characters
ZipIterate over multiple iterators simultaneously

Summary of the IteratorInterface methods

Modifier methods

Modifier methods are methods which transform the actual iterator to another one. Here is a list of the native modifier methods:

alternate()     // Alternate values of multiple iterators.
apply()         // Wrap the iterator into a custom iterator.
cartesian()     // Performs the cartesian product between current and child iterators.
chain()         // Chain the iterator with other iterators.
chunks()        // Create an iterator over chunks of N elements.
filter()        // Filter the values of the iterator by using a callable.
filterKeys()    // Filter the keys of the iterator by using a callable.
flatten()       // Flatten the iterator, by assuming that each element is iterable itself.
interleave()    // Interleaves the values of the iterator with another iterator.
limit()         // Limit the number of elements of the iterator.
loop()          // Loop over the iterator a certain number of times.
mapKeys()       // Map the keys of the iterator by using a callable.
mapKeyValues()  // Map the keys and values of the iterator by using a callable.
map()           // Map the values of the iterator by using a callable.
run()           // Just run the given callable on each item.
skip()          // Skip the given number of elements at the start of the iterator.
slice()         // A combination of skip() and limit().
zip()           // Iterate over multiple iterators simultaneously.

Consuming/reducing methods

Once the wanted iterator has been created, then one "reducer" method can be called. These methodS will consume the iterator (partially or totally), so in order to reuse the iterator, the native Iterator method rewind() has to be called first.

Here is a list of the reducing methods:

isFound()   // Advance the iterator until the given predicate returns `true`.
find()      // Advance the iterator until the given predicate returns `true`.
first()     // Return the first value of the iterator.
last()      // Return the last value of the iterator. Consume the iterator entirely.
nth()       // Return the given Nth value of the iterator. Consume the iterator until this item is reached.
reduce()    // Reduce the iterator to a single value.
all()       // Check if all the iterator values fill the requirement of the given predicate.
any()       // heck if at least one of the iterator values fills the requirement of the given predicate.
count()     // Return the number of items of the iterator. Consume the iterator entirely.
min()       // Return the minimum value of the iterator.
max()       // Return the minimum value of the iterator.
sum()       // Return the sum of the values of the iterator.
average()   // Return the average of the values of the iterator.
join()      // Join all the values of the iterator into a single string.
toValues()  // Return an array of all the values of the iterator.
toArray()   // Return an array of all the values of the iterator.
consume()   // Consume all the iterator (like toValues(), but don't return anything).

Development

Running the test suite

$ composer create-project dajoha/php-iter
$ cd php-iter
$ vendor/bin/phpunit

Running phpstan

$ composer create-project dajoha/php-iter
$ cd php-iter
$ vendor/bin/phpstan analyze -v