PHPackages                             collectable/collection - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. collectable/collection

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

collectable/collection
======================

A flexible PHP collection with dot-notation and wildcard path support.

1.0.0(today)00MITPHPPHP ^8.4CI passing

Since Jun 9Pushed todayCompare

[ Source](https://github.com/chipslays/collectable)[ Packagist](https://packagist.org/packages/collectable/collection)[ Docs](https://github.com/chipslays/collectable)[ RSS](/packages/collectable-collection/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (2)Versions (2)Used By (0)

Collection
==========

[](#collection)

A flexible PHP collection with dot-notation and wildcard path support.

---

Native PHP array functions are a mess — `array_map`, `array_filter`, `usort`, `array_walk` all have inconsistent argument order, most of them don't chain, and processing nested data means writing loops inside loops. Laravel's Collection solves this elegantly, but drags the entire framework with it. Collection is a standalone, zero-dependency package that works in any PHP project.

---

Features
--------

[](#features)

- **Dot-notation &amp; wildcards.**
    - Access, write, filter, and remove deeply nested data with paths like `users.*.emails.*.address` — no more chaining `foreach` or unpacking nested arrays by hand. Wildcards work across `get`, `set`, `remove`, and most other methods, and you can key results by any field with `*[id]` syntax.
- **100+ methods, one fluent interface.**
    - Filtering (`where`, `whereIn`, `whereBetween`, `whereContains`, `whereMatches`), transformation (`map`, `evolve`, `mapPath`, `flatMap`, `scan`), aggregates (`sum`, `avg`, `median`, `standardDeviation`, `percentage`), multi-column sorting, pagination, set operations (`diff`, `intersect`, `zip`, `crossJoin`, `symmetricDiff`) — everything chains, everything returns a collection.
- **Contracts for every concern.**
    - Fourteen focused interfaces — `Filterable`, `Sortable`, `Aggregatable`, `Sliceable` and more — let you type-hint against exactly what a function needs instead of depending on the concrete class. Better for testing, better for boundaries, better for static analysis.
- **Explicit about mutation.**
    - Transforming methods return a new collection. Mutating methods (`push`, `pop`, `splice`, `transform`) are named and documented to make it obvious when you're changing state in place.
- **Pipelines built in.**
    - `pipe`, `pipeThrough`, `tap`, `when`, `unless`, `whenEmpty` let you build readable processing chains without intermediate variables or broken-up logic.
- **Macros.**
    - Add your own methods to every collection at runtime — no subclassing required.
- **ArrayAccess with dot-notation.**
    - `$c['user.address.city']` just works.

---

Installation
------------

[](#installation)

Install package via Composer:

```
composer require collectable/collection
```

Usage
-----

[](#usage)

```
use Collectable\Collection;

$helloWorld = new Collection(['hello', 'world'])
    ->map(fn($word) => strtoupper($word))
    ->join(' '); // "HELLO WORLD"
```

You also can use helper function `collection()`:

```
$helloWorld = collection(...)->map(...)->join(...);
```

---

Table of Contents
-----------------

[](#table-of-contents)

- [Creating a Collection](#creating-a-collection)
- [Reading &amp; Writing Data](#reading--writing-data)
- [Checking Existence](#checking-existence)
- [Finding Items](#finding-items)
- [Filtering](#filtering)
- [Mapping &amp; Transforming](#mapping--transforming)
- [Sorting](#sorting)
- [Grouping &amp; Keying](#grouping--keying)
- [Aggregates](#aggregates)
- [Slicing &amp; Pagination](#slicing--pagination)
- [Set Operations](#set-operations)
- [Iteration &amp; Pipelines](#iteration--pipelines)
- [Mutation](#mutation)
- [Serialization](#serialization)
- [Debugging](#debugging)
- [Dot-Notation &amp; Wildcards](#dot-notation--wildcards)
- [Wildcard with a custom key](#wildcard-with-a-custom-key-field)
- [ArrayAccess Support](#arrayaccess-support)
- [Contracts](#contracts)
- [Macros](#macros)

---

Creating a Collection
---------------------

[](#creating-a-collection)

### `make(array $items, string $wildcard = '*', string $delimiter = '.')`

[](#makearray-items-string-wildcard---string-delimiter--)

Create a collection from an array.

```
$c = Collection::make(['apple', 'banana', 'cherry']);
```

### `wrap(mixed $value)`

[](#wrapmixed-value)

Wrap any value into a collection. If it is already a collection it is returned as-is.

```
Collection::wrap('hello');       // ['hello']
Collection::wrap([1, 2, 3]);     // [1, 2, 3]
Collection::wrap($collection);   // same collection
```

### `unwrap(mixed $value)`

[](#unwrapmixed-value)

Convert a collection (or array) back to a plain PHP array.

```
Collection::unwrap($collection); // plain array
Collection::unwrap([1, 2, 3]);   // [1, 2, 3]
```

### `times(int $n, callable $callback)`

[](#timesint-n-callable-callback)

Generate a collection of `$n` items produced by a callback (index starts at 1).

```
Collection::times(5, fn($i) => $i * 2);
// [2, 4, 6, 8, 10]
```

### `range(int|float $from, $to, $step = 1)`

[](#rangeintfloat-from-to-step--1)

Create a collection of numbers in a range.

```
Collection::range(1, 5);           // [1, 2, 3, 4, 5]
Collection::range(0, 1, 0.25);     // [0, 0.25, 0.5, 0.75, 1.0]
```

### `fromJson(string $json)`

[](#fromjsonstring-json)

Create a collection from a JSON string.

```
$c = Collection::fromJson('{"name":"Alice","age":30}');
$c->get('name'); // 'Alice'
```

---

Reading &amp; Writing Data
--------------------------

[](#reading--writing-data)

### `get(?string $path, mixed $default = null)`

[](#getstring-path-mixed-default--null)

Retrieve a value by dot-notation path. Supports wildcards.

```
$c = Collection::make(['user' => ['name' => 'Alice', 'age' => 30]]);

$c->get('user.name');           // 'Alice'
$c->get('user.email', 'n/a');   // 'n/a'  (default)
```

### `set(string $path, mixed $value)`

[](#setstring-path-mixed-value)

Set a value at a dot-notation path. Creates intermediate arrays as needed.

```
$c->set('user.email', 'alice@example.com');
$c->set('user.address.city', 'Moscow');
```

### `put(string $path, mixed $value)`

[](#putstring-path-mixed-value)

Alias for `set()`.

```
$c->put('name', 'Bob');
```

### `getOrPut(string $path, mixed $default)`

[](#getorputstring-path-mixed-default)

Return the value if it exists; otherwise store and return `$default`. `$default` can be a callable — it is only called when the key is missing.

```
$token = $c->getOrPut('cache.token', fn() => bin2hex(random_bytes(16)));
```

### `pull(string $path, mixed $default = null)`

[](#pullstring-path-mixed-default--null)

Retrieve the value and then remove it from the collection.

```
$token = $c->pull('user.token'); // gets value and removes the key
```

### `remove(string $path)` / `forget(string $path)`

[](#removestring-path--forgetstring-path)

Remove an item by dot-notation path. Both methods are identical.

```
$c->remove('user.password');
$c->forget('user.token');
```

### `all()` / `toArray()`

[](#all--toarray)

Return all items as a plain PHP array.

```
$array = $c->all();
```

### `keys(?string $path = null)`

[](#keysstring-path--null)

Return all top-level keys, or keys of the array at a given path.

```
$c->keys();       // ['user', 'meta']
$c->keys('user'); // ['name', 'age']
```

### `values()`

[](#values)

Return a new collection with consecutive integer keys (re-indexed).

```
Collection::make(['a' => 1, 'b' => 2])->values()->all();
// [1, 2]
```

### `collect(?string $path)`

[](#collectstring-path)

Retrieve a path and wrap the result in a new collection — useful for chaining.

```
$c->collect('users')->where('active', true)->pluck('name');
```

---

Checking Existence
------------------

[](#checking-existence)

### `has(string $path)`

[](#hasstring-path)

Check whether a path exists and the value is **not null**.

```
$c->has('user.name');     // true
$c->has('user.nickname'); // false
```

### `hasKey(string $path)`

[](#haskeystring-path)

Check whether a path exists (even if the value is `null`).

```
$c->hasKey('user.deleted_at'); // true even when value is null
```

### `hasAny(string ...$paths)`

[](#hasanystring-paths)

Return `true` if **at least one** of the given paths exists.

```
$c->hasAny('email', 'phone'); // true if either exists
```

### `hasAll(string ...$paths)`

[](#hasallstring-paths)

Return `true` only if **all** of the given paths exist.

```
$c->hasAll('id', 'name', 'email');
```

### `isEmpty()` / `isNotEmpty()`

[](#isempty--isnotempty)

Check whether the collection has no items / has at least one item.

```
$c->isEmpty();    // false
$c->isNotEmpty(); // true
```

### `isList()`

[](#islist)

Check whether the items form a plain indexed array (0, 1, 2, …).

```
Collection::make([1, 2, 3])->isList();            // true
Collection::make(['a' => 1, 'b' => 2])->isList(); // false
```

### `isAssoc()`

[](#isassoc)

Check whether the collection is an associative array (not a list).

```
Collection::make(['a' => 1])->isAssoc(); // true
```

### `count(?string $path = null)`

[](#countstring-path--null)

Count top-level items, or items at a given path.

```
$c->count();        // number of top-level items
$c->count('users'); // count of the 'users' array
```

---

Finding Items
-------------

[](#finding-items)

### `first(?callable $callback, mixed $default = null)`

[](#firstcallable-callback-mixed-default--null)

Return the first item, or the first item satisfying a callback.

```
$c->first();                                  // first item
$c->first(fn($u) => $u['active']);            // first active user
$c->first(fn($u) => $u['age'] > 18, 'guest'); // with default
```

### `last(?callable $callback, mixed $default = null)`

[](#lastcallable-callback-mixed-default--null)

Return the last item, or the last item satisfying a callback.

```
$c->last();
$c->last(fn($u) => $u['active']);
```

### `firstOrFail(?callable $callback)`

[](#firstorfailcallable-callback)

Return the first matching item or throw `\RuntimeException` if none found.

```
$user = $users->firstOrFail(fn($u) => $u['id'] === 42);
```

### `firstWhere(string $path, $operatorOrValue, $value = null)`

[](#firstwherestring-path-operatororvalue-value--null)

Return the first item where the value at `$path` matches.

```
$users->firstWhere('role', 'admin');
$users->firstWhere('age', '>=', 18);
```

### `firstKey(?callable $callback)` / `lastKey(?callable $callback)`

[](#firstkeycallable-callback--lastkeycallable-callback)

Return the key of the first (or last) matching item.

```
$c->firstKey();                       // key of first item
$c->firstKey(fn($v) => $v['active']); // key of first active item
```

### `value(string $path, mixed $default = null)`

[](#valuestring-path-mixed-default--null)

Get the value at `$path` from the **first** item in the collection.

```
$users->value('email'); // email of the first user
```

### `find(string $path, mixed $value, mixed $default = null)`

[](#findstring-path-mixed-value-mixed-default--null)

Return the first item where `$path === $value`.

```
$users->find('id', 42);
$users->find('email', 'alice@example.com');
```

### `search(mixed $valueOrCallback)`

[](#searchmixed-valueorcallback)

Return the key of the first matching item, or `false`.

```
$c->search('Alice');
$c->search(fn($v) => $v['active']);
```

### `sole(string|callable|null $pathOrCallback = null, mixed $value = null)`

[](#solestringcallablenull-pathorcallback--null-mixed-value--null)

Return the single matching item or throw if there are zero or multiple matches.

```
$user = $users->sole('id', 42);
```

### `hasSole(...)`

[](#hassole)

Return `true` if exactly one item matches (boolean variant of `sole()`).

```
$users->hasSole('role', 'superadmin');
```

### `after(mixed $valueOrCallback, mixed $default = null)`

[](#aftermixed-valueorcallback-mixed-default--null)

Return the item immediately **after** the given value.

```
Collection::make([1, 2, 3, 4])->after(2); // 3
```

### `before(mixed $valueOrCallback, mixed $default = null)`

[](#beforemixed-valueorcallback-mixed-default--null)

Return the item immediately **before** the given value.

```
Collection::make([1, 2, 3, 4])->before(3); // 2
```

---

Filtering
---------

[](#filtering)

### `filter(string|callable $pathOrCallback, mixed $value = null)`

[](#filterstringcallable-pathorcallback-mixed-value--null)

Keep only items that satisfy a callback or a path condition.

```
$c->filter(fn($u) => $u['active']);
$c->filter('active');        // items where active is truthy
$c->filter('role', 'admin'); // items where role === 'admin'
```

### `reject(string|callable $pathOrCallback, mixed $value = null)`

[](#rejectstringcallable-pathorcallback-mixed-value--null)

Keep items that do **not** satisfy the condition (inverse of `filter()`).

```
$c->reject('active');
$c->reject('role', 'guest');
```

### `where(string $path, $operatorOrValue, $value = null)`

[](#wherestring-path-operatororvalue-value--null)

Filter by a dot-notation path with an optional comparison operator.

Supported operators: `=`, `==`, `===`, `!=`, `!==`, ``, `>`, `>=`, `where('role', 'admin');    // loose ==
$users->where('age', '>=', 18);
$users->where('status', '===', 1); // strict
```

### `whereIn(string $path, array $values)` / `whereNotIn(string $path, array $values)`

[](#whereinstring-path-array-values--wherenotinstring-path-array-values)

Filter by whether the path value is (or is not) in a list.

```
$users->whereIn('role', ['admin', 'editor']);
$users->whereNotIn('status', ['banned', 'inactive']);
```

### `whereNull(?string $path)` / `whereNotNull(?string $path)`

[](#wherenullstring-path--wherenotnullstring-path)

Filter items where the path value is (or is not) `null`.

```
$users->whereNull('deleted_at');    // soft-deleted
$users->whereNotNull('deleted_at'); // active
```

### `whereBetween(string $path, array $values)` / `whereNotBetween(...)`

[](#wherebetweenstring-path-array-values--wherenotbetween)

Filter by an inclusive range `[$min, $max]`.

```
$products->whereBetween('price', [10, 100]);
$users->whereNotBetween('age', [18, 65]);
```

### `whereContains(string $path, string $needle, bool $ignoreCase = false)`

[](#wherecontainsstring-path-string-needle-bool-ignorecase--false)

Filter items where the string value at `$path` contains `$needle`.

```
$users->whereContains('name', 'ali');
$users->whereContains('email', '@gmail', true); // case-insensitive
```

### `whereStartsWith(string $path, string $prefix, bool $ignoreCase = false)`

[](#wherestartswithstring-path-string-prefix-bool-ignorecase--false)

Filter items where the string value starts with a prefix.

```
$users->whereStartsWith('name', 'Al');
```

### `whereEndsWith(string $path, string $suffix, bool $ignoreCase = false)`

[](#whereendswithstring-path-string-suffix-bool-ignorecase--false)

Filter items where the string value ends with a suffix.

```
$users->whereEndsWith('email', '@gmail.com');
```

### `whereMatches(string $path, string $regex)`

[](#wherematchesstring-path-string-regex)

Filter items where the string value matches a regular expression.

```
$users->whereMatches('code', '/^[A-Z]{2}\d{3}$/');
```

### `whereInstanceOf(string|array $class, ?string $path = null)`

[](#whereinstanceofstringarray-class-string-path--null)

Filter items by class or interface.

```
$items->whereInstanceOf(User::class);
$items->whereInstanceOf([Admin::class, Moderator::class]);
```

### `whereStrict(string $path, mixed $value)`

[](#wherestrictstring-path-mixed-value)

Strict `===` version of `where()`.

```
$users->whereStrict('status', 1); // int 1 only, not '1'
```

### `unique(string|callable|null $by = null)` / `uniqueStrict(...)`

[](#uniquestringcallablenull-by--null--uniquestrict)

Remove duplicate items. Optionally group by a path or callback.

```
$c->unique();
$users->unique('email');
$users->unique(fn($u) => $u['domain']);
```

### `duplicates(string|callable|null $by = null)` / `duplicatesStrict(...)`

[](#duplicatesstringcallablenull-by--null--duplicatesstrict)

Return only the duplicate items.

```
$users->duplicates('email'); // users with a repeated email
```

---

Mapping &amp; Transforming
--------------------------

[](#mapping--transforming)

### `map(callable $callback)`

[](#mapcallable-callback)

Apply a callback to every item. Returns a **new** collection.

```
$prices = $c->map(fn($item) => $item['price'] * 1.2);
```

### `filterMap(callable $callback)`

[](#filtermapcallable-callback)

Map and discard `null`/`false` results in a single pass.

```
$emails = $users->filterMap(fn($u) => $u['active'] ? $u['email'] : null);
```

### `mapWithKeys(callable $callback)`

[](#mapwithkeyscallable-callback)

Rebuild the collection as key -&gt; value pairs.

```
$lookup = $users->mapWithKeys(fn($u) => [$u['id'] => $u['name']]);
// [1 => 'Alice', 2 => 'Bob']
```

### `mapKeys(callable $callback)`

[](#mapkeyscallable-callback)

Transform only the keys, keeping values unchanged.

```
$c->mapKeys(fn($k) => strtoupper($k));
```

### `mapToGroups(callable $callback)`

[](#maptogroupscallable-callback)

Map items to `[$key => $value]` pairs, then group by key.

```
$c->mapToGroups(fn($u) => [$u['department'] => $u['name']]);
```

### `mapSpread(callable $callback)` / `eachSpread(callable $callback)`

[](#mapspreadcallable-callback--eachspreadcallable-callback)

Spread each sub-array item as separate arguments.

```
Collection::make([[1, 2], [3, 4]])->mapSpread(fn($a, $b) => $a + $b);
// [3, 7]
```

### `mapInto(string $class)`

[](#mapintostring-class)

Map items into instances of `$class` (passes each item to the constructor).

```
$objects = $items->mapInto(ProductDTO::class);
```

### `mapBy(string|callable $key)` / `keyBy(...)`

[](#mapbystringcallable-key--keyby)

Re-key the collection by a path or callback (duplicate keys overwrite).

```
$byId = $users->mapBy('id');
// [1 => [...user1...], 2 => [...user2...]]
```

### `mapPath(string $path, callable $callback)`

[](#mappathstring-path-callable-callback)

Transform only the value at a specific path inside each item.

```
$c->mapPath('price', fn($p) => round($p * 1.2, 2));
$c->mapPath('meta.slug', 'strtolower');
```

### `evolve(array $transformers)`

[](#evolvearray-transformers)

Apply multiple path transformations in a single call.

```
$c->evolve([
    'price'     => fn($p) => round($p * 1.2, 2),
    'meta.slug' => 'strtolower',
    'name'      => 'trim',
]);
```

### `transform(callable $callback)`

[](#transformcallable-callback)

Like `map()` but mutates the collection **in-place**.

```
$c->transform(fn($v) => $v * 2);
```

### `flatMap(callable $callback)`

[](#flatmapcallable-callback)

Map each item and collapse the results one level.

```
$emails = $users->flatMap(fn($u) => $u['emails']);
```

### `pluck(string $path, ?string $key = null)`

[](#pluckstring-path-string-key--null)

Extract a single field from every item.

```
$names = $users->pluck('name');
$byId  = $users->pluck('name', 'id'); // [1 => 'Alice', 2 => 'Bob']
```

### `select(array $fields)`

[](#selectarray-fields)

Pick multiple fields from each item, supporting dot-notation paths.

```
$users->select(['id', 'name', 'user.email']);
```

### `only(array $keys)`

[](#onlyarray-keys)

Return a new collection with only the specified top-level keys.

```
$c->only(['id', 'name']);
```

### `except(array $keys)`

[](#exceptarray-keys)

Return a new collection with the specified top-level keys removed.

```
$c->except(['password', 'token']);
```

### `flatten(int|float $depth = INF)`

[](#flattenintfloat-depth--inf)

Flatten nested arrays into a single indexed array.

```
Collection::make([[1, [2, 3]], [4]])->flatten();
// [1, 2, 3, 4]

Collection::make([[1, [2, 3]], [4]])->flatten(1);
// [1, [2, 3], 4]
```

### `collapse()`

[](#collapse)

Collapse one level of nested arrays into a single flat array.

```
Collection::make([[1, 2], [3, 4]])->collapse()->all();
// [1, 2, 3, 4]
```

### `dot(string $prefix = '')`

[](#dotstring-prefix--)

Flatten a nested array to dot-notation key -&gt; value pairs.

```
Collection::make(['user' => ['name' => 'Alice', 'age' => 30]])->dot()->all();
// ['user.name' => 'Alice', 'user.age' => 30]
```

### `undot()`

[](#undot)

Convert dot-notation keys back to a nested array.

```
Collection::make(['user.name' => 'Alice', 'user.age' => 30])->undot()->all();
// ['user' => ['name' => 'Alice', 'age' => 30]]
```

### `flip()`

[](#flip)

Swap keys and values.

```
Collection::make(['a' => 1, 'b' => 2])->flip()->all();
// [1 => 'a', 2 => 'b']
```

### `reverse(bool $preserveKeys = false)`

[](#reversebool-preservekeys--false)

Return items in reverse order.

```
Collection::make([1, 2, 3])->reverse()->all(); // [3, 2, 1]
```

### `transpose()`

[](#transpose)

Transpose a matrix — rows become columns.

```
Collection::make([[1, 2, 3], [4, 5, 6]])->transpose()->all();
// [[1, 4], [2, 5], [3, 6]]
```

### `scan(callable $callback, mixed $initial = null)`

[](#scancallable-callback-mixed-initial--null)

Running fold — like `reduce()` but returns every intermediate value.

```
Collection::make([1, 2, 3, 4])->scan(fn($carry, $v) => $carry + $v, 0)->all();
// [1, 3, 6, 10]
```

### `reduce(callable $callback, mixed $initial = null)`

[](#reducecallable-callback-mixed-initial--null)

Reduce the collection to a single value.

```
$total = $c->reduce(fn($carry, $item) => $carry + $item, 0);
```

### `toTree(string $idKey, string $parentKey, string $childrenKey = 'children', $rootId = null)`

[](#totreestring-idkey-string-parentkey-string-childrenkey--children-rootid--null)

Build a nested tree structure from a flat list.

```
$tree = $categories->toTree('id', 'parent_id');
// Each item gets a 'children' key containing its sub-items
```

---

Sorting
-------

[](#sorting)

### `sort(?callable $callback = null)`

[](#sortcallable-callback--null)

Sort items. With no callback uses natural ordering.

```
$c->sort();
$c->sort(fn($a, $b) => $a  $b);
```

### `sortDesc(?callable $callback = null)`

[](#sortdesccallable-callback--null)

Sort in descending order.

```
$c->sortDesc();
```

### `sortBy(string|callable|array $by, bool $desc = false)`

[](#sortbystringcallablearray-by-bool-desc--false)

Sort items by a dot-notation path, callback, or multiple columns.

```
$users->sortBy('name');
$users->sortBy('age', desc: true);
$products->sortBy(['category', 'price' => 'desc']);
```

### `sortByDesc(string|callable $by)`

[](#sortbydescstringcallable-by)

Shorthand for `sortBy($by, desc: true)`.

```
$products->sortByDesc('price');
```

### `sortKeys(?callable $callback = null)` / `ksort(?callable $callback = null)`

[](#sortkeyscallable-callback--null--ksortcallable-callback--null)

Sort by key.

```
$c->sortKeys();
```

### `sortKeysDesc()`

[](#sortkeysdesc)

Sort by key in descending order.

```
$c->sortKeysDesc();
```

### `sortKeysUsing(callable $comparator)`

[](#sortkeysusingcallable-comparator)

Sort by key using a custom comparator.

```
$c->sortKeysUsing('strnatcasecmp');
```

---

Grouping &amp; Keying
---------------------

[](#grouping--keying)

### `groupBy(string|callable $by)`

[](#groupbystringcallable-by)

Group items into sub-collections by a path or callback.

```
$byRole = $users->groupBy('role');
// ['admin' => Collection, 'editor' => Collection]

$users->groupBy(fn($u) => $u['active'] ? 'active' : 'inactive');
```

### `countBy(string|callable|null $by = null)`

[](#countbystringcallablenull-by--null)

Count occurrences per group.

```
$c->countBy();             // count each unique value
$users->countBy('role');   // ['admin' => 2, 'editor' => 5]
```

### `partition(callable $callback)`

[](#partitioncallable-callback)

Split the collection into two: passing and failing items.

```
[$admins, $others] = $users->partition(fn($u) => $u['role'] === 'admin');
```

---

Aggregates
----------

[](#aggregates)

### `sum(string|callable|null $pathOrCallback = null)`

[](#sumstringcallablenull-pathorcallback--null)

Sum all items, a plucked path, or a computed value.

```
$c->sum();
$orders->sum('total');
$orders->sum(fn($o) => $o['price'] * $o['qty']);
```

### `product(string|callable|null $pathOrCallback = null)`

[](#productstringcallablenull-pathorcallback--null)

Multiply all items together.

```
$c->product();
$items->product('qty');
```

### `avg(string|callable|null $pathOrCallback = null)` / `average(...)`

[](#avgstringcallablenull-pathorcallback--null--average)

Return the average value.

```
$scores->avg('score');
```

### `min(string|callable|null $pathOrCallback = null)` / `max(...)`

[](#minstringcallablenull-pathorcallback--null--max)

Return the minimum or maximum value.

```
$products->min('price');
$products->max('price');
```

### `minBy(string|callable $by)` / `maxBy(string|callable $by)`

[](#minbystringcallable-by--maxbystringcallable-by)

Return the **item** with the minimum or maximum value at a path.

```
$cheapest = $products->minBy('price'); // returns the whole item
$priciest = $products->maxBy('price');
```

### `median(?string $path = null)`

[](#medianstring-path--null)

Return the median value.

```
$scores->median('score');
```

### `mode(?string $path = null)`

[](#modestring-path--null)

Return the most frequently occurring value(s).

```
$c->mode('score'); // Collection of the most common scores
```

### `standardDeviation(?string $path = null, bool $sample = false)`

[](#standarddeviationstring-path--null-bool-sample--false)

Return the population (or sample) standard deviation.

```
$scores->standardDeviation('score');
$scores->standardDeviation('score', sample: true);
```

### `percentage(callable $callback, int $precision = 2)`

[](#percentagecallable-callback-int-precision--2)

Return the percentage of items passing a test (0–100.0).

```
$users->percentage(fn($u) => $u['active']); // e.g. 75.50
```

### `contains(mixed $pathOrValueOrCallback, mixed $value = null)`

[](#containsmixed-pathorvalueorcallback-mixed-value--null)

Check if the collection contains a value, or a path matches a value.

```
$c->contains('Alice');
$users->contains('role', 'admin');
$c->contains(fn($v) => $v > 10);
```

### `doesntContain(...)` / `some(...)` / `containsStrict(...)`

[](#doesntcontain--some--containsstrict)

Variants of `contains()` — inverse, alias, and strict comparison.

```
$c->doesntContain('admin');
$users->some('role', 'admin');
$users->containsStrict('status', 1);
```

### `every(string|callable $pathOrCallback, mixed $value = null)`

[](#everystringcallable-pathorcallback-mixed-value--null)

Check if **all** items satisfy a condition.

```
$users->every(fn($u) => $u['active']);
$users->every('role', 'admin');
```

---

Slicing &amp; Pagination
------------------------

[](#slicing--pagination)

### `slice(int $offset, ?int $length = null)`

[](#sliceint-offset-int-length--null)

Return a sub-range of items.

```
$c->slice(0, 10); // first 10 items
$c->slice(-3);    // last 3 items
```

### `take(int $n)`

[](#takeint-n)

Return the first `$n` (or last, if negative) items.

```
$c->take(3);  // first 3
$c->take(-2); // last 2
```

### `skip(int $n)`

[](#skipint-n)

Skip the first `$n` items.

```
$c->skip(2); // [3, 4, 5] from [1, 2, 3, 4, 5]
```

### `forPage(int $page, int $perPage)`

[](#forpageint-page-int-perpage)

Return items for a specific page (1-based).

```
$c->forPage(2, 15); // items 16–30
```

### `paginate(int $perPage, int $page = 1)`

[](#paginateint-perpage-int-page--1)

Return a pagination envelope with metadata.

```
$result = $users->paginate(15, 2);
// ['data' => Collection, 'total' => 100, 'per_page' => 15,
//  'current_page' => 2, 'last_page' => 7, 'from' => 16, 'to' => 30]
```

### `chunk(int $size)`

[](#chunkint-size)

Split the collection into chunks of `$size`. Returns a collection of collections.

```
$c->chunk(3); // [[1,2,3], [4,5,6], [7]]
```

### `chunkWhile(callable $callback)`

[](#chunkwhilecallable-callback)

Chunk items into consecutive groups while the condition holds.

```
Collection::make([1, 2, 3, 7, 8, 11])
    ->chunkWhile(fn($v, $prev) => $v === $prev + 1);
// [[1,2,3], [7,8], [11]]
```

### `sliding(int $size, int $step = 1)`

[](#slidingint-size-int-step--1)

Create a sliding window over the collection.

```
Collection::make([1, 2, 3, 4, 5])->sliding(3)->map->all();
// [[1,2,3], [2,3,4], [3,4,5]]
```

### `split(int $n)` / `splitIn(int $n)`

[](#splitint-n--splitinint-n)

Split into `$n` roughly equal groups.

```
Collection::make([1, 2, 3, 4, 5])->split(3);
// [[1, 2], [3, 4], [5]]
```

### `nth(int $step, int $offset = 0)`

[](#nthint-step-int-offset--0)

Return every `$step`-th item.

```
$c->nth(2);    // 1st, 3rd, 5th …
$c->nth(3, 1); // 2nd, 5th, 8th …
```

### `pad(int $size, mixed $value = null)`

[](#padint-size-mixed-value--null)

Pad the collection to `$size` with a value. Negative pads at the start.

```
Collection::make([1, 2])->pad(5, 0)->all(); // [1, 2, 0, 0, 0]
Collection::make([1, 2])->pad(-5, 0)->all(); // [0, 0, 0, 1, 2]
```

---

Set Operations
--------------

[](#set-operations)

### `merge(array|Collection $items)`

[](#mergearraycollection-items)

Merge another array or collection (top-level).

```
$c->merge(['d', 'e']);
```

### `mergeRecursive(array|Collection $items)`

[](#mergerecursivearraycollection-items)

Deep-merge arrays — repeated string keys produce nested arrays.

```
$c->mergeRecursive(['meta' => ['extra' => true]]);
```

### `replace(array|Collection $items)` / `replaceRecursive(...)`

[](#replacearraycollection-items--replacerecursive)

Replace values using PHP's `array_replace` semantics.

```
$c->replace([0 => 'x']);
$c->replaceRecursive(['user' => ['name' => 'Bob']]);
```

### `concat(array|Collection $items)`

[](#concatarraycollection-items)

Append all values, always re-indexing (numeric keys never overwrite).

```
$c->concat(['d', 'e']);
```

### `union(array|Collection $items)`

[](#unionarraycollection-items)

Fill in missing keys from `$items` without overwriting existing ones.

```
$c->union(['a' => 1, 'b' => 99]); // 'b' added only if not present
```

### `diff(array|Collection $items)`

[](#diffarraycollection-items)

Return items not present in `$items`.

```
Collection::make([1, 2, 3, 4])->diff([2, 4])->all(); // [1, 3]
```

### `diffAssoc(...)` / `diffKeys(...)`

[](#diffassoc--diffkeys)

Diff comparing both key+value or keys only.

```
$c->diffAssoc(['a' => 1]);
$c->diffKeys(['a' => 'x', 'c' => 'y']);
```

### `diffUsing(array|Collection $items, callable $comparator)`

[](#diffusingarraycollection-items-callable-comparator)

Diff using a custom comparator function.

```
$objects->diffUsing($other, fn($a, $b) => $a->id  $b->id);
```

### `intersect(array|Collection $items)`

[](#intersectarraycollection-items)

Return items present in **both** collections.

```
Collection::make([1, 2, 3])->intersect([2, 3, 4])->all(); // [2, 3]
```

### `intersectByKeys(...)` / `intersectAssoc(...)` / `intersectUsing(...)`

[](#intersectbykeys--intersectassoc--intersectusing)

Intersection variants by keys, key+value, or custom comparator.

### `symmetricDiff(array|Collection $items)`

[](#symmetricdiffarraycollection-items)

Return items present in one collection but not both (A ∖ B) ∪ (B ∖ A).

```
$old->symmetricDiff($new); // items that were added or removed
```

### `zip(array ...$arrays)`

[](#ziparray-arrays)

Pair items from multiple arrays together.

```
Collection::make([1, 2, 3])->zip(['a', 'b', 'c'])->all();
// [[1,'a'], [2,'b'], [3,'c']]
```

### `unzip()`

[](#unzip)

Unzip a collection of tuples into separate collections (inverse of `zip()`).

```
[$numbers, $letters] = Collection::make([[1,'a'],[2,'b']])->unzip();
```

### `crossJoin(array ...$arrays)`

[](#crossjoinarray-arrays)

Return the cartesian product.

```
Collection::make([1, 2])->crossJoin(['a', 'b'])->all();
// [[1,'a'], [1,'b'], [2,'a'], [2,'b']]
```

### `combine(array|Collection $values)`

[](#combinearraycollection-values)

Use this collection as keys and `$values` as values.

```
Collection::make(['a', 'b'])->combine([1, 2])->all();
// ['a' => 1, 'b' => 2]
```

### `multiply(int $times)`

[](#multiplyint-times)

Repeat every item `$times` times.

```
Collection::make([1, 2])->multiply(3)->all(); // [1, 2, 1, 2, 1, 2]
```

### `shuffle()`

[](#shuffle)

Return a new collection with items in random order.

```
$c->shuffle();
```

### `random(int $n = 1)`

[](#randomint-n--1)

Return `$n` random items. Returns the item directly when `$n === 1`.

```
$c->random();  // one random item
$c->random(3); // Collection of 3 random items
```

---

Iteration &amp; Pipelines
-------------------------

[](#iteration--pipelines)

### `each(callable $callback)`

[](#eachcallable-callback)

Iterate over every item. Return `false` to stop early.

```
$c->each(function ($value, $key) {
    echo "$key: $value\n";
});
```

### `eachSpread(callable $callback)`

[](#eachspreadcallable-callback)

Iterate by spreading each sub-array as arguments.

```
Collection::make([['Alice', 30], ['Bob', 25]])
    ->eachSpread(fn($name, $age) => print "$name is $age\n");
```

### `takeUntil(mixed $valueOrCallback)`

[](#takeuntilmixed-valueorcallback)

Take items until the condition is met.

```
Collection::make([1, 2, 3, 4, 5])->takeUntil(fn($v) => $v > 3)->all();
// [1, 2, 3]
```

### `takeWhile(mixed $valueOrCallback)`

[](#takewhilemixed-valueorcallback)

Take items while the condition holds.

```
Collection::make([1, 2, 3, 4, 5])->takeWhile(fn($v) => $v < 4)->all();
// [1, 2, 3]
```

### `skipUntil(mixed $valueOrCallback)`

[](#skipuntilmixed-valueorcallback)

Skip items until the condition is met, then return the rest.

```
Collection::make([1, 2, 3, 4, 5])->skipUntil(fn($v) => $v >= 3)->all();
// [3, 4, 5]
```

### `skipWhile(mixed $valueOrCallback)`

[](#skipwhilemixed-valueorcallback)

Skip items while the condition holds, then return the rest.

```
Collection::make([1, 2, 3, 4, 5])->skipWhile(fn($v) => $v < 3)->all();
// [3, 4, 5]
```

### `pipe(callable $callback)`

[](#pipecallable-callback)

Pass the collection to a callback and return whatever the callback returns.

```
$total = $c->pipe(fn($c) => $c->sum('price'));
```

### `pipeInto(string $class)`

[](#pipeintostring-class)

Pass the collection as the first argument to a class constructor.

```
$presenter = $c->pipeInto(UserPresenter::class);
```

### `pipeThrough(array $pipes)`

[](#pipethrougharray-pipes)

Pass the collection through an ordered array of callables.

```
$result = $c->pipeThrough([
    fn($c) => $c->filter('active'),
    fn($c) => $c->sortBy('name'),
    fn($c) => $c->values(),
]);
```

### `tap(callable $callback)`

[](#tapcallable-callback)

Call a callback for side-effects, then return `$this` (stays in chain).

```
$c->filter('active')
  ->tap(fn($c) => logger()->info('Active users: ' . $c->count()))
  ->sortBy('name');
```

### `when(bool|callable $condition, callable $then, ?callable $else = null)`

[](#whenboolcallable-condition-callable-then-callable-else--null)

Execute `$then` when the condition is truthy.

```
$c->when($isAdmin, fn($c) => $c->merge($adminItems));
$c->when(fn($c) => $c->isNotEmpty(), fn($c) => $c->sortBy('name'));
```

### `unless(bool|callable $condition, callable $then, ?callable $else = null)`

[](#unlessboolcallable-condition-callable-then-callable-else--null)

Execute `$then` when the condition is **falsy** (inverse of `when()`).

```
$c->unless($c->isEmpty(), fn($c) => $c->sortBy('name'));
```

### `whenEmpty(callable $then)` / `whenNotEmpty(callable $then)`

[](#whenemptycallable-then--whennotemptycallable-then)

Conditional execution based on whether the collection is empty.

```
$c->whenEmpty(fn($c) => $c->push('default'));
$c->whenNotEmpty(fn($c) => $c->sortBy('name'));
```

### `ensure(string|array $types)`

[](#ensurestringarray-types)

Assert that every item is of the expected type; throw otherwise.

```
$c->ensure('int');
$c->ensure(['int', 'float']);
$c->ensure(User::class);
```

---

Mutation
--------

[](#mutation)

### `push(mixed $value)`

[](#pushmixed-value)

Append a value to the end.

```
$c->push('new item');
```

### `prepend(mixed $value, mixed $key = null)`

[](#prependmixed-value-mixed-key--null)

Add a value to the beginning.

```
$c->prepend(0);
$c->prepend('home', 'first');
```

### `insert(int $index, mixed $value)`

[](#insertint-index-mixed-value)

Insert a value at a specific index (non-mutating, returns new collection).

```
$c->insert(2, 'x');  // inject at position 2
$c->insert(-1, 'x'); // before the last item
```

### `pop()`

[](#pop)

Remove and return the last item.

```
$last = $c->pop();
```

### `shift()`

[](#shift)

Remove and return the first item.

```
$first = $c->shift();
```

### `splice(int $offset, ?int $length = null, array $replacement = [])`

[](#spliceint-offset-int-length--null-array-replacement--)

Remove items starting at `$offset`, optionally replacing them. Mutates the collection and returns the removed items as a new collection.

```
$removed = $c->splice(2);            // removes from index 2 to end
$removed = $c->splice(1, 2);         // removes 2 items at index 1
$removed = $c->splice(1, 2, ['x']);  // removes 2 and inserts 'x'
```

### `clear()`

[](#clear)

Remove all items from the collection.

```
$c->clear();
```

### `copy()`

[](#copy)

Return a deep clone of the collection.

```
$clone = $c->copy();
```

### `join(string $glue, ?string $finalGlue = null)` / `implode(string $value, ?string $glue = null)`

[](#joinstring-glue-string-finalglue--null--implodestring-value-string-glue--null)

Join all items into a string.

```
$c->join(', '); // 'Alice, Bob, Carol'
$c->join(', ', ' and '); // 'Alice, Bob and Carol'

$users->implode('name', ', '); // pluck 'name' then join
$c->implode(', '); // join scalar items
```

---

Serialization
-------------

[](#serialization)

### `toJson(int $flags = 0)` / `toPrettyJson()`

[](#tojsonint-flags--0--toprettyjson)

Convert the collection to a JSON string.

```
$c->toJson();
$c->toPrettyJson();
```

### `fromJson(string $json)` *(static)*

[](#fromjsonstring-json-static)

Create a collection from a JSON string.

```
$c = Collection::fromJson('{"a":1,"b":2}');
```

### `__toString()`

[](#__tostring)

Automatically called when the collection is cast to a string (returns JSON).

```
echo $c; // prints JSON
```

---

Debugging
---------

[](#debugging)

### `dump()`

[](#dump)

Dump the collection contents and return `$this` (stays in chain).

```
$c->filter('active')->dump()->sortBy('name');
```

### `dd()`

[](#dd)

Dump the collection and **terminate execution**.

```
$c->dd();
```

---

Dot-Notation &amp; Wildcards
----------------------------

[](#dot-notation--wildcards)

The collection understands dot-notation paths for deeply nested data and supports `*` wildcards to operate on multiple items at once.

```
$c = Collection::make([
    'users' => [
        ['id' => 1, 'name' => 'Alice', 'role' => 'admin',  'emails' => [['address' => 'alice@a.com']]],
        ['id' => 2, 'name' => 'Bob',   'role' => 'editor', 'emails' => [['address' => 'bob@b.com'], ['address' => 'bob2@b.com']]],
    ],
]);

// Read a single path
$c->get('users.0.name'); // 'Alice'

// Wildcard: get all names (keyed by array index)
$c->get('users.*.name'); // [0 => 'Alice', 1 => 'Bob']

// Nested wildcards: all email addresses
$c->get('users.*.emails.*.address');
// [0 => [0 => 'alice@a.com'], 1 => [0 => 'bob@b.com', 1 => 'bob2@b.com']]

// Set a value on every item
$c->set('users.*.active', true);

// Remove a field from every item
$c->remove('users.*.role');
```

### Wildcard with a custom key: `*[field]`

[](#wildcard-with-a-custom-key-field)

By default `*` uses the numeric array index as the result key. You can instruct it to use the value of a specific **field** from each item instead by writing `*[field]` in the path segment.

```
// Get names keyed by user id instead of 0, 1, 2 …
$c->get('users.*[id].name');
// [1 => 'Alice', 2 => 'Bob']

// Nested wildcards — outer key comes from the user id
$c->get('users.*[id].emails.*.address');
// [1 => [0 => 'alice@a.com'], 2 => [0 => 'bob@b.com', 1 => 'bob2@b.com']]
```

> If the specified key field is missing from an item, that item falls back to its numeric index in the result.
>
> Note: the key field name in `*[field]` must be a simple top-level field (not a dot-notation path), because the dot delimiter is also used inside the brackets.

---

ArrayAccess Support
-------------------

[](#arrayaccess-support)

The collection implements `ArrayAccess`, so you can use bracket syntax with dot-notation paths.

```
$c['user.name'];           // same as get('user.name')
$c['user.name'] = 'Alice'; // same as set('user.name', 'Alice')
isset($c['user.name']);    // same as has('user.name')
unset($c['user.name']);    // same as remove('user.name')
```

---

Contracts
---------

[](#contracts)

Every aspect of `Collection` is described by a focused PHP interface in `Collectable\Contracts`. You can type-hint against any single interface instead of the concrete class — useful for testing, custom implementations, and keeping dependencies minimal.

```
Collectable\Contracts
├── Collectable          toUpperCase()->all();
// ['HELLO', 'WORLD']
```

See `Macroable` [the source code](src/Concerns/Macroable.php) for the full list of methods.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/19103498?v=4)[chipslays](/maintainers/chipslays)[@chipslays](https://github.com/chipslays)

---

Top Contributors

[![chipslays](https://avatars.githubusercontent.com/u/19103498?v=4)](https://github.com/chipslays "chipslays (6 commits)")

---

Tags

arraycollectiondot-notationwildcardarraycollectiondot notationwildcard

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/collectable-collection/health.svg)

```
[![Health](https://phpackages.com/badges/collectable-collection/health.svg)](https://phpackages.com/packages/collectable-collection)
```

###  Alternatives

[aimeos/map

Easy and elegant handling of PHP arrays as array-like collection objects similar to jQuery and Laravel Collections

4.3k443.7k14](/packages/aimeos-map)[athari/yalinqo

YaLinqo, a LINQ-to-objects library for PHP

4581.2M5](/packages/athari-yalinqo)[yansongda/supports

common components

211.4M32](/packages/yansongda-supports)[armincms/json

A Laravel Nova field.

25153.6k3](/packages/armincms-json)[graze/data-structure

Data collections and containers

12293.1k9](/packages/graze-data-structure)[graze/sort

A collection of array sorting transforms and functions

11295.5k2](/packages/graze-sort)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
