API

Static 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::fromIterable(string $filepath): Collection;

Collection::fromFile('http://loripsum.net/api');

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 behavior 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(); // [2 => 3, 3 => 4, 4 => 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;

$fibonacci = static function ($a = 0, $b = 1): array {
    return [$b, $a + $b];
};

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

Another example

$even = Collection::unfold(static function ($carry) {return $carry + 2;}, -2);
$odd = Collection::unfold(static function ($carry) {return $carry + 2;}, -1);
// Is the same as
$even = Collection::range(0, \INF, 2);
$odd = Collection::range(1, \INF, 2);

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 return values are reused as callback arguments at the next callback call.

Signature: Collection::unfold(callable $callback, ...$parameters): Collection;

// A list of Naturals from 1 to Infinity.
Collection::unfold(fn($n) => $n + 1, 1)
    ->normalize();
$fibonacci = static function ($a = 0, $b = 1): array {
    return [$b, $a + $b];
};

Collection::unfold($fibonacci)
    ->limit(10); // [[0, 1], [1, 1], [1, 2], [2, 3], [3, 5], [5, 8], [8, 13], [13, 21], [21, 34], [34, 55]]

Another example

$even = Collection::unfold(static function (int $carry): int {return $carry + 2;}, -2);
$odd = Collection::unfold(static function (int $carry): int {return $carry + 2;}, -1);
// Is the same as
$even = Collection::range(0, \INF, 2);
$odd = Collection::range(1, \INF, 2);

Methods (operations)

Background

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

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace App;

use ArrayIterator;
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)(new ArrayIterator($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.

Warning

This is a lossy operation because PHP array keys cannot be duplicated and must either be int or string. If you want to ensure no data is lost in the case of duplicate keys, look at the Collection::normalize() operation.

Interface: Allable

Signature: Collection::all(): array;

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace App;

use ArrayIterator;
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 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()); // [0 => 'c', 1 => 'b', 2 => 'd']

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

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

append

Add one or more items to a collection.

Warning

If appended values overwrite existing values, you might find that this operation doesn’t work correctly when the collection is converted into an array. It’s always better to never convert the collection to an array and use it in a loop. However, if for some reason, you absolutely need to convert it into an array, then use the Collection::normalize() operation.

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'); // [0 => 5, 1 => 6, 2 => 3]

Collection::fromIterable(['1', '2', '3'])
    ->append('4')
    ->append('5', '6')
    ->normalize(); // ['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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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 function ($key, $value) {
            return $key * 2;
        },
        static function ($value, $key) {
            return $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 function ($key, $value) {
            return $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 function ($value, $key) {
            return $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]

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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']

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace App;

use loophp\collection\Collection;

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

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

$result = Collection::fromIterable(range('a', 'c'))
    ->contains('a', 'z'); // true

$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

current

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

Interface: Currentable

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

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

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']

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $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
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $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
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $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()
    ); // [2 => User<foo>]

// Example 3 -> Using a custom accessor callback, with object values
final class Person
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $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()
    ); // [2 => Person<foo>]

// Example 4 -> Using both accessor and comparator callbacks, with object values
final class Cat
{
    private string $name;

    public function __construct(string $name)
    {
        $this->name = $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()
    ); // [3 => Cat<booba>]

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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;

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

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

$collection = Collection::fromIterable(range(1, 10))
    ->filter($divisibleBy2)
    ->filter($divisibleBy3); // [6]

first

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

The current operation can then be used to extract the item out of the collection.

Interface: Firstable

Signature: Collection::first(): 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())
    ->first()
    ->current(); // ['a' => '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 list 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.

Interface: FoldLeftable

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

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

            return $carry;
        },
        ''
    ); // [2 => 'ABC']

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): Collection;

Collection::fromIterable([64, 4, 2, 8])
    ->foldLeft1(static fn(float $carry, float $value): float => $carry / $value); // [3 => 1.0]

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 = null): Collection;

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

            return $carry;
        },
        ''
    ); // [0 => '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): Collection;

Collection::fromIterable([8, 12, 24, 4])
    ->foldLeft1(static fn(float $carry, float $value): float => $carry / $value); // [0 => 4.0]

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(); // [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) // [1 => 'b']

Collection::fromIterable(range('a', 'c'))->get(4, '') // [0 => '']

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 their keys.

The default behaviour can be customized with a callback.

Interface: GroupByable

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

$callback = static function () {
        yield 1 => 'a';
        yield 1 => 'b';
        yield 1 => 'c';
        yield 2 => 'd';
        yield 2 => 'e';
        yield 3 => 'f';
};

$collection = Collection::fromIterable($callback())
    ->groupBy(); // [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.

Tip

Internally this operation uses foldLeft, which is why the result will have the last element’s key.

Interface: Implodeable

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

Collection::fromIterable(range('a', 'c'))
    ->implode('-'); // [2 => '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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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 any elements inside.

Interface: IsEmptyable

Signature: Collection::isEmpty(): bool;

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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

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.

The current operation can then be used to extract the item out of the collection.

Interface: Lastable

Signature: Collection::last(): 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()
    ->current(); // ['c' => '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 prefered 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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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)
            )
    );

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(); // ['d', 'e', 'c'] -> 'a' and 'b' are lost due to key overlap
$collection->normalize()->all() // ['a', 'b', 'c', 'd', 'e']

normalize

Replace, reorder and use numeric keys on a collection.

Note

If you want to retrieve collection elements as an array via Collection::all() instead of consuming the collection through a foreach, most often you will want to use this method before the array transformation in order to prevent data loss.

Interface: Normalizeable

Signature: Collection::normalize(): Collection;

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace App;

use ArrayIterator;
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());
/*
[
    ['f', 6],
    ['g', 7],
    ['h', 8],
    ['i', 9],
]
 */

// Numbers that are not greater than 5
print_r($right->all());
/*
[
    ['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()
    ->current();

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

// Example 3 -> Use Partition operation separately
[$left] = iterator_to_array(Partition::of()($isGreaterThan(5))(new ArrayIterator($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;

$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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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

If prepended values overwrite existing values or keys, you might find that this operation doesn’t work correctly when the collection is converted into an array. It’s always better to never convert the collection to an array and use it in a loop. However, if for some reason, you absolutely need to convert it into an array, then use the Collection::normalize() operation.

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(); // [0 => 1, 1 => 2, 2 => 3]

Collection::fromIterable(['1', '2', '3'])
    ->prepend('4')
    ->prepend('5', '6')
    ->normalize()
    ->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.

Interface: Reduceable

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

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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::empty()
    ->reduce($callback); // []

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

reduction

Reduce a collection of items through a given callback and yield each intermediary results.

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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;

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

$collection = Collection::fromIterable(range(1, 10))
    ->reject($divisibleBy2, $divisibleBy3); // [1, 5, 7]

$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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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.

Interface: ScanLeftable

Signature: Collection::scanLeft(callable $callback, $initial = null): 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]

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.

Interface: ScanRightable

Signature: Collection::scanRight(callable $callback, $initial = null): 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]

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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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 => false !== strpos($line, 'require-dev'))
    // Skip items after the string "}" is found.
    ->until(static fn ($line): bool => false !== strpos($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 => false === strpos($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.

By default, it will sort by values and using a callback. If you want to sort by keys, you can pass a parameter to change the behavior or use twice the flip operation. See the example below.

Interface: Sortable

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

<?php

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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 ($left, $right): int => $left <=> $right
    ); // [2 => 'x', 1 => 'y', 0 => 'z']

// Example 3 -> 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 4 -> 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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace App;

use ArrayIterator;
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()
    ->current();

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)(new ArrayIterator($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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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 function (string $document): bool {
            return false !== strpos($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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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 alowed + 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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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(): Collection;

$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 function (int $value): int
{
    return 0 === $value % 2 ?
        $value / 2:
        $value * 3 + 1;
};

$collection = Collection::unfold($collatz, 10)
    ->until(static function ($number): bool {
        return 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(): Collection;

$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

/**
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

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]