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.2(2w ago)11MITPHPPHP ^8.4CI passing

Since Jun 9Pushed 2w agoCompare

[ 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 1w ago

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

 Collection 📁
==============

[](#--collection-)

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

---

Note

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. 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` or `users.*[id].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 `*[field]` 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 --prefer-dist
```

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)

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

$c = collection();
$c = collection(['apple', 'banana', 'cherry']);
```

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

// Wildcard with a custom key: `*[field]`
$c->get('users.*[id].emails.*.address');
// [1 => [0 => 'alice@a.com'], 2 => [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');
// [1337 => 'Alice', 1338 => 'Bob']

// Nested wildcards — outer key comes from the user id
$c->get('users.*[id].emails.*.address');
// [1337 => [0 => 'alice@a.com'], 1338 => [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

42

—

FairBetter than 88% of packages

Maintenance96

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity53

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

Every ~13 days

Total

3

Last Release

18d 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 (14 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.3k459.4k17](/packages/aimeos-map)[grasmash/expander

Expands internal property references in PHP arrays.

13864.3M10](/packages/grasmash-expander)[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.

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

Data collections and containers

12295.9k10](/packages/graze-data-structure)

PHPackages © 2026

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