API

On this page you’ll find a description of all the methods available in this library and their signature.

Static constructors

The following static methods acts as constructors.

empty

Create an empty Collection.

Signature: Collection::empty(): Collection;

$collection = Collection::empty();

fromCallable

Create a collection from a callable.

Tip

This can be very useful when working with a PHP Generator, since it will allow the collection object to behave as if the Generator was rewindable.

Signature: Collection::fromCallable(callable $callable, iterable $parameters = []): Collection;

$callback = static function (): Generator {
    yield 'a';
    yield 'b';
    yield 'c';
};

$collection = Collection::fromCallable($callback);

fromFile

Create a collection from a file.

Signature: Collection::fromFile(string $filepath, ?Closure $consumer = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Get data from a file
Collection::fromFile('/etc/passwd');

// Get data from a URL
Collection::fromFile('http://loripsum.net/api');

// Get data from a CSV file
Collection::fromFile('data.csv', fgetcsv(...));

fromGenerator

Create a collection from a Generator.

Warning

The difference between this constructor and fromIterable is that the generator is decorated with a caching Iterator. Generators are not rewindable by design and using fromGenerator automatically adds the caching layer for you.

Tip

You can reproduce the same behaviour by using fromIterable directly followed by the cache operation.

Signature: Collection::fromGenerator(Generator $generator): Collection;

$generator = (static fn () => yield from range(1, 5))();
$generator->next();
$generator->next();

$collection = Collection::fromGenerator($generator)
    ->all(); // [0 => 3, 1 => 4, 2 => 5]

fromIterable

Create a collection from an iterable.

Warning

When instantiating from a PHP Generator, the collection object will inherit its behaviour: it will only be iterable a single time, and an exception will be thrown if multiple operations which attempt to re-iterate are applied, for example count(). To circumvent this internal PHP limitation, use Collection::fromGenerator() or better Collection::fromCallable() which requires the generating callable not yet initialized.

Signature: Collection::fromIterable(iterable $iterable): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c']);

fromResource

Create a collection from a resource.

Signature: Collection::fromResource($resource): Collection;

$stream = fopen('data://text/plain,' . $string, 'rb');

$collection = Collection::fromResource($stream);

fromString

Create a collection from a string.

Signature: Collection::fromString(string $string, string $delimiter = ''): Collection;

$data = file_get_contents('http://loripsum.net/api');

$collection = Collection::fromString($data);

range

Build a collection from a range of values.

Signature: Collection::range(float $start = 0.0, float $end = INF, float $step = 1.0): Collection;

$even = Collection::range(0, 20, 2); // [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

times

Create a collection by invoking a callback a given amount of times.

If no callback is provided, then it will create a simple list of incremented integers.

Signature: Collection::times(int $number = 0, ?callable $callback = null): Collection;

$collection = Collection::times(5); // [1, 2, 3, 4, 5]

unfold

Create a collection by yielding from a callback with an initial value.

Warning

The callback needs to return a list of values which will be reused as callback arguments on the next callback call. Therefore, the returned list should contain values of the same type as the parameters for the callback function.

Signature: Collection::unfold(callable $callback, array $parameters = []): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

use const INF;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> A list of Naturals from 1 to Infinity (use `limit` to keep only a set of them)
Collection::unfold(static fn (int $n): array => [$n + 1], [1])
    ->unwrap()
    ->all(); // [1, 2, 3, 4, ...]

// Example 2 -> fibonacci sequence
Collection::unfold(static fn (int $a = 0, int $b = 1): array => [$b, $a + $b])
    ->pluck(0)
    ->limit(10)
    ->all(); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

// Example 3 -> infinite range, similar to the `range` operation
$even = Collection::unfold(static fn ($carry): array => [$carry + 2], [-2])->unwrap();
$odd = Collection::unfold(static fn ($carry): array => [$carry + 2], [-1])->unwrap();

// Is the same as
$even = Collection::range(0, INF, 2);
$odd = Collection::range(1, INF, 2);

Methods (operations)

Operations are pure functions which can be used to manipulate an iterator, either directly or through the Collection object.

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;
use loophp\collection\Operation\Filter;

include __DIR__ . '/../../../../vendor/autoload.php';

$input = [1, 2, 3, 4];
$even = static fn (int $value): bool => $value % 2 === 0;

// Standalone usage
$filtered = Filter::of()($even)($input);

print_r(iterator_to_array($filtered)); // [2, 4]

// Usage via Collection object
$filtered = Collection::fromIterable($input)->filter($even);

print_r($filtered->all()); // [2, 4]

When used separately, operations typically return a PHP Generator or an Iterator. When used as a Collection method, operations fall into a few main categories based on the return type:

  1. Operations that return a boolean or scalar value: Contains, Count, Equals, Every, Falsy, Has, IsEmpty, Match (or MatchOne), Nullsy, Truthy.

  2. Operations that return a Collection of Collection objects: Partition, Span.

  3. Operations that return keys/values from the collection: All, Current, Key.

  4. Operations that return a new Collection object: all other operations.

Note

The Key operation can return any value because Collection leverages PHP Generators, which allow using any type as a key as opposed to array, which only allows int|string keys.

Note

Earlier versions of the package had most operations returning a new Collection object. This was changed based on convenience and ease of use; typical usage of operations which return boolean values would involve immediately retrieving the value inside, whereas for most other operations further transformations are likely to be applied.

all

Convert the collection into an array, re-indexing keys using Collection::normalize() to prevent data loss by default.

Warning

Before version 6.0.0, this operation did not re-index keys by default, which meant at times data loss could occur. The reason data loss could occur is because PHP array keys cannot be duplicated and must either be int or string. The old behaviour can still be achieved by using the operation with the $normalize parameter as false.

Interface: Allable

Signature: Collection::all(bool $normalize = true): array;

<?php

declare(strict_types=1);

namespace App;

use Generator;
use loophp\collection\Collection;
use loophp\collection\Operation\All;
use loophp\collection\Operation\Filter;
use loophp\collection\Operation\Pipe;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> usage with "list" collection
$generator = static function (): Generator {
    yield 0 => 'a';

    yield 1 => 'b';

    yield 0 => 'c';

    yield 2 => 'd';
};

$collection = Collection::fromIterable($generator());
print_r($collection->all(false)); // [0 => 'c', 1 => 'b', 2 => 'd']

$collection = Collection::fromIterable($generator());
print_r($collection->all()); // ['a', 'b', 'c', 'd']

// Example 2 -> usage with "map" collection
$collection = Collection::fromIterable(['foo' => 1, 'bar' => 2]);

print_r($collection->all(false)); // ['foo' => 1, 'bar' => 2]
print_r($collection->all()); // [1, 2]

// Example 3 -> standalone operation usage
$even = static fn (int $value): bool => $value % 2 === 0;

$piped = Pipe::of()(Filter::of()($even), All::of()(true))([1, 2, 3, 4]);
print_r(iterator_to_array($piped)); // [2, 4]

append

Add one or more items to a collection.

Warning

This operation maintains the keys of the appended items. If you wish to re-index the keys you can use the Collection::normalize() operation, or Collection::all() when converting into an array, which will apply normalize by default.

Interface: Appendable

Signature: Collection::append(...$items): Collection;

Collection::fromIterable([1 => '1', 2 => '2', 3 => '3'])
    ->append('4'); // [1 => '1', 2 => '2', 3 => '3', 0 => '4']

Collection::fromIterable(['1', '2', '3'])
    ->append('4')
    ->append('5', '6')
    ->all(); // ['1', '2', '3', '4', '5', '6']

apply

Execute callback(s) on each element of the collection.

Iterates on the collection items regardless of the return value of the callback.

If the callback does not return true then it stops applying callbacks on subsequent items.

Interface: Applyable

Signature: Collection::apply(callable ...$callbacks): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$callback = static function ($value, $key): bool {
    var_dump('Value is: ' . $value . ', key is: ' . $key);

    return true;
};

$collection = Collection::fromIterable(['1', '2', '3']);

$collection
    ->apply($callback)
    ->squash(); // trigger the callback

/* Will print:
string(22) "Value is: 1, key is: 0"
string(22) "Value is: 2, key is: 1"
string(22) "Value is: 3, key is: 2"
 */

associate

Transform keys and values of the collection independently and combine them.

Interface: Associateable

Signature: Collection::associate(?callable $callbackForKeys = null, ?callable $callbackForValues = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1: Both callbacks are provided
$input = range(1, 5);

Collection::fromIterable($input)
    ->associate(
        static fn ($key, $value) => $key * 2,
        static fn ($value, $key) => $value * 3
    );

// [
//   0 => 3,
//   2 => 6,
//   4 => 9,
//   6 => 12,
//   8 => 15,
// ]

// Example 2: Only the callback for keys is provided
$input = range(1, 5);

Collection::fromIterable($input)
    ->associate(
        static fn ($key, $value) => $key * 2
    );

// [
//   0 => 1,
//   2 => 2,
//   4 => 3,
//   6 => 4,
//   8 => 5,
// ]

// Example 3: Only the callback for values is provided
$input = range(1, 5);

Collection::fromIterable($input)
    ->associate(
        null,
        static fn ($value, $key) => $value * 3
    );

// [
//   0 => 3,
//   1 => 6,
//   2 => 9,
//   3 => 12,
//   4 => 15,
// ]

asyncMap

Asynchronously apply a single callback to every item of a collection and use the return value.

Warning

This method requires amphp/parallel-functions to be installed.

Warning

This operation is non-deterministic, we cannot ensure the order of the elements at the end. Additionally, keys are preserved - use the Collection::normalize operation if you want to re-index the keys.

Warning

An earlier version of this operation allowed usage with multiple callbacks. This behaviour was removed in version 5.0; asyncMapN should be used instead, or, alternatively, multiple successive asyncMap calls can achieve the same result.

Interface: AsyncMapable

Signature: Collection::asyncMap(callable callback): Collection;

$mapper = static function(int $value): int {
    sleep($value);

    return $value * 2;
};

$collection = Collection::fromIterable(['c' => 3, 'b' => 2, 'a' => 1])
    ->asyncMap($mapper); // ['a' => 2, 'b' => 4, 'c' => 6]

asyncMapN

Asynchronously apply one or more supplied callbacks to every item of a collection and use the return value.

Tip

This operation is best used when multiple callbacks need to be applied. If you only want to apply a single callback, asyncMap should be preferred as it benefits from more specific type hints.

Warning

This method requires amphp/parallel-functions to be installed.

Warning

This operation is non-deterministic, we cannot ensure the order of the elements at the end. Additionally, keys are preserved - use the Collection::normalize operation if you want to re-index the keys.

Interface: AsyncMapNable

Signature: Collection::asyncMapN(callable ...$callbacks): Collection;

$mapper1 = static function(int $value): int {
    sleep($value);

    return $value;
};

$mapper2 = static function(int $value): int {
    return $value * 2;
};

$collection = Collection::fromIterable(['c' => 3, 'b' => 2, 'a' => 1])
    ->asyncMapN($mapper1, $mapper2); // ['a' => 2, 'b' => 4, 'c' => 6]

averages

Calculate the average of a collection of numbers.

The average constitute the result obtained by adding together several amounts and then dividing this total by the number of amounts.

Based on scanLeft1, this operation will return the average at each iteration. Therefore, if you’re looking for one single result, you must get the last item using last operation.

Interface: Averagesable

Signature: Collection::averages(): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$collection = Collection::fromIterable([1, 2, 3, 4, 5])
    ->averages(); // [1, 1.5, 2, 2.5, 3] from [1/1, (1+2)/2, (1+2+3)/3 ...]

$average = Collection::fromIterable([1, 2, 3, 4, 5])
    ->averages()
    ->last(); // [3]

$collection = Collection::empty()
    ->averages(); // []

cache

Useful when using a resource as input and you need to run through the collection multiple times.

Interface: Cacheable

Signature: Collection::cache(?CacheItemPoolInterface $cache = null): Collection;

$fopen = fopen(__DIR__ . '/vendor/autoload.php', 'r');

$collection = Collection::fromResource($fopen)
    ->cache();

chunk

Chunk a collection of items into chunks of items of a given size.

Interface: Chunkable

Signature: Collection::chunk(int ...$sizes): Collection;

$collection = Collection::fromIterable(range(0, 6))
    ->chunk(2); // [[0, 1], [2, 3], [4, 5], [6]]

$collection = Collection::fromIterable(range(0, 6))
    ->chunk(1, 2); // [[0], [1, 2], [3], [4, 5], [6]]

coalesce

Return the first non-nullsy value in a collection.

Nullsy values are:

  • The null value: null

  • Empty array: []

  • The integer zero: 0

  • The boolean: false

  • The empty string: ''

Interface: Coalesceable

Signature: Collection::coalesce(): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$input = range('a', 'e');

$collection = Collection::fromIterable($input)
    ->coalesce(); // [ 0 => 'a' ]

$input = ['', null, 'foo', false, ...range('a', 'e')];

$collection = Collection::fromIterable($input)
    ->coalesce(); // [ 2 => 'foo' ]

collapse

Collapse a collection of items into a simple flat collection.

Warning

Key differences compared to flatten are that only one level will be collapsed and values at the bottom level will be removed.

Interface: Collapseable

Signature: Collection::collapse(): Collection;

$collection = Collection::fromIterable([[1], [2, 3], [4, [5]]])
    ->collapse(); // [1, 2, 3, 4, [5]]

$collection = Collection::fromIterable([1, 2, [3]])
    ->collapse(); // [3]

column

Return the values from a single column in the input iterables.

Tip

If the iterables you are selecting from are Generators, the operation will allow accessing keys of any type, not just int|string.

Interface: Columnable

Signature: Collection::column($column): Collection;

$records = [
    [
        'id' => 2135,
        'first_name' => 'John',
        'last_name' => 'Doe',
    ],
    [
        'id' => 3245,
        'first_name' => 'Sally',
        'last_name' => 'Smith',
    ],
    [
        'id' => 5342,
        'first_name' => 'Jane',
        'last_name' => 'Jones',
    ],
    [
        'id' => 5623,
        'first_name' => 'Peter',
        'last_name' => 'Doe',
    ],
];

$result = Collection::fromIterable($records)
    ->column('first_name'); // ['John', 'Sally', 'Jane', 'Peter']

$result = Collection::fromIterable($records)
    ->column('non_existent_key'); // []

combinate

Get all the combinations of a given length of a collection of items.

Interface: Combinateable

Signature: Collection::combinate(?int $length = null): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c'])
    ->combinate(2); // [['a', 'b'], ['b', 'c'], ['a', 'c']]

combine

Combine a collection of items with some other keys.

Note

When the two sets to combine are not equal in size, null is used as filling value.

Interface: Combineable

Signature: Collection::combine(...$keys): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$result = Collection::fromIterable(range('A', 'E'))
    ->combine(...range('e', 'a')); // ['e' => 'A', 'd' => 'B', 'c' => 'C', 'b' => 'D', 'a' => 'E']

$result = Collection::fromIterable(range('a', 'e'))
    ->combine(...range('a', 'c')); // ['a' => 'a', 'b' => 'b', 'c' => 'c', 'd' => null, 'e' => null]

$result = Collection::fromIterable(range('a', 'c'))
    ->combine(...range('a', 'e')); // ['a' => 'a', 'b' => 'b', 'c' => 'c', null => 'd', null => 'e']

compact

Remove given values from the collection; if no values are provided, it removes nullsy values.

Nullsy values are:

  • The null value: null

  • Empty array: []

  • The integer zero: 0

  • The boolean: false

  • The empty string: ''

Interface: Compactable

Signature: Collection::compact(...$values): Collection;

$collection = Collection::fromIterable(['a', 1 => 'b', null, false, 0, 'c'])
    ->compact(); // [0 => 'a', 1 => 'b', 5 => 'c']

$collection = Collection::fromIterable(['a', 1 => 'b', null, false, 0, 'c'])
    ->compact(null, 0); // [0 => 'a', 1 => 'b', 3 => false, 5 => 'c']

compare

Fold the collection through a comparison operation, yielding the “highest” or “lowest” element as defined by the comparator callback. The callback takes a pair of two elements and should return the “highest” or “lowest” one as desired.

If no custom logic is required for the comparison, the simpler max or min operations can be used instead.

Tip

This operation is a specialised application of foldLeft1.

Interface: Comparable

Signature: Collection::compare(callable $comparator, $default = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;
use stdClass;

use const INF;

include __DIR__ . '/../../../../vendor/autoload.php';

$callback = static fn (stdClass $left, stdClass $right): stdClass => $left->age > $right->age
    ? $left
    : $right;

$input = [
    (object) ['id' => 2, 'age' => 5],
    (object) ['id' => 1, 'age' => 10],
];

$result = Collection::fromIterable($input)
    ->compare($callback); // (object) ['id' => 1, 'age' => 10]

$result = Collection::empty()
    ->compare($callback, -INF); // -INF

contains

Check if the collection contains one or more values.

Warning

The values parameter is variadic and will be evaluated as a logical OR.

Interface: Containsable

Signature: Collection::contains(...$values): bool;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Does it contains the letter 'd' ?
$result = Collection::fromIterable(range('a', 'c'))
    ->contains('d'); // false

// Does it contains the letter 'a' or 'z' ?
$result = Collection::fromIterable(range('a', 'c'))
    ->contains('a', 'z'); // true

// Does it contains the letter 'd' ?
$result = Collection::fromIterable(['a' => 'b', 'c' => 'd'])
    ->contains('d'); // true

count

Returns the number of elements in a collection.

Tip

If you only want to check whether the collection is empty or not, use the isEmpty operation as it will be significant more performant in a large collection.

Interface: Countable

Signature: Collection::count(): int;

$collection = Collection::fromIterable(range('a', 'c'))
    ->count(); // 3

countIn

This operation requires a reference to a parameter that will contain the amount of items in the collection.

The difference with the count operation is that the count operation will return the amount of items in the collection and the countIn operation will yield over the collection itself while updating the counter variable.

Interface: CountInAble

Signature: Collection::countIn(int &$counter): Collection;

<?php

declare(strict_types=1);

include __DIR__ . '/../../../../vendor/autoload.php';

use loophp\collection\Collection;

$lettersCounter = $wordsCounter = 0;

$collection = Collection::fromString('The quick brown fox jumps over the lazy dog')
    ->countIn($lettersCounter)
    ->words()
    ->countIn($wordsCounter)
    ->map(
        static function (string $word, int $k) use ($wordsCounter): string {
            return sprintf('[%s/%s]: %s', $k + 1, $wordsCounter, $word);
        }
    )
    ->all(); // [ "[1/9]: The", "[2/9]: quick", "[3/9]: brown", "[4/9]: fox", "[5/9]: jumps", "[6/9]: over", "[7/9]: the", "[8/9]: lazy", "[9/9]: dog" ]

print_r($wordsCounter); // 9
print_r($lettersCounter); // 43

current

Get the value of an item in the collection given a numeric index or the default 0.

If the given numeric index is out of bound, current will return a default value null that can be modified by providing a second argument to the operation.

Interface: Currentable

Signature: Collection::current(int $index = 0, $default = null);

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

Collection::fromIterable(['a', 'b', 'c', 'd'])->current(); // Return 'a'
Collection::fromIterable(['a', 'b', 'c', 'd'])->current(0); // Return 'a'
Collection::fromIterable(['a', 'b', 'c', 'd'])->current(1); // Return 'b'
Collection::fromIterable(['a', 'b', 'c', 'd'])->current(10); // Return null
Collection::fromIterable(['a', 'b', 'c', 'd'])->current(10, 'unavailable'); // Return 'unavailable'

cycle

Cycle indefinitely around a collection of items.

Interface: Cycleable

Signature: Collection::cycle(): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c', 'd'])
    ->cycle();

diff

Compares the collection against another collection, iterable, or set of multiple values. This method will return the values in the original collection that are not present in the given argument set.

Interface: Diffable

Signature: Collection::diff(...$values): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c', 'd', 'e'])
    ->diff('a', 'b', 'c', 'x'); // [3 => 'd', 4 => 'e']

diffKeys

Compares the collection against another collection, iterable, or set of multiple keys. This method will return the key / value pairs in the original collection that are not present in the given argument set.

Interface: Diffkeysable

Signature: Collection::diffKeys(...$keys): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c', 'd', 'e'])
    ->diffKeys(1, 2); // [0 => 'a', 3 => 'd', 4 => 'e']

dispersion

This method extends the library’s functionality by allowing users to calculate a dispersion value using the amount of value changes within the collection.

For example, the set [a, b, a] contains three elements and two changes. From a to b and from b to a. Therefore, the dispersion value is 2 / 2 = 1.

The dispersion value is normalized between 0 and 1. A dispersion of 0 indicates no dispersion, while a dispersion of 1 means maximum dispersion.

Interface: Dispersionable

Signature: Collection::dispersion(): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$collection = Collection::fromIterable(['a'])
    ->dispersion(); // [0]

$collection = Collection::fromIterable(['a', 'b'])
    ->dispersion(); // [0, 1]

$collection = Collection::fromIterable(['a', 'b', 'a'])
    ->dispersion(); // [0, 1, 1]

$collection = Collection::fromIterable(['a', 'b', 'b'])
    ->dispersion(); // [0, 1, .5]

distinct

Remove duplicated values from a collection, preserving keys.

The operation has 2 optional parameters that allow you to customize precisely how values are accessed and compared to each other.

The first parameter is the comparator. This is a curried function which takes first the left part, then the right part and then returns a boolean.

The second parameter is the accessor. This binary function takes the value and the key of the current iterated value and then return the value to compare. This is useful when you want to compare objects.

Interface: Distinctable

Signature: Collection::distinct(?callable $comparatorCallback = null, ?callable $accessorCallback = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use Closure;
use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> Using the default callbacks, with scalar values
$collection = Collection::fromIterable(['a', 'b', 'a', 'c'])
    ->distinct(); // [0 => 'a', 1 => 'b', 3 => 'c']

// Example 2 -> Using a custom comparator callback, with object values
final class User
{
    public function __construct(private string $name) {}

    public function name(): string
    {
        return $this->name;
    }
}

$users = [
    new User('foo'),
    new User('bar'),
    new User('foo'),
    new User('a'),
];

$collection = Collection::fromIterable($users)
    ->distinct(
        static fn (User $left): Closure => static fn (User $right): bool => $left->name() === $right->name()
    ); // [0 => User<foo>, 1 => User<bar>, 3 => User<a>]

// Example 3 -> Using a custom accessor callback, with object values
final class Person
{
    public function __construct(private string $name) {}

    public function name(): string
    {
        return $this->name;
    }
}

$users = [
    new Person('foo'),
    new Person('bar'),
    new Person('foo'),
    new Person('a'),
];

$collection = Collection::fromIterable($users)
    ->distinct(
        null,
        static fn (Person $person): string => $person->name()
    ); // [0 => Person<foo>, 1 => Person<bar>, 3 => Person<a>]

// Example 4 -> Using both accessor and comparator callbacks, with object values
final class Cat
{
    public function __construct(private string $name) {}

    public function name(): string
    {
        return $this->name;
    }
}

$users = [
    new Cat('izumi'),
    new Cat('nakano'),
    new Cat('booba'),
    new Cat('booba'),
];

$collection = Collection::fromIterable($users)
    ->distinct(
        static fn (string $left): Closure => static fn (string $right): bool => $left === $right,
        static fn (Cat $cat): string => $cat->name()
    ); // [0 => Cat<izumi>, 1 => Cat<nakano>, 2 => Cat<booba>]

drop

Drop the first n items of the collection.

Interface: Dropable

Signature: Collection::drop(int $count): Collection;

Collection::fromIterable(range(10, 20))
    ->drop(2); // [12,13,14,15,16,17,18,19,20]

dropWhile

Iterate over the collection items and takes from it its elements from the moment when the condition fails for the first time till the end of the list.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation.

Interface: DropWhileable

Signature: Collection::dropWhile(callable ...$callbacks): Collection;

$isSmallerThanThree = static function (int $value): bool {
    return 3 > $value;
};

Collection::fromIterable([1,2,3,4,5,6,7,8,9,1,2,3])
    ->dropWhile($isSmallerThanThree); // [3,4,5,6,7,8,9,1,2,3]

dump

Dump one or multiple items. It uses symfony/var-dumper if it is available, var_dump() otherwise. A custom callback can be also used.

Interface: Dumpable

Signature: Collection::dump(string $name = '', int $size = 1, ?Closure $closure = null): Collection;

Collection::fromIterable(range('a', 'e'))
    ->dump('Debug', 2); // Will debug the 2 first values.

duplicate

Find duplicated values from the collection.

The operation has 2 optional parameters that allow you to customize precisely how values are accessed and compared to each other.

The first parameter is the comparator. This is a curried function which takes first the left part, then the right part and then returns a boolean.

The second parameter is the accessor. This binary function takes the value and the key of the current iterated value and then return the value to compare. This is useful when you want to compare objects.

Interface: Duplicateable

Signature: Collection::duplicate(?callable $comparatorCallback = null, ?callable $accessorCallback = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use Closure;
use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> Using the default callbacks, with scalar values
$collection = Collection::fromIterable(['a', 'b', 'a', 'c', 'a', 'c'])
    ->duplicate(); // [2 => 'a', 4 => 'a', 5 => 'c']

// Example 2 -> Using a custom comparator callback, with object values
final class User
{
    public function __construct(private string $name) {}

    public function name(): string
    {
        return $this->name;
    }
}

$users = [
    new User('foo'),
    new User('bar'),
    new User('foo'),
    new User('a'),
];

$collection = Collection::fromIterable($users)
    ->duplicate(
        static fn (User $left): Closure => static fn (User $right): bool => $left->name() === $right->name()
    ); // [2 => User<foo>]

// Example 3 -> Using a custom accessor callback, with object values
final class Person
{
    public function __construct(private string $name) {}

    public function name(): string
    {
        return $this->name;
    }
}

$people = [
    new Person('foo'),
    new Person('bar'),
    new Person('foo'),
    new Person('a'),
];

$collection = Collection::fromIterable($people)
    ->duplicate(
        null,
        static fn (Person $person): string => $person->name()
    ); // [2 => Person<foo>]

// Example 4 -> Using both accessor and comparator callbacks, with object values
final class Cat
{
    public function __construct(private string $name) {}

    public function name(): string
    {
        return $this->name;
    }
}

$cats = [
    new Cat('izumi'),
    new Cat('nakano'),
    new Cat('booba'),
    new Cat('booba'),
];

$collection = Collection::fromIterable($cats)
    ->duplicate(
        static fn (string $left): Closure => static fn (string $right): bool => $left === $right,
        static fn (Cat $cat): string => $cat->name()
    ); // [3 => Cat<booba>]

entropy

This method extends the library’s functionality by allowing users to calculate the normalized Shannon entropy of each item in a collection. Since it is not possible to calculate it lazily just like in the average operation, this operation returns a collection of entropy values, calculated individually for each item. Therefore, if you’re looking for the entropy of the whole collection, you must get the last item using last operation.

The implementation provides the normalized version of Shannon entropy. Normalization ensures that the entropy value is scaled between 0 and 1, making it easier to compare entropy across different collections, irrespective of their size or the diversity of elements they contain.

An entropy of 0 indicates no diversity (all elements are the same), while an entropy of 1 signifies maximum diversity (all elements are different).

Interface: Entropyable

Signature: Collection::entropy(): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$collection = Collection::fromIterable(['a'])
    ->entropy(); // [0]

$collection = Collection::fromIterable(['a', 'b'])
    ->entropy(); // [0, 1]

$collection = Collection::fromIterable(['a', 'b', 'a'])
    ->entropy(); // [0, 1, 0.9182958340544896]

equals

Compare two collections for equality. Collections are considered equal if:

  • they have the same number of elements;

  • they contain the same elements, regardless of the order they appear in or their keys.

Elements will be compared using strict equality (===). If you want to customize how elements are compared or the order in which the keys/values appear is important, use the same operation.

Tip

This operation enables comparing Collection objects in PHPUnit tests using the dedicated assertObjectEquals assertion.

Warning

Because this operation needs to traverse both collections to determine if the same elements are contained within them, a performance cost is incurred. The operation will stop as soon as it encounters an element of one collection that cannot be found in the other. However, it is not recommended to use it for potentially large collections, where same can be used instead.

Interface: Equalsable

Signature: Collection::equals(Collection $other): bool;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

Collection::fromIterable([1, 2, 3])
    ->equals(Collection::fromIterable([1, 2, 3])); // true

Collection::fromIterable([1, 2, 3])
    ->equals(Collection::fromIterable([3, 1, 2])); // true

Collection::fromIterable([1, 2, 3])
    ->equals(Collection::fromIterable([1, 2])); // false

Collection::fromIterable([1, 2, 3])
    ->equals(Collection::fromIterable([1, 2, 4])); // false

Collection::fromIterable(['foo' => 'f'])
    ->equals(Collection::fromIterable(['foo' => 'f'])); // true

Collection::fromIterable(['foo' => 'f'])
    ->same(Collection::fromIterable(['bar' => 'f'])); // true

Collection::fromIterable(['foo' => 'f', 'bar' => 'b'])
    ->equals(Collection::fromIterable(['foo' => 'f', 'baz' => 'b'])); // true

$a = (object) ['id' => 'a'];
$a2 = (object) ['id' => 'a'];

Collection::fromIterable([$a])
    ->equals(Collection::fromIterable([$a])); // true

Collection::fromIterable([$a])
    ->equals(Collection::fromIterable([$a2])); // false

every

Check whether all elements in the collection pass the test implemented by the provided callback(s).

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR.

Interface: Everyable

Signature: Collection::every(callable ...$callbacks): bool;

$callback = static function (int $value): bool => $value < 20;

$result = Collection::fromIterable(range(0, 10))
    ->every($callback); // true

$result = Collection::fromIterable(range(0, 10))
    ->append(21)
    ->every($callback); // false

$result = Collection::fromIterable([])
    ->every($callback); // true

explode

Explode a collection into subsets based on a given value.

This operation uses the split operation with the flag Splitable::REMOVE and thus, values used to explode the collection are removed from the chunks.

Interface: Explodeable

Signature: Collection::explode(...$explodes): Collection;

$collection = Collection::fromString('I am a text.')
    ->explode(' '); // [['I', 'a', 'm', 'a', 't', 'e', 'x', 't', '.']]

falsy

Check if the collection contains only falsy values. A value is determined to be falsy by applying a bool cast.

Interface: Falsyable

Signature: Collection::falsy(): bool;

$result = Collection::fromIterable([2, 3, 4])
    ->falsy(); // false

$result = Collection::fromIterable([2, null, 4])
    ->falsy(); // false

$result = Collection::fromIterable(['', null, 0])
    ->falsy(); // true

filter

Filter collection items based on one or more callbacks.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation.

Tip

It is only when the callback returns true that the value is kept.

Tip

If you’re looking for keeping the value in the iterator when the return is false, see the reject operation.

Interface: Filterable

Signature: Collection::filter(callable ...$callbacks): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$divisibleBy2 = static fn ($value): bool => 0 === $value % 2;
$divisibleBy3 = static fn ($value): bool => 0 === $value % 3;

// Filter values divisible by 3.
$collection = Collection::fromIterable(range(1, 10))
    ->filter($divisibleBy3); // [3, 6, 9]

// Filter values divisible by 2 or 3.
$collection = Collection::fromIterable(range(1, 10))
    ->filter($divisibleBy2, $divisibleBy3); // [2, 3, 4, 6, 8, 9, 10]

// Filter values divisible by 2 and 3.
$collection = Collection::fromIterable(range(1, 10))
    ->filter($divisibleBy2)
    ->filter($divisibleBy3); // [6]

find

Find a collection item using one or more callbacks. If the value cannot be found, that is, no callback returns true for any collection item, it will return the $default value.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation.

Tip

This operation is a shortcut for filter + current.

Tip

It is only when the callback returns true that the value is selected.

Interface: Findable

Signature: Collection::find($default = null, callable ...$callbacks);

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

use function range;

include __DIR__ . '/../../../../vendor/autoload.php';

$divisibleBy3 = static fn ($value): bool => 0 === $value % 3;

// Example 1: find a value and use the default `null` if not found
$value = Collection::fromIterable(range(1, 10))
    ->find(null, $divisibleBy3); // 3

$value = Collection::fromIterable([1, 2, 4])
    ->find(null, $divisibleBy3); // null

$value = Collection::fromIterable(['foo' => 'f', 'bar' => 'b'])
    ->find(null, static fn ($value): bool => 'b' === $value); // 'b'

$value = Collection::fromIterable(['foo' => 'f', 'bar' => 'b'])
    ->find(null, static fn ($value): bool => 'x' === $value); // null

// Example 2: find a value and use a custom default if not found
$value = Collection::fromIterable([1, 2, 4])
    ->find(-1, $divisibleBy3); // -1

$value = Collection::fromIterable(['foo' => 'f', 'bar' => 'b'])
    ->find(404, static fn ($value): bool => 'x' === $value); // 404

// Example 3: use with a Doctrine Query
/** @var EntityManagerInterface $em */
$q = $em->createQuery('SELECT u FROM MyProject\Model\Product p');

$isBook = static fn ($product): bool => 'books' === $product->getCategory();

$value = Collection::fromIterable($q->toIterable())
    ->find(null, $isBook);

first

Get the first item from the collection in a separate collection. Alias for head.

Interface: Firstable

Signature: Collection::first($default = null): mixed;

$generator = static function (): Generator {
    yield 'a' => 'a';
    yield 'b' => 'b';
    yield 'c' => 'c';
};

Collection::fromIterable($generator())
    ->first(); // a

flatMap

Transform the collection using a callback and keep the return value, then flatten it one level. The supplied callback needs to return an iterable: either an array or a class that implements Traversable.

Tip

This operation is nothing more than a shortcut for map + flatten(1), or map + unwrap.

Warning

Keys are preserved, use the Collection::normalize operation if you want to re-index the keys.

Interface: FlatMapable

Signature: Collection::flatMap(callable $callback): Collection;

$square = static fn (int $val): int => $val ** 2;
$squareArray = static fn (int $val): array => [$val ** 2];
$squareCollection = static fn (int $val): Collection => Collection::fromIterable([$val ** 2]);

$collection = Collection::fromIterable(range(1, 3))
    ->flatMap($squareArray); // [1, 4, 9]

$collection = Collection::fromIterable(range(1, 3))
    ->flatMap($squareCollection); // [1, 4, 9]

$collection = Collection::fromIterable(range(1, 3))
    ->map($square)
    ->flatten(1); // [1, 4, 9]

$collection = Collection::fromIterable(range(1, 3))
    ->map($square)
    ->unwrap(); // [1, 4, 9]

flatten

Flatten a collection of items into a simple flat collection.

Interface: Flattenable

Signature: Collection::flatten(int $depth = PHP_INT_MAX): Collection;

$collection = Collection::fromIterable([0, [1, 2], [3, [4, [5, 6]]]])
    ->flatten(); // [0, 1, 2, 3, 4, 5, 6]

$collection = Collection::fromIterable([0, [1, 2], [3, [4, 5]])
    ->flatten(1); // [0, 1, 2, 3, [4, 5]]

flip

Flip keys and items in a collection.

Interface: Flipable

Signature: Collection::flip(): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c', 'a'])
    ->flip();

Tip

array_flip() and Collection::flip() can behave differently, check the following examples.

When using regular arrays, array_flip() can be used to remove duplicates (deduplicate an array).

$dedupArray = array_flip(array_flip(['a', 'b', 'c', 'd', 'a']));

This example will return ['a', 'b', 'c', 'd'].

However, when using a collection:

$dedupCollection = Collection::fromIterable(['a', 'b', 'c', 'd', 'a'])
    ->flip()
    ->flip()
    ->all();

This example will return ['a', 'b', 'c', 'd', 'a'].

foldLeft

Takes the initial value and the first item of the collection and applies the function to them, then feeds the function with this result and the second argument and so on. See scanLeft for intermediate results.

When the collection is empty, the initial value is returned.

Interface: FoldLeftable

Signature: Collection::foldLeft(callable $callback, mixed $initial): mixed;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$callback = static fn (int $carry, int $item): int => $carry + $item;

$collection = Collection::fromIterable(range(1, 5))
    ->foldLeft($callback, 0); // 15

$collection = Collection::empty()
    ->foldLeft($callback, 'foo'); // 'foo'

foldLeft1

Takes the first two items of the list and applies the function to them, then feeds the function with this result and the third argument and so on. See scanLeft1 for intermediate results.

Interface: FoldLeft1able

Signature: Collection::foldLeft1(callable $callback): mixed;

$callback = static fn(int $carry, int $value): int => $carry - $value;

Collection::fromIterable([64, 4, 2, 8])
    ->foldLeft1($callback); // 50

Collection::empty()
    ->foldLeft1($callback); // null

foldRight

Takes the initial value and the last item of the list and applies the function, then it takes the penultimate item from the end and the result, and so on. See scanRight for intermediate results.

Interface: FoldRightable

Signature: Collection::foldRight(callable $callback, $initial): mixed;

Collection::fromIterable(range('A', 'C'))
    ->foldLeft(
        static function (string $carry, string $item): string {
            $carry .= $item;

            return $carry;
        },
        ''
    ); // 'CBA'

foldRight1

Takes the last two items of the list and applies the function, then it takes the third item from the end and the result, and so on. See scanRight1 for intermediate results.

Interface: FoldRight1able

Signature: Collection::foldRight1(callable $callback): mixed;

$callback = static fn(int $carry, int $value): int => $carry + $value;

Collection::fromIterable([1, 2, 3, 4])
    ->foldRight1($callback); // 10

Collection::empty()
    ->foldRight1($callback); // null

forget

Remove items having specific keys.

Interface: Forgetable

Signature: Collection::forget(...$keys): Collection;

$collection = Collection::fromIterable(range('a', 'e'))
    ->forget(0, 4); // [1 => 'b', 2 => 'c', 3 => 'd']

frequency

Calculate the frequency of the items in the collection

Returns a new key-value collection with frequencies as keys.

Interface: Frequencyable

Signature: Collection::frequency(): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c', 'b', 'c', 'c'])
    ->frequency()
    ->all(false); // [1 => 'a', 2 => 'b', 3 => 'c'];

get

Get a specific element of the collection from a key; if the key doesn’t exist, returns the default value.

Interface: Getable

Signature: Collection::get($key, $default = null): Collection;

Collection::fromIterable(range('a', 'c'))->get(1) // 'b'

Collection::fromIterable(range('a', 'c'))->get(4, '') // ''

group

Takes a list and returns a list of lists such that the concatenation of the result is equal to the argument. Moreover, each sublist in the result contains only equal elements.

Interface: Groupable

Signature: Collection::group(): Collection;

Collection::fromString('Mississippi')
    ->group(); // [ [0 => 'M'], [1 => 'i'], [2 => 's', 3 => 's'], [4 => 'i'], [5 => 's', 6 => 's'], [7 => 'i'], [8 => 'p', 9 => 'p'], [10 => 'i'] ]

groupBy

Group items based on a custom callback. The grouping will be based on the returned value of the callback. The callback takes two parameters, the value and the key of the current item in the iterator, the returned value must be an integer or a string.

Interface: GroupByable

Signature: Collection::groupBy(callable $callback): Collection;

<?php

declare(strict_types=1);

namespace App;

use Generator;
use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$generator = static function (): Generator {
    yield 1 => 'a';

    yield 1 => 'b';

    yield 1 => 'c';

    yield 2 => 'd';

    yield 2 => 'e';

    yield 3 => 'f';
};

$groupByCallback = static fn (string $char, int $key): int => $key;

$collection = Collection::fromIterable($generator())
    ->groupBy($groupByCallback); // [1 => ['a', 'b', 'c'], 2 => ['d', 'e'], 3 => ['f']]

has

Check if the collection has values with the help of one or more callables.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR.

Interface: Hasable

Signature: Collection::has(callable ...$callbacks): bool;

$result = Collection::fromIterable(range('A', 'C'))
    ->has(static fn (): string => 'B'); // true

$result = Collection::fromIterable(range('A', 'C'))
    ->has(static fn (): string => 'D'); // false

$result = Collection::fromIterable(range('A', 'C'))
    ->has(
        static fn ($value, $key): string => $key > 4 ? 'D' : 'A',
        static fn ($value, $key): string => 'Z'
    ); // true

ifThenElse

Execute a mapping callback on each item of the collection when a condition is met.

If no else callback is provided, the identity function is applied (elements are not modified).

Interface: IfThenElseable

Signature: Collection::ifThenElse(callable $condition, callable $then, ?callable $else = null): Collection;

$input = range(1, 5);

$condition = static function (int $value): bool {
    return 0 === $value % 2;
};

$then = static function (int $value): int {
    return $value * $value;
};

$else = static function (int $value): int {
    return $value + 2;
};

Collection::fromIterable($input)
    ->ifThenElse($condition, $then); // [1, 4, 3, 16, 5]

Collection::fromIterable($input)
    ->ifThenElse($condition, $then, $else) // [3, 4, 5, 16, 7]

implode

Join all the elements of the collection into a single string using a glue provided or the empty string as default.

Interface: Implodeable

Signature: Collection::implode(string $glue = ''): string;

Collection::fromIterable(range('a', 'c'))
    ->implode('-'); // 'a-b-c'

init

Returns the collection without its last item.

Interface: Initable

Signature: Collection::init(): Collection;

Collection::fromIterable(range('a', 'e'))
    ->init(); // ['a', 'b', 'c', 'd']

inits

Returns all initial segments of the collection, shortest first.

Interface: Initsable

Signature: Collection::inits(): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$input = range('a', 'c');

Collection::fromIterable(range('a', 'c'))
    ->inits(); // [[], [[0, 'a']], [[0, 'a'], [1, 'b']], [[0, 'a'], [1, 'b'], [2, 'c']]]

Collection::fromIterable(array_combine(range('A', 'C'), $input))
    ->inits(); // [[], [['A', 'a']], [['A', 'a'], ['B', 'b']], [['A', 'a'], ['B', 'b'], ['C', 'c']]]

// To get only the values:

// Using `map` + `array_column`
Collection::fromIterable($input)
    ->inits()
    ->map(static fn (array $data): array => array_column($data, 1));
// [[], ['a'], ['a', 'b'], ['a', 'b', 'c']]

// Using the `pluck` operation
$var = Collection::fromIterable($input)
    ->inits()
    ->pluck('*.1');
// [[], ['a'], ['a', 'b'], ['a', 'b', 'c']]

intersect

Removes any values from the original collection that are not present in the given values set.

Interface: Intersectable

Signature: Collection::intersect(...$values): Collection;

$collection = Collection::fromIterable(range('a', 'e'))
    ->intersect('a', 'b', 'c'); // ['a', 'b', 'c']

intersectKeys

Removes any keys from the original collection that are not present in the given keys set.

Interface: Intersectkeysable

Signature: Collection::intersectKeys(...$keys): Collection;

$collection = Collection::fromIterable(range('a', 'e'))
    ->intersectKeys(0, 2, 4); // ['a', 'c', 'e']

intersperse

Insert a given value at every n element of a collection; indices are not preserved.

Interface: Intersperseable

Signature: Collection::intersperse($element, int $every = 1, int $startAt = 0): Collection;

$collection = Collection::fromIterable(range('a', 'c'))
    ->intersperse('x');

foreach($collection as $item) {
    var_dump($item); // 'x', 'a', 'x', 'b', 'x', 'c'
}

isEmpty

Check if a collection has no elements inside.

Interface: IsEmptyable

Signature: Collection::isEmpty(): bool;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

Collection::fromIterable(range('a', 'c'))
    ->isEmpty(); // false

Collection::fromIterable([])
    ->isEmpty(); // true

Collection::fromIterable([null])
    ->isEmpty(); // false

isNotEmpty

Check if a collection has at least one element inside.

Interface: IsNotEmptyable

Signature: Collection::isNotEmpty(): bool;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

Collection::fromIterable(range('a', 'c'))
    ->isNotEmpty(); // true

Collection::fromIterable([])
    ->isNotEmpty(); // false

Collection::fromIterable([null])
    ->isNotEmpty(); // true

jsonSerialize

Returns the collection items as an array, allowing serialization. Essentially calls all(false), which means the collection is not normalized by default.

See the section on serialization.

Interface: JsonSerializable

Signature: Collection::jsonSerialize(): array;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

Collection::fromIterable([1, 2, 3])
    ->jsonSerialize(); // [1, 2, 3]

Collection::fromIterable([1, 2, 3])
    ->filter(static fn (int $val): bool => $val % 2 !== 0)
    ->jsonSerialize(); // [0 => 1, 2 => 3]

Collection::fromIterable(['foo' => 1, 'bar' => 2])
    ->jsonSerialize(); // ['foo' => 1, 'bar' => 2]

key

Get the key of an item in the collection given a numeric index, default index is 0.

Interface: Keyable

Signature: Collection::key(int $index = 0);

Collection::fromIterable(['a', 'b', 'c', 'd'])->key(); // Return 0
Collection::fromIterable(['a', 'b', 'c', 'd'])->key(0); // Return 0
Collection::fromIterable(['a', 'b', 'c', 'd'])->key(1); // Return 1
Collection::fromIterable(['a', 'b', 'c', 'd'])->key(10); // Return null

keys

Get the keys of the items.

Interface: Keysable

Signature: Collection::keys(): Collection;

$collection = Collection::fromIterable(range('a', 'd'))
    ->keys(); // [0, 1, 2, 3]

last

Extract the last element of a collection, which must be finite and non-empty.

Interface: Lastable

Signature: Collection::last($default = null): Collection;

$generator = static function (): Generator {
    yield 'a' => 'a';
    yield 'b' => 'b';
    yield 'c' => 'c';
    yield 'a' => 'd';
    yield 'b' => 'e';
    yield 'c' => 'f';
};

Collection::fromIterable($generator())
    ->last(); // 'f'

limit

Limit the number of values in the collection.

Interface: Limitable

Signature: Collection::limit(int count = -1, int $offset = 0): Collection;

$even = Collection::range(0, \INF, 2)
    ->limit(5); // [0, 2, 4, 6, 8]

lines

Split a string into lines.

Interface: Linesable

Signature: Collection::lines(): Collection;

$string = <<<'EOF'
The quick brown fox jumps over the lazy dog.

This is another sentence.
EOF;

Collection::fromString($string)
    ->lines(); // ['The quick brown fox jumps over the lazy dog.', '', 'This is another sentence.']

map

Apply a single callback to every item of a collection and use the return value.

Warning

An earlier version of this operation allowed usage with multiple callbacks. This behaviour was removed in version 5.0; mapN should be used instead, or, alternatively, multiple successive map calls can achieve the same result.

Warning

Keys are preserved, use the Collection::normalize operation if you want to re-index the keys.

Interface: Mapable

Signature: Collection::map(callable $callback): Collection;

$square = static fn (int $val): int => $val ** 2;
$toString = static fn (int $val): string => (string) $val;
$appendBar = static fn (int $val): string => $val . 'bar';

$collection = Collection::fromIterable(range(1, 3))
    ->map($square)
    ->map($toString)
    ->map($appendBar); // ['1bar', '4bar', '9bar']

mapN

Apply one or more callbacks to every item of a collection and use the return value.

Tip

This operation is best used when multiple callbacks need to be applied. If you only want to apply a single callback, map should be preferred as it benefits from more specific type hints.

Warning

Keys are preserved, use the Collection::normalize operation if you want to re-index the keys.

Interface: MapNable

Signature: Collection::mapN(callable ...$callbacks): Collection;

$square = static fn (int $val): int => $val ** 2;
$toString = static fn (int $val): string => (string) $val;
$appendBar = static fn (int $val): string => $val . 'bar';

$collection = Collection::fromIterable(range(1, 3))
    ->mapN($square, $toString, $appendBar); // ['1bar', '4bar', '9bar']

match

Check if the collection matches a given user callback.

You must provide a callback that can get the key, the current value, and the iterator as parameters.

When no matcher callback is provided, the user callback must return true (the default value of the matcher callback) in order to stop.

The returned value of the operation is true when the callback matches at least one element of the collection, false otherwise.

If you want to match the user callback against another value (other than true), you must provide your own matcher callback as a second argument, and it must return a boolean.

Interface: Matchable

Signature: Collection::match(callable $callback, ?callable $matcher = null): bool;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> match found
$result = Collection::fromIterable(range(1, 100))
    ->match(static fn (int $value): bool => 10 > $value); // true

// Example 2 -> match not found
$result = Collection::fromIterable(range(1, 100))
    ->match(static fn (int $value): bool => 314 < $value); // false

// Example 3 -> match found
$input = [
    'Ningbo (CN)',
    'California (US)',
    'Brussels (EU)',
    'New York (US)',
];

$pattern = '/\(EU\)$/';

$result = Collection::fromIterable($input)
    ->match(static fn (string $value): bool => (bool) preg_match($pattern, $value)); // true

// Example 4 -> custom matcher
$result = Collection::fromIterable(range(1, 10))
    ->match(static fn (int $value): bool => 5 !== $value, static fn (): bool => false); // true

matching

Collection lets you use the Criteria API provided by Doctrine Collections, but in a lazy way.

Interface: Matchingable

Signature: Collection::matching(Criteria $criteria): Collection;

<?php

declare(strict_types=1);

namespace App;

use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManagerInterface;
use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

/** @var EntityManagerInterface $em */
$q = $em->createQuery('SELECT u FROM MyProject\Model\User u');

// 1. Injecting the query resultset in a collection
$collection = Collection::fromIterable($q->toIterable());

// 2. Using a criteria
$collection = Collection::fromIterable($q->toIterable())
    ->matching(
        Criteria::create()
            ->where(
                Criteria::expr()
                    ->eq('isActive', true)
            )
    );

max

Generate the maximum value of the collection by successively applying the PHP max function to each pair of two elements.

If custom logic is required to determine the maximum, such as when comparing objects, the compare operation can be used instead.

Interface: Maxable

Signature: Collection::max($default = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

use const PHP_INT_MAX;

include __DIR__ . '/../../../../vendor/autoload.php';

$result = Collection::fromIterable([1, 4, 3, 0, 2])
    ->max(); // 4

$result = Collection::fromIterable([1, 4, null, 0, 2])
    ->max(); // 4

$result = Collection::empty()
    ->max(PHP_INT_MAX); // PHP_INT_MAX

merge

Merge one or more iterables onto a collection.

Interface: Mergeable

Signature: Collection::merge(iterable ...$sources): Collection;

Collection::fromIterable(range(1, 5))
    ->merge(range(6, 10)); // range(1, 10)

$collection = Collection::fromIterable(['a', 'b', 'c'])
    ->merge(Collection::fromIterable(['d', 'e']);

$collection->all() // ['a', 'b', 'c', 'd', 'e']

min

Generate the minimum value of the collection by successively applying the PHP min function to each pair of two elements.

If custom logic is required to determine the minimum, such as when comparing objects, the compare operation can be used instead.

Interface: Minable

Signature: Collection::min($default = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

use const PHP_INT_MIN;

include __DIR__ . '/../../../../vendor/autoload.php';

$result = Collection::fromIterable([1, 4, 3, 0, 2])
    ->min(); // 0

$result = Collection::fromIterable([1, 4, null, 0, 2])
    ->min(); // null

$result = Collection::empty()
    ->min(PHP_INT_MIN); // PHP_INT_MIN

normalize

Replace, reorder and use numeric keys on a collection.

Interface: Normalizeable

Signature: Collection::normalize(): Collection;

<?php

declare(strict_types=1);

namespace App;

use Generator;
use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> string keys
$collection = Collection::fromIterable(['a' => 10, 'b' => 100, 'c' => 1000])
    ->normalize();  // [0 => 10, 1 => 100, 2 => 1000]

// Example 2 -> duplicate keys
$generator = static function (): Generator {
    yield 1 => 'a';

    yield 2 => 'b';

    yield 1 => 'c';

    yield 3 => 'd';
};

$collection = Collection::fromIterable($generator())
    ->normalize(); // [0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd']

// Example 3 -> "missing" numeric keys
$collection = Collection::fromIterable(range(1, 5))
    ->filter(static fn (int $val): bool => $val % 2 === 0) // [1 => 2, 3 => 4]
    ->normalize(); // [0 => 2, 1 => 4]

nth

Get every n-th element of a collection.

Interface: Nthable

Signature: Collection::nth(int $step, int $offset = 0): Collection;

$collection = Collection::fromIterable(range(1, 20))
    ->nth(5); // [0 => 1, 5 => 6, 10 => 11, 15 => 16]

nullsy

Check if the collection contains only nullsy values.

Nullsy values are:

  • The null value: null

  • Empty array: []

  • The integer zero: 0

  • The boolean: false

  • The empty string: ''

Interface: Nullsyable

Signature: Collection::nullsy(): bool;

$result = Collection::fromIterable([null, false])
    ->nullsy(); // true

$result = Collection::fromIterable(['a', null, 'c'])
    ->nullsy(); // false

pack

Wrap each item into an array containing 2 items: the key and the value.

Interface: Packable

Signature: Collection::pack(): Collection;

$input = ['a' => 'b', 'c' => 'd', 'e' => 'f'];

$c = Collection::fromIterable($input)
    ->pack();

 // [
 //   ['a', 'b'],
 //   ['c', 'd'],
 //   ['e', 'f'],
 // ]

pad

Pad a collection to the given length with a given value.

Interface: Padable

Signature: Collection::pad(int $size, $value): Collection;

$collection = Collection::fromIterable(range(1, 5))
    ->pad(8, 'foo'); // [1, 2, 3, 4, 5, 'foo', 'foo', 'foo']

pair

Make an associative collection from pairs of values.

Interface: Pairable

Signature: Collection::pair(): Collection;

$input = [
    [
        'key' => 'k1',
        'value' => 'v1',
    ],
    [
        'key' => 'k2',
        'value' => 'v2',
    ],
    [
        'key' => 'k3',
        'value' => 'v3',
    ],
    [
        'key' => 'k4',
        'value' => 'v4',
    ],
    [
        'key' => 'k4',
        'value' => 'v5',
    ],
];

$c = Collection::fromIterable($input)
    ->unwrap()
    ->pair();

// [
//    [k1] => v1
//    [k2] => v2
//    [k3] => v3
//    [k4] => [
//        [0] => v4
//        [1] => v5
//    ]
// ]

partition

Partition the collection into two subgroups of items using one or more callables.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation.

The raw Partition operation returns a generator yielding two iterators.

The first inner iterator is the result of a filter operation, it contains items that have met the provided callback(s). The second (and last) inner iterator is the result of a reject operation, it contains items that have not met the provided callback(s).

When the partition operation is used through the Collection object, the two resulting iterators will be converted and mapped into a Collection object.

The first inner collection contains items that have met the provided callback(s). The second (and last) collection contains items that have not met the provided callback(s).

Interface: Partitionable

Signature: Collection::partition(callable ...$callbacks): Collection;

<?php

declare(strict_types=1);

namespace App;

use Closure;
use loophp\collection\Collection;
use loophp\collection\Operation\Partition;

include __DIR__ . '/../../../../vendor/autoload.php';

$isGreaterThan = static fn (int $left): Closure => static fn (int $right): bool => $left < $right;

$input = array_combine(range('a', 'l'), [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3]);

// Example 1 -> Retrieve the left and right groups
[$left, $right] = Collection::fromIterable($input)
    ->partition($isGreaterThan(5))
    ->all();

// Numbers that are greater than 5
print_r($left->all(false));
/*
[
    ['f', 6],
    ['g', 7],
    ['h', 8],
    ['i', 9],
]
 */

// Numbers that are not greater than 5
print_r($right->all(false));
/*
[
    ['a', 1],
    ['b', 2],
    ['c', 3],
    ['d', 4],
    ['e', 5],
    ['j', 1],
    ['k', 2],
    ['l', 3],
]
 */

// Example 2 -> Retrieve the first group only
$left = Collection::fromIterable($input)
    ->partition($isGreaterThan(5))
    ->first();

// Numbers that are greater than 5
print_r($left->all(false));
/*
[
['f', 6],
['g', 7],
['h', 8],
['i', 9],
]
 */

// Example 3 -> Use Partition operation separately
[$left] = iterator_to_array(Partition::of()($isGreaterThan(5))($input));

// Numbers that are greater than 5
print_r(iterator_to_array($left));
/*
[
['f', 6],
['g', 7],
['h', 8],
['i', 9],
]
 */

permutate

Find all the permutations of a collection.

Interface: Permutateable

Signature: Collection::permutate(): Collection;

$collection = Collection::fromIterable(['a', 'b'])
    ->permutate(); // [['a', 'b'], ['b', 'a']]

pipe

Pipe together multiple operations and apply them in succession to the collection items. To maintain a lazy nature, each operation needs to return a Generator. Custom operations and operations provided in the API can be combined together.

Interface: Pipeable

Signature: Collection::pipe(callable ...$callbacks): Collection;

<?php

declare(strict_types=1);

namespace App;

use Closure;
use Generator;
use loophp\collection\Collection;
use loophp\collection\Operation\AbstractOperation;
use loophp\collection\Operation\Reverse;

include __DIR__ . '/../../../../vendor/autoload.php';

$square = static function ($collection): Generator {
    foreach ($collection as $item) {
        yield $item ** 2;
    }
};

$toString = static function ($collection): Generator {
    foreach ($collection as $item) {
        yield (string) $item;
    }
};

$times = new class() extends AbstractOperation {
    public function __invoke(): Closure
    {
        return static function ($collection): Generator {
            foreach ($collection as $item) {
                yield "{$item}x";
            }
        };
    }
};

Collection::fromIterable(range(1, 5))
    ->pipe($square, Reverse::of(), $toString, $times())
    ->all(); // ['25x', '16x', '9x', '4x', '1x']

pluck

Retrieves all of the values of a collection for a given key. Nested values can be retrieved using “dot notation” and the wildcard character *.

Interface: Pluckable

Signature: Collection::pluck($pluck, $default = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> numeric keys
$fibonacci = static fn ($a = 0, $b = 1): array => [$b, $a + $b];
$collection = Collection::unfold($fibonacci)
    ->limit(6)
    ->pluck(0); // [1, 1, 2, 3, 5, 8]

// Example 2 -> basic string keys
$input = [
    ['foo' => 'A', 'bar' => 'B'],
    ['foo' => 'C', 'bar' => 'D'],
];
$collection = Collection::fromIterable($input)
    ->pluck('foo');  // ['A', 'C']

// Example 3 -> nested keys and values
$input = [
    ['foo' => 'A', 'bar' => ['baz' => 'B']],
    ['foo' => 'C', 'bar' => ['baz' => 'D']],
];
$collection = Collection::fromIterable($input);
$collection->pluck('bar'); // [['baz' => 'B'], ['baz' => 'D']]
$collection->pluck('bar.baz'); // ['B', 'D']
$collection->pluck('*.baz'); // [[null, 'B'], [null, 'D']]

prepend

Push an item onto the beginning of the collection.

Warning

This operation maintains the keys of the prepended items. If you wish to re-index the keys you can use the Collection::normalize() operation, or Collection::all() when converting into an array, which will apply normalize by default.

Interface: Prependable

Signature: Collection::prepend(...$items): Collection;

Collection::fromIterable([1 => '1', 2 => '2', 3 => '3'])
    ->prepend('4'); // [0 => 4, 1 => '1', 2 => '2', 3 => '3']

Collection::fromIterable(['1', '2', '3'])
    ->prepend('4')
    ->prepend('5', '6')
    ->all(); // ['5', '6', '4', '1', '2', '3']

product

Get the cartesian product of items of a collection.

Interface: Productable

Signature: Collection::product(iterable ...$iterables): Collection;

$collection = Collection::fromIterable(range('A', 'C'))
    ->product([1, 2]); // [['A', 1], ['A', 2], ['B', 1], ['B', 2], ['C', 1], ['C', 2]]

random

Returns a random item from the collection.

An optional integer can be passed to random to specify how many items you would like to randomly retrieve. An optional seed can be passed as well.

Interface: Randomable

Signature: Collection::random(int $size = 1, ?int $seed = null): Collection;

$collection = Collection::fromIterable(['4', '5', '6'])
    ->random(); // ['6']

reduce

Reduce a collection of items through a given callback. An optional initial value is by default set to null and can be customized by the user through the second argument.

When the collection is empty, the initial value is returned.

Interface: Reduceable

Signature: Collection::reduce(callable $callback, mixed $initial = null): mixed;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$callback = static fn (int $carry, int $item): int => $carry + $item;

$collection = Collection::fromIterable(range(1, 5))
    ->reduce($callback, 0); // 15

$collection = Collection::empty()
    ->reduce($callback, 'foo'); // 'foo'

$collection = Collection::empty()
    ->reduce($callback); // null

reduction

Takes the initial value and the first item of the list and applies the function to them, then feeds the function with this result and the second argument and so on. It returns the list of intermediate and final results.

When the collection is empty, the initial value is yielded.

Interface: Reductionable

Signature: Collection::reduction(callable $callback, $initial = null): Collection;

$callback = static fn ($carry, $item) => $carry + $item;

$collection = Collection::fromIterable(range(1, 5))
    ->reduction($callback); // [1, 3, 6, 10, 15]

reject

Reject collection items based on one or more callbacks.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation. However, due to the nature of this operation, the behaviour is the same.

Tip

It is only when the callback returns false that the value is kept.

Tip

If you’re looking for keeping the value in the iterator when the return is true, see the filter operation.

Interface: Rejectable

Signature: Collection::reject(callable ...$callbacks): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$divisibleBy2 = static fn ($value): bool => 0 === $value % 2;
$divisibleBy3 = static fn ($value): bool => 0 === $value % 3;

// Reject values divisible by 2
$collection = Collection::fromIterable(range(1, 10))
    ->reject($divisibleBy2); // [1, 3, 5, 7, 9]

// Reject values divisible by 2 or 3
$collection = Collection::fromIterable(range(1, 10))
    ->reject($divisibleBy2, $divisibleBy3); // [1, 5, 7]

// Reject values not divisible by 2 and then by 3
$collection = Collection::fromIterable(range(1, 10))
    ->reject($divisibleBy2)
    ->reject($divisibleBy3); // [1, 5, 7]

reverse

Reverse the order of items in a collection.

Interface: Reverseable

Signature: Collection::reverse(): Collection;

$collection = Collection::fromIterable(['a', 'b', 'c'])
    ->reverse(); // ['c', 'b', 'a']

rsample

Take a random sample of elements of items from a collection. Accepts a probability parameter which will influence the number of items sampled - higher probabilities increase the chance of sampling close to the entire collection.

Interface: RSampleable

Signature: Collection::rsample(float $probability): Collection;

$collection = Collection::fromIterable(range(1, 5));
$collection->rsample(1.0); // [1, 2, 3, 4, 5]
$collection->rsample(0.5); // will get about half of the elements at random

same

Compare two collections for sameness. Collections are considered same if:

  • they have the same number of elements;

  • they have the same keys and elements, in the same order.

By default elements and keys will be compared using strict equality (===). However, this behaviour can be customized with a comparator callback. This should be a curried function which takes first the left value and key, then the right value and key, and returns a boolean.

This operation will stop and return a value as soon as one of the collections has been seen fully or as soon as the comparison yields false for any key-value pair.

Interface: Sameable

Signature: Collection::same(Collection $other, ?callable $comparatorCallback = null): bool;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;
use stdClass;

include __DIR__ . '/../../../../vendor/autoload.php';

Collection::fromIterable([1, 2, 3])
    ->same(Collection::fromIterable([1, 2, 3])); // true

Collection::fromIterable([1, 2, 3])
    ->same(Collection::fromIterable([3, 1, 2])); // false

Collection::fromIterable([1, 2, 3])
    ->same(Collection::fromIterable([1, 2])); // false

Collection::fromIterable([1, 2, 3])
    ->same(Collection::fromIterable([1, 2, 4])); // false

Collection::fromIterable(['foo' => 'f'])
    ->same(Collection::fromIterable(['foo' => 'f'])); // true

Collection::fromIterable(['foo' => 'f'])
    ->same(Collection::fromIterable(['bar' => 'f'])); // false

Collection::fromIterable(['foo' => 'f', 'bar' => 'b'])
    ->same(Collection::fromIterable(['foo' => 'f', 'bar' => 'b'])); // true

Collection::fromIterable(['foo' => 'f', 'bar' => 'b'])
    ->same(Collection::fromIterable(['bar' => 'b', 'foo' => 'f'])); // false

$a = (object) ['id' => 'a'];
$a2 = (object) ['id' => 'a'];

Collection::fromIterable([$a])
    ->equals(Collection::fromIterable([$a])); // true

Collection::fromIterable([$a])
    ->equals(Collection::fromIterable([$a2])); // false

$comparator = static fn (string $left) => static fn (string $right): bool => $left === $right;
Collection::fromIterable(['foo' => 'f'])
    ->same(Collection::fromIterable(['bar' => 'f']), $comparator); // true

$comparator = static fn ($left, $leftKey) => static fn ($right, $rightKey): bool => $left === $right
    && mb_strtolower($leftKey) === mb_strtolower($rightKey);
Collection::fromIterable(['foo' => 'f'])
    ->same(Collection::fromIterable(['FOO' => 'f']), $comparator); // true

$comparator = static fn (stdClass $left) => static fn (stdClass $right): bool => $left->id === $right->id;
Collection::fromIterable([$a])
    ->same(Collection::fromIterable([$a2]), $comparator); // true

scale

Scale/normalize values. Values will be scaled between 0 and 1 by default, if no desired bounds are provided.

Interface: Scaleable

Signature: Collection::scale(float $lowerBound, float $upperBound, float $wantedLowerBound = 0.0, float $wantedUpperBound = 1.0, float $base = 0.0): Collection;

$default = Collection::fromIterable([0, 5, 10, 15, 20])
    ->scale(0, 20); // [0, 0.25, 0.5, 0.75, 1]

$withBounds = Collection::fromIterable([0, 5, 10, 15, 20])
    ->scale(0, 20, 0, 12); // [0, 3, 6, 9, 12]

$withBoundsAndBase = Collection::fromIterable([0, 5, 10, 15, 20])
    ->scale(0, 20, 0, 12, \M_E); // [1, 6.90, 9.45, 10.94, 12]

scanLeft

Takes the initial value and the first item of the list and applies the function to them, then feeds the function with this result and the second argument and so on. It returns the list of intermediate and final results.

When the collection is empty, the initial value is yielded.

Interface: ScanLeftable

Signature: Collection::scanLeft(callable $callback, $initial): Collection;

$callback = static function ($carry, $value) {
    return $carry / $value;
};

Collection::fromIterable([4, 2, 4])
    ->scanLeft($callback, 64)
    ->normalize(); // [64, 16, 8, 2]

Collection::empty()
    ->scanLeft($callback, 3); // [0 => 3]

scanLeft1

Takes the first two items of the list and applies the function to them, then feeds the function with this result and the third argument and so on. It returns the list of intermediate and final results.

Warning

You might need to use the normalize operation after this.

Interface: ScanLeft1able

Signature: Collection::scanLeft1(callable $callback): Collection;

$callback = static function ($carry, $value) {
    return $carry / $value;
};

Collection::fromIterable([64, 4, 2, 8])
    ->scanLeft1($callback); // [64, 16, 8, 1]

Collection::fromIterable([12])
    ->scanLeft1($callback); // [12]

Collection::empty()
    ->scanLeft1($callback); // []

scanRight

Takes the initial value and the last item of the list and applies the function, then it takes the penultimate item from the end and the result, and so on. It returns the list of intermediate and final results.

When the collection is empty, the initial value is yielded.

Interface: ScanRightable

Signature: Collection::scanRight(callable $callback, $initial): Collection;

$callback = static function ($carry, $value) {
    return $value / $carry;
};

Collection::fromIterable([8, 12, 24, 4])
    ->scanRight($callback, 2); // [8, 1, 12, 2, 2]

Collection::empty()
    ->scanRight($callback, 3); // [3]

scanRight1

Takes the last two items of the list and applies the function, then it takes the third item from the end and the result, and so on. It returns the list of intermediate and final results.

Warning

You might need to use the normalize operation after this.

Interface: ScanRight1able

Signature: Collection::scanRight1(callable $callback): Collection;

$callback = static function ($carry, $value) {
    return $value / $carry;
};

Collection::fromIterable([8, 12, 24, 2])
    ->scanRight1($callback); // [8, 1, 12, 2]

Collection::fromIterable([12])
    ->scanRight1($callback); // [12]

Collection::empty()
    ->scanRight1($callback); // []

shuffle

Shuffle a collection, randomly changing the order of items.

Interface: Shuffleable

Signature: Collection::shuffle(?int $seed = null): Collection;

$collection = Collection::fromIterable(['4', '5', '6'])
    ->random(); // ['6', '4', '5']

$collection = Collection::fromIterable(['4', '5', '6'])
    ->random(); // ['5', '6', '5']

since

Skip items until the callback is met.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation.

Interface: Sinceable

Signature: Collection::since(callable ...$callbacks): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;
use loophp\collection\Contract\Operation\Splitable;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> Parse the composer.json of a package and get the require-dev dependencies.
/** @var resource $fileResource */
$fileResource = fopen(__DIR__ . '/composer.json', 'rb');
$collection = Collection::fromResource($fileResource)
    // Group items when EOL character is found.
    ->split(Splitable::REMOVE, static fn (string $character): bool => "\n" === $character)
    // Implode characters to create a line string
    ->map(static fn (array $characters): string => implode('', $characters))
    // Skip items until the string "require-dev" is found.
    ->since(static fn ($line): bool => str_contains($line, 'require-dev'))
    // Skip items after the string "}" is found.
    ->until(static fn ($line): bool => str_contains($line, '}'))
    // Re-index the keys
    ->normalize()
    // Filter out the first line and the last line.
    ->filter(
        static fn ($line, $index): bool => 0 !== $index,
        static fn ($line): bool => !str_contains($line, '}')
    )
    // Trim remaining results and explode the string on ':'.
    ->map(
        static fn ($line) => trim($line),
        static fn ($line) => explode(':', $line)
    )
    // Take the first item.
    ->pluck(0)
    // Convert to array.
    ->all();

// Example 2 -> Usage as logical OR and logical AND
$input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3];

$isGreaterThanThree = static fn (int $value): bool => 3 < $value;
$isGreaterThanEight = static fn (int $value): bool => 8 < $value;

$logicalORCollection = Collection::fromIterable($input)
    ->since($isGreaterThanThree, $isGreaterThanEight);
// [4, 5, 6, 7, 8, 9, 1, 2, 3]

$logicalANDCollection = Collection::fromIterable($input)
    ->since($isGreaterThanThree)
    ->since($isGreaterThanEight);
// [9, 1, 2, 3]

slice

Get a slice of a collection.

Interface: Sliceable

Signature: Collection::slice(int $offset, ?int $length = -1): Collection;

$collection = Collection::fromIterable(range('a', 'z'))
    ->slice(5, 3); // [5 => 'f', 6 => 'g', 7 => 'h']

sort

Sort a collection using a callback. If no callback is provided, it will sort using natural order. The direction by default match the PHP usort function.

By default, it will sort by values and using the default callback. If you want to sort by keys, you can pass a parameter to change the behaviour.

Since version 7.4, sorting is stable by default. Stable sort algorithms sort equal elements in the same order that they appear in the input.

Interface: Sortable

Signature: Collection::sort(int $type = Sortable::BY_VALUES, ?callable $callback = null): Collection;

Callback signature: Closure(mixed $right, mixed $left, mixed $rightKey, mixed $leftKey): int

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;
use loophp\collection\Contract\Operation\Sortable;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> Regular values sorting
$collection = Collection::fromIterable(['z', 'y', 'x'])
    ->sort(); // [2 => 'x', 1 => 'y', 0 => 'z']

// Example 2 -> Regular values sorting with a custom callback
$collection = Collection::fromIterable(['z', 'y', 'x'])
    ->sort(
        Sortable::BY_VALUES,
        static fn (string $left, string $right): int => $left <=> $right
    ); // [0 => 'z', 1 => 'y', 2 => 'x']

// Example 3 -> Regular values sorting with a custom callback, inverted
$collection = Collection::fromIterable(['z', 'y', 'x'])
    ->sort(
        Sortable::BY_VALUES,
        static fn (string $left, string $right): int => $right <=> $left
    ); // [2 => 'x', 1 => 'y', 0 => 'z']

// Example 4 -> Regular keys sorting (no callback is needed here)
$collection = Collection::fromIterable([3 => 'z', 2 => 'y', 1 => 'x'])
    ->sort(Sortable::BY_KEYS); // [1 => 'x', 2 => 'y', 3 => 'z']

// Example 5 -> Regular keys sorting using the flip() operation twice
$collection = Collection::fromIterable([3 => 'z', 2 => 'y', 1 => 'x'])
    ->flip() // Exchange values and keys
    ->sort() // Sort the values (which are now the keys)
    ->flip(); // Flip again to put back the keys and values, sorted by keys.
// [1 => 'x', 2 => 'y', 3 => 'z']

span

Partition the collection into two subgroups where the first element is the longest prefix (possibly empty) of elements that satisfy the callback(s) and the second element is the remainder.

The raw Span operation returns a generator yielding two iterators.

The first inner iterator is the result of a TakeWhile operation. The second (and last) inner iterator is the result of a DropWhile operation.

When the span operation is used through the Collection object, the two resulting iterators will be converted and mapped into Collection objects.

Interface: Spanable

Signature: Collection::span(callable ...$callbacks): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;
use loophp\collection\Operation\Span;

include __DIR__ . '/../../../../vendor/autoload.php';

$input = range(1, 10);

// Example 1 -> Retrieve the left and right groups
[$first, $last] = Collection::fromIterable($input)
    ->span(static fn ($x): bool => 4 > $x)
    ->all();

print_r($first->all()); // [1, 2, 3]
print_r($last->all());  // [4, 5, 6, 7, 8, 9, 10]

// Example 2 -> Retrieve the second group only
$last = Collection::fromIterable($input)
    ->span(static fn ($x): bool => 4 > $x)
    ->last();

print_r($last->all()); // [4, 5, 6, 7, 8, 9, 10]

// Example 3 -> Use Span operation separately
[$left] = iterator_to_array(Span::of()(static fn ($x): bool => 4 > $x)($input));

print_r(iterator_to_array($left)); // [1, 2, 3]

split

Split a collection using one or more callbacks.

A flag must be provided in order to specify whether the value used to split the collection should be added at the end of a chunk, at the beginning of a chunk, or completely removed.

Interface: Splitable

Signature: Collection::split(int $type = Splitable::BEFORE, callable ...$callbacks): Collection;

$splitter = static function ($value): bool => 0 === $value % 3;

$collection = Collection::fromIterable(range(0, 10))
    ->split(Splitable::BEFORE, $splitter); // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

$collection = Collection::fromIterable(range(0, 10))
    ->split(Splitable::AFTER, $splitter); [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

$collection = Collection::fromIterable(range(0, 10))
    ->split(Splitable::REMOVE, $splitter); [[], [1, 2], [4, 5], [7, 8], [10]]

squash

Eagerly apply operations in a collection rather than lazily.

Interface: Squashable

Signature: Collection::squash(): Collection;

<?php

declare(strict_types=1);

namespace App;

use Exception;
use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$results = Collection::fromIterable(range(0, 100))
    ->filter(static fn (int $int) => 0 === $int % 2)
    ->map(static fn (int $int) => 'document' . $int . '.pdf')
    ->map(
        static function (string $doc): string {
            if (!file_exists('/doc/' . $doc)) {
                throw new Exception('Nonexistent file');
            }

            return (string) file_get_contents($doc);
        }
    )
    ->squash(); // Instantly trigger an exception if a file does not exist.

// If no exception, you can continue the processing...
$results = $results
    ->filter(
        static fn (string $document): bool => str_contains($document, 'foobar')
    );

strict

Enforce a single type in the collection at runtime. If the collection contains objects, they will either be expected to implement the same interfaces or be of the exact same class (no inheritance logic applies).

Note that the current logic allows arrays of any type in the collection, as well as null.

Warning

This will trigger an InvalidArgumentException if the collection contains elements of mixed types when consumed.

Tip

The logic for determining the type of items comes from the TypedIterator. In addition, an optional callback can be provided to this operation if a different logic for type enforcement is desired.

Interface: Strictable

Signature: Collection::strict(?callable $callback = null): Collection;

<?php

declare(strict_types=1);

namespace App;

use Countable;
use loophp\collection\Collection;
use stdClass;

use function gettype;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> allowed

Collection::fromIterable(range(1, 3))
    ->strict()
    ->all(); // [1, 2, 3]

// Example 2 -> allowed

$obj1 = new stdClass();
$obj2 = new stdClass();
$obj3 = null;

Collection::fromIterable([$obj1, $obj2, $obj3])
    ->strict()
    ->all(); // [$obj1, $obj2, $obj3]

// Example 3 -> allowed

$obj1 = new class() implements Countable {
    public function count(): int
    {
        return 0;
    }
};

$obj2 = new class() implements Countable {
    public function count(): int
    {
        return 0;
    }
};

Collection::fromIterable([$obj1, $obj2])
    ->strict()
    ->all(); // [$obj1, $obj2]

// Example 4 -> allowed

$arr1 = [1, 2, 3];
$arr2 = ['foo' => 'bar'];

Collection::fromIterable([$arr1, $arr2])
    ->strict()
    ->all(); // [$arr1, $arr2]

// Example 5 -> not allowed

Collection::fromIterable([1, 'foo', 3])
    ->strict()
    ->all(); // InvalidArgumentException

// Example 6 -> not allowed + custom callback

$obj1 = new class() implements Countable {
    public function count(): int
    {
        return 0;
    }
};

$obj2 = new class() {
    public function count(): int
    {
        return 0;
    }
};

Collection::fromIterable([$obj1, $obj2])
    ->strict()
    ->all(); // InvalidArgumentException

Collection::fromIterable([$obj1, $obj2])
    ->strict(static fn ($value): string => gettype($value))
    ->all(); // [$obj1, $obj2]

tail

Get the collection items except the first.

Interface: Tailable

Signature: Collection::tail(): Collection;

Collection::fromIterable(['a', 'b', 'c'])
    ->tail(); // [1 => 'b', 2 => 'c']

tails

Returns the list of initial segments of the collection, shortest last. Similar to applying tail successively and collecting all results in one list.

Interface: Tailsable

Signature: Collection::tails(): Collection;

Collection::fromIterable(['a', 'b', 'c'])
    ->tails(); // [['a', 'b', 'c'], ['b', 'c'], ['c'], []]

takeWhile

Iterate over the collection items while the provided callback(s) are satisfied.

It stops iterating when the callback(s) are not met.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation.

Be careful, this operation is not the same as the filter operation.

Interface: TakeWhileable

Signature: Collection::takeWhile(callable ...$callbacks): Collection;

$isSmallerThanThree = static function (int $value): bool {
    return 3 > $value;
};

Collection::fromIterable([1,2,3,4,5,6,7,8,9,1,2,3])
    ->takeWhile($isSmallerThanThree); // [1,2]

transpose

Computes the transpose of a matrix.

Interface: Transposeable

Signature: Collection::transpose(): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

// Example 1 -> classic matrix transpose
$collection = Collection::fromIterable([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
    ->transpose();  // [1, 4, 7], [2, 5, 8], [3, 6, 9]

// Example 2 -> extract multiple keys
$records = [
    [
        'id' => 2135,
        'first_name' => 'John',
        'last_name' => 'Doe',
    ],
    [
        'id' => 3245,
        'first_name' => 'Sally',
        'last_name' => 'Smith',
    ],
    [
        'id' => 5342,
        'first_name' => 'Jane',
        'last_name' => 'Jones',
    ],
    [
        'id' => 5623,
        'first_name' => 'Peter',
        'last_name' => 'Doe',
    ],
];

$collection = Collection::fromIterable($records)
    ->transpose();

// [
//     'id' => [2135, 3245, 5432, 5623],
//     'first_name' => ['John', 'Sally', 'Jane', 'Peter'],
//     'last_name' => ['Doe', 'Smith', 'Jones', 'Doe']
// ]

truthy

Check if the collection contains only truthy values. Opposite of falsy.

A value is determined to be truthy by applying a bool cast.

Interface: Truthyable

Signature: Collection::truthy(): bool;

$result = Collection::fromIterable([2, 3, 4])
    ->truthy(); // true

$result = Collection::fromIterable(['a', '', 'c', 'd'])
    ->truthy(); // false

unlines

Opposite of lines, creates a single string from multiple lines using PHP_EOL as the glue.

Interface: Unlinesable

Signature: Collection::unlines(): string;

$lines = [
    'The quick brown fox jumps over the lazy dog.',
    '',
    'This is another sentence.',
];

Collection::fromIterable($lines)
    ->unlines();
//    'The quick brown fox jumps over the lazy dog.
//
//     This is another sentence.'

unpack

Opposite of pack, transforms groupings of items representing a key and a value into actual keys and values.

Interface: Unpackable

Signature: Collection::unpack(): Collection;

$input = [['a', 'b'], ['c', 'd'], ['e', 'f']];

$c = Collection::fromIterable($input)
    ->unpack();

// [
//     ['a' => 'b'],
//     ['c' => 'd'],
//     ['e' => 'f'],
// ];

unpair

Opposite of pair, creates a flat list of values from a collection of key-value pairs.

Interface: Unpairable

Signature: Collection::unpair(): Collection;

$input = [
    'k1' => 'v1',
    'k2' => 'v2',
    'k3' => 'v3',
    'k4' => 'v4',
];

$c = Collection::fromIterable($input)
    ->unpair(); // ['k1', 'v1', 'k2', 'v2', 'k3', 'v3', 'k4', 'v4']

until

Iterate over the collection items until the provided callback(s) are satisfied.

Warning

The callbacks parameter is variadic and will be evaluated as a logical OR. If you’re looking for a logical AND, you have to make multiple calls to the same operation.

Interface: Untilable

Signature: Collection::until(callable ...$callbacks): Collection;

// The Collatz conjecture (https://en.wikipedia.org/wiki/Collatz_conjecture)
$collatz = static fn (int $value): array => 0 === $value % 2
        ? [$value / 2]
        : [$value * 3 + 1];

$collection = Collection::unfold($collatz, [10])
    ->unwrap()
    ->until(static fn ($number): bool => 1 === $number);

unwindow

Opposite of window, usually needed after a call to that operation. Turns already-created windows back into a flat list.

Interface: Unwindowable

Signature: Collection::unwindow(): Collection;

// Drop all the items before finding five 9 in a row.
$input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18];

Collection::fromIterable($input)
    ->window(4)
    ->dropWhile(static fn (array $value): bool => $value !== [9, 9, 9, 9, 9])
    ->unwindow()
    ->drop(1)
    ->normalize(); // [10, 11, 12, 13, 14, 15, 16, 17, 18]

unwords

Opposite of words and similar to lines, creates a single string from multiple strings using one space as the glue.

Interface: Unwordsable

Signature: Collection::unwords(): string;

$words = [
    'The',
    'quick',
    'brown',
    'fox',
    'jumps',
    'over',
    'the',
    'lazy',
    'dog.'
];

Collection::fromIterable($words)
    ->unwords(); // 'The quick brown fox jumps over the lazy dog.'

unwrap

Opposite of wrap, turn a collection of arrays into a flat list. Equivalent to Collection::flatten(1).

Interface: Unwrapable

Signature: Collection::unwrap(): Collection;

$asociative = Collection::fromIterable([['a' => 'A'], ['b' => 'B'], ['c' => 'C']])
    ->unwrap(); // ['a' => 'A', 'b' => 'B', 'c' => 'C']

$list = Collection::fromIterable([['a'], ['b'], ['c']])
    ->unwrap(); // ['a', 'b', 'c']

unzip

Opposite of zip, splits zipped items in a collection.

Interface: Unzipable

Signature: Collection::unzip(): Collection;

$a = Collection::fromIterable(['a' => 'a', 'b' => 'b', 'c' => 'c'])
    ->zip(['d', 'e', 'f', 'g'], [1, 2, 3, 4, 5]);

$b = Collection::fromIterable($a)
    ->unzip(); // [ ['a','b','c',null,null], ['d','e','f','g',null], [1,2,3,4,5] ]

when

This operation will execute the given $whenTrue callback when the given $predicate callback evaluates to true. Otherwise it will execute the $whenFalse callback if any.

Unlike the ifThenElse operation where the operation is applied to each element of the collection, this operation operates on the collection directly.

Interface: Whenable

Signature: Collection::when(callable $predicate, callable $whenTrue, ?callable $whenFalse = null): Collection;

Collection::fromIterable([1, 2])
    ->when(
        static fn() => true,
        static fn(Iterator $collection): Collection => Collection::fromIterable($collection)->append(3)
    ); // [1, 2, 3]

window

Loop the collection yielding windows of data by adding a given number of items to the current item. Initially the windows yielded will be smaller, until size 1 + $size is reached.

Tip

To remove the window size constraint and have a dynamic window size, set the $size to -1.

Note

When $size is equal to 0, the window will only contain the current element, wrapped in an array.

Interface: Windowable

Signature: Collection::window(int $size): Collection;

<?php

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

include __DIR__ . '/../../../../vendor/autoload.php';

$data = range('a', 'e');

Collection::fromIterable($data)
    ->window(0); // [['a'], ['b'], ['c'], ['d'], ['e']]

Collection::fromIterable($data)
    ->window(1); // [['a'], ['a', 'b'], ['b', 'c'], ['c', 'd'], ['d', 'e']]

Collection::fromIterable($data)
    ->window(2); // [['a'], ['a', 'b'], ['a', 'b', 'c'], ['b', 'c', 'd'], ['c', 'd', 'e']]

Collection::fromIterable($data)
    ->window(-1); // [['a'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd', 'e']]

words

Get a list of words from a string, splitting based on the character set: \t, \n, ' '.

Interface: Wordsable

Signature: Collection::words(): Collection;

$string = <<<'EOF'
The quick brown fox jumps over the lazy dog.

This is another sentence.
EOF;

Collection::fromString($string)
    ->words();
// ['The', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog.', 'This', 'is', 'another', 'sentence.']

wrap

Wrap every element into an array.

Interface: Wrapable

Signature: Collection::wrap(): Collection;

$associative = Collection::fromIterable(['a' => 'A', 'b' => 'B', 'c' => 'C'])
   ->wrap(); // [['a' => 'A'], ['b' => 'B'], ['c' => 'C']]

$list = Collection::fromIterable(range('a', 'c'))
   ->wrap(); // [[0 => 'a'], [1 => 'b'], [2 => 'c']]

zip

Zip a collection together with one or more iterables.

Interface: Zipable

Signature: Collection::zip(iterable ...$iterables): Collection;

$even = Collection::range(0, INF, 2);
$odd = Collection::range(1, INF, 2);

$positiveIntegers = Collection::fromIterable($even)
    ->zip($odd)
    ->limit(100)
    ->unwrap(); // [0, 1, 2, 3 ... 196, 197, 198, 199]