collectable / collection
A flexible PHP collection with dot-notation and wildcard path support.
Requires
- php: ^8.4
Requires (Dev)
- pestphp/pest: ^3.0 || ^4.0
- symfony/var-dumper: *
README
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
- Dot-notation & wildcards.
- Access, write, filter, and remove deeply nested data with paths like
users.*.emails.*.addressorusers.*[id].emails.*.address— no more chainingforeachor unpacking nested arrays by hand. Wildcards work acrossget,set,remove, and most other methods, and you can key results by any field with*[field]syntax.
- Access, write, filter, and remove deeply nested data with paths like
- 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.
- Filtering (
- Contracts for every concern.
- Fourteen focused interfaces —
Filterable,Sortable,Aggregatable,Sliceableand 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.
- Fourteen focused interfaces —
- 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.
- Transforming methods return a new collection. Mutating methods (
- Pipelines built in.
pipe,pipeThrough,tap,when,unless,whenEmptylet 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
Install package via Composer:
composer require collectable/collection --prefer-dist
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
- Creating a Collection
- Reading & Writing Data
- Checking Existence
- Finding Items
- Filtering
- Mapping & Transforming
- Sorting
- Grouping & Keying
- Aggregates
- Slicing & Pagination
- Set Operations
- Iteration & Pipelines
- Mutation
- Serialization
- Debugging
- Dot-Notation & Wildcards
- Wildcard with a custom key
- ArrayAccess Support
- Contracts
- Macros
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 = '.')
Create a collection from an array.
$c = Collection::make(['apple', 'banana', 'cherry']);
wrap(mixed $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)
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)
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)
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)
Create a collection from a JSON string.
$c = Collection::fromJson('{"name":"Alice","age":30}'); $c->get('name'); // 'Alice'
Reading & Writing Data
get(?string $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)
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)
Alias for set().
$c->put('name', 'Bob');
getOrPut(string $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)
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)
Remove an item by dot-notation path. Both methods are identical.
$c->remove('user.password'); $c->forget('user.token');
all() / toArray()
Return all items as a plain PHP array.
$array = $c->all();
keys(?string $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()
Return a new collection with consecutive integer keys (re-indexed).
Collection::make(['a' => 1, 'b' => 2])->values()->all(); // [1, 2]
collect(?string $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
has(string $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)
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)
Return true if at least one of the given paths exists.
$c->hasAny('email', 'phone'); // true if either exists
hasAll(string ...$paths)
Return true only if all of the given paths exist.
$c->hasAll('id', 'name', 'email');
isEmpty() / isNotEmpty()
Check whether the collection has no items / has at least one item.
$c->isEmpty(); // false $c->isNotEmpty(); // true
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()
Check whether the collection is an associative array (not a list).
Collection::make(['a' => 1])->isAssoc(); // true
count(?string $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
first(?callable $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)
Return the last item, or the last item satisfying a callback.
$c->last(); $c->last(fn($u) => $u['active']);
firstOrFail(?callable $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)
Return the first item where the value at $path matches.
$users->firstWhere('role', 'admin'); $users->firstWhere('age', '>=', 18);
firstKey(?callable $callback) / lastKey(?callable $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)
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)
Return the first item where $path === $value.
$users->find('id', 42); $users->find('email', 'alice@example.com');
search(mixed $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)
Return the single matching item or throw if there are zero or multiple matches.
$user = $users->sole('id', 42);
hasSole(...)
Return true if exactly one item matches (boolean variant of sole()).
$users->hasSole('role', 'superadmin');
after(mixed $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)
Return the item immediately before the given value.
Collection::make([1, 2, 3, 4])->before(3); // 2
Filtering
filter(string|callable $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)
Keep items that do not satisfy the condition (inverse of filter()).
$c->reject('active'); $c->reject('role', 'guest');
where(string $path, $operatorOrValue, $value = null)
Filter by a dot-notation path with an optional comparison operator.
Supported operators: =, ==, ===, !=, !==, <>, >, >=, <, <=
$users->where('active'); $users->where('role', 'admin'); // loose == $users->where('age', '>=', 18); $users->where('status', '===', 1); // strict
whereIn(string $path, array $values) / whereNotIn(string $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)
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(...)
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)
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)
Filter items where the string value starts with a prefix.
$users->whereStartsWith('name', 'Al');
whereEndsWith(string $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)
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)
Filter items by class or interface.
$items->whereInstanceOf(User::class); $items->whereInstanceOf([Admin::class, Moderator::class]);
whereStrict(string $path, mixed $value)
Strict === version of where().
$users->whereStrict('status', 1); // int 1 only, not '1'
unique(string|callable|null $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(...)
Return only the duplicate items.
$users->duplicates('email'); // users with a repeated email
Mapping & Transforming
map(callable $callback)
Apply a callback to every item. Returns a new collection.
$prices = $c->map(fn($item) => $item['price'] * 1.2);
filterMap(callable $callback)
Map and discard null/false results in a single pass.
$emails = $users->filterMap(fn($u) => $u['active'] ? $u['email'] : null);
mapWithKeys(callable $callback)
Rebuild the collection as key -> value pairs.
$lookup = $users->mapWithKeys(fn($u) => [$u['id'] => $u['name']]); // [1 => 'Alice', 2 => 'Bob']
mapKeys(callable $callback)
Transform only the keys, keeping values unchanged.
$c->mapKeys(fn($k) => strtoupper($k));
mapToGroups(callable $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)
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)
Map items into instances of $class (passes each item to the constructor).
$objects = $items->mapInto(ProductDTO::class);
mapBy(string|callable $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)
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)
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)
Like map() but mutates the collection in-place.
$c->transform(fn($v) => $v * 2);
flatMap(callable $callback)
Map each item and collapse the results one level.
$emails = $users->flatMap(fn($u) => $u['emails']);
pluck(string $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)
Pick multiple fields from each item, supporting dot-notation paths.
$users->select(['id', 'name', 'user.email']);
only(array $keys)
Return a new collection with only the specified top-level keys.
$c->only(['id', 'name']);
except(array $keys)
Return a new collection with the specified top-level keys removed.
$c->except(['password', 'token']);
flatten(int|float $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 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 = '')
Flatten a nested array to dot-notation key -> value pairs.
Collection::make(['user' => ['name' => 'Alice', 'age' => 30]])->dot()->all(); // ['user.name' => 'Alice', 'user.age' => 30]
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()
Swap keys and values.
Collection::make(['a' => 1, 'b' => 2])->flip()->all(); // [1 => 'a', 2 => 'b']
reverse(bool $preserveKeys = false)
Return items in reverse order.
Collection::make([1, 2, 3])->reverse()->all(); // [3, 2, 1]
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)
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)
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)
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
sort(?callable $callback = null)
Sort items. With no callback uses natural ordering.
$c->sort(); $c->sort(fn($a, $b) => $a <=> $b);
sortDesc(?callable $callback = null)
Sort in descending order.
$c->sortDesc();
sortBy(string|callable|array $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)
Shorthand for sortBy($by, desc: true).
$products->sortByDesc('price');
sortKeys(?callable $callback = null) / ksort(?callable $callback = null)
Sort by key.
$c->sortKeys();
sortKeysDesc()
Sort by key in descending order.
$c->sortKeysDesc();
sortKeysUsing(callable $comparator)
Sort by key using a custom comparator.
$c->sortKeysUsing('strnatcasecmp');
Grouping & Keying
groupBy(string|callable $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)
Count occurrences per group.
$c->countBy(); // count each unique value $users->countBy('role'); // ['admin' => 2, 'editor' => 5]
partition(callable $callback)
Split the collection into two: passing and failing items.
[$admins, $others] = $users->partition(fn($u) => $u['role'] === 'admin');
Aggregates
sum(string|callable|null $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)
Multiply all items together.
$c->product(); $items->product('qty');
avg(string|callable|null $pathOrCallback = null) / average(...)
Return the average value.
$scores->avg('score');
min(string|callable|null $pathOrCallback = null) / max(...)
Return the minimum or maximum value.
$products->min('price'); $products->max('price');
minBy(string|callable $by) / maxBy(string|callable $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)
Return the median value.
$scores->median('score');
mode(?string $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)
Return the population (or sample) standard deviation.
$scores->standardDeviation('score'); $scores->standardDeviation('score', sample: true);
percentage(callable $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)
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(...)
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)
Check if all items satisfy a condition.
$users->every(fn($u) => $u['active']); $users->every('role', 'admin');
Slicing & Pagination
slice(int $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)
Return the first $n (or last, if negative) items.
$c->take(3); // first 3 $c->take(-2); // last 2
skip(int $n)
Skip the first $n items.
$c->skip(2); // [3, 4, 5] from [1, 2, 3, 4, 5]
forPage(int $page, int $perPage)
Return items for a specific page (1-based).
$c->forPage(2, 15); // items 16–30
paginate(int $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)
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)
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)
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)
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)
Return every $step-th item.
$c->nth(2); // 1st, 3rd, 5th … $c->nth(3, 1); // 2nd, 5th, 8th …
pad(int $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
merge(array|Collection $items)
Merge another array or collection (top-level).
$c->merge(['d', 'e']);
mergeRecursive(array|Collection $items)
Deep-merge arrays — repeated string keys produce nested arrays.
$c->mergeRecursive(['meta' => ['extra' => true]]);
replace(array|Collection $items) / replaceRecursive(...)
Replace values using PHP's array_replace semantics.
$c->replace([0 => 'x']); $c->replaceRecursive(['user' => ['name' => 'Bob']]);
concat(array|Collection $items)
Append all values, always re-indexing (numeric keys never overwrite).
$c->concat(['d', 'e']);
union(array|Collection $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)
Return items not present in $items.
Collection::make([1, 2, 3, 4])->diff([2, 4])->all(); // [1, 3]
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)
Diff using a custom comparator function.
$objects->diffUsing($other, fn($a, $b) => $a->id <=> $b->id);
intersect(array|Collection $items)
Return items present in both collections.
Collection::make([1, 2, 3])->intersect([2, 3, 4])->all(); // [2, 3]
intersectByKeys(...) / intersectAssoc(...) / intersectUsing(...)
Intersection variants by keys, key+value, or custom comparator.
symmetricDiff(array|Collection $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)
Pair items from multiple arrays together.
Collection::make([1, 2, 3])->zip(['a', 'b', 'c'])->all(); // [[1,'a'], [2,'b'], [3,'c']]
unzip()
Unzip a collection of tuples into separate collections (inverse of zip()).
[$numbers, $letters] = Collection::make([[1,'a'],[2,'b']])->unzip();
crossJoin(array ...$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)
Use this collection as keys and $values as values.
Collection::make(['a', 'b'])->combine([1, 2])->all(); // ['a' => 1, 'b' => 2]
multiply(int $times)
Repeat every item $times times.
Collection::make([1, 2])->multiply(3)->all(); // [1, 2, 1, 2, 1, 2]
shuffle()
Return a new collection with items in random order.
$c->shuffle();
random(int $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 & Pipelines
each(callable $callback)
Iterate over every item. Return false to stop early.
$c->each(function ($value, $key) { echo "$key: $value\n"; });
eachSpread(callable $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)
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)
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)
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)
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)
Pass the collection to a callback and return whatever the callback returns.
$total = $c->pipe(fn($c) => $c->sum('price'));
pipeInto(string $class)
Pass the collection as the first argument to a class constructor.
$presenter = $c->pipeInto(UserPresenter::class);
pipeThrough(array $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)
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)
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)
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)
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)
Assert that every item is of the expected type; throw otherwise.
$c->ensure('int'); $c->ensure(['int', 'float']); $c->ensure(User::class);
Mutation
push(mixed $value)
Append a value to the end.
$c->push('new item');
prepend(mixed $value, mixed $key = null)
Add a value to the beginning.
$c->prepend(0); $c->prepend('home', 'first');
insert(int $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()
Remove and return the last item.
$last = $c->pop();
shift()
Remove and return the first item.
$first = $c->shift();
splice(int $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()
Remove all items from the collection.
$c->clear();
copy()
Return a deep clone of the collection.
$clone = $c->copy();
join(string $glue, ?string $finalGlue = null) / implode(string $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
toJson(int $flags = 0) / toPrettyJson()
Convert the collection to a JSON string.
$c->toJson(); $c->toPrettyJson();
fromJson(string $json) (static)
Create a collection from a JSON string.
$c = Collection::fromJson('{"a":1,"b":2}');
__toString()
Automatically called when the collection is cast to a string (returns JSON).
echo $c; // prints JSON
Debugging
dump()
Dump the collection contents and return $this (stays in chain).
$c->filter('active')->dump()->sortBy('name');
dd()
Dump the collection and terminate execution.
$c->dd();
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]
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
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
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 <- full contract (extends all below)
├── Accessable <- get / set / has / remove / pull
├── Arrayable <- all / values / keys / flatten / groupBy …
├── Queryable <- first / last / find / search / sole …
├── Filterable <- filter / reject / where* / unique …
├── Mappable <- map / pluck / reduce / evolve / scan …
├── Aggregatable <- sum / avg / min / max / every / countBy …
├── Sliceable <- slice / take / chunk / paginate / sliding …
├── Combinable <- merge / diff / intersect / zip / crossJoin …
├── Sortable <- sort / sortBy / sortKeys …
├── Mutable <- push / pop / shift / splice / insert …
├── Pipeable <- pipe / tap / when / unless / ensure …
├── Jsonable <- toJson / fromJson
└── Debuggable <- dump / dd
Collection implements Collectable, which extends all of the above plus the
standard PHP interfaces (ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Stringable).
Type-hinting against a specific interface
Instead of depending on the concrete Collection class you can type-hint against
the narrowest interface that covers what you actually need.
use Collectable\Contracts\Aggregatable; use Collectable\Contracts\Filterable; use Collectable\Contracts\Sortable; // This function only needs to filter and sort — no concrete class required. function getTopUsers(Filterable&Sortable $users, int $limit): array { return $users ->where('active', true) ->sortByDesc('score') ->take($limit) ->all(); } // Works with Collection out of the box: getTopUsers(Collection::make($data), 10);
Full contract reference
| Interface | Key methods |
|---|---|
Accessable |
get, set, put, has, hasKey, hasAny, hasAll, remove, forget, pull, getOrPut |
Arrayable |
all, toArray, values, keys, count, isEmpty, isList, isAssoc, flatten, collapse, dot, undot, only, except, flip, reverse, groupBy, partition, join, implode, shuffle, random, multiply, copy, transpose, toTree |
Queryable |
first, last, firstOrFail, firstWhere, firstKey, lastKey, value, after, before, search, find, sole, hasSole |
Filterable |
filter, reject, where, whereIn, whereNotIn, whereNull, whereNotNull, whereBetween, whereNotBetween, whereStrict, whereInstanceOf, whereContains, whereStartsWith, whereEndsWith, whereMatches, unique, uniqueStrict, duplicates, duplicatesStrict |
Mappable |
map, filterMap, mapWithKeys, mapKeys, mapToGroups, mapSpread, mapInto, mapBy, keyBy, mapPath, evolve, transform, flatMap, pluck, select, scan, reduce |
Aggregatable |
sum, product, avg, average, min, max, minBy, maxBy, median, mode, standardDeviation, percentage, countBy, contains, doesntContain, some, containsStrict, every |
Sliceable |
slice, take, skip, forPage, paginate, chunk, chunkWhile, sliding, split, splitIn, nth, pad, takeUntil, takeWhile, skipUntil, skipWhile |
Combinable |
merge, mergeRecursive, replace, replaceRecursive, concat, union, diff, diffAssoc, diffKeys, diffUsing, intersect, intersectByKeys, intersectAssoc, intersectUsing, symmetricDiff, zip, unzip, crossJoin, combine |
Sortable |
sort, sortDesc, sortBy, sortByDesc, sortKeys, sortKeysDesc |
Mutable |
push, prepend, insert, pop, shift, splice, clear |
Pipeable |
each, eachSpread, pipe, pipeInto, pipeThrough, tap, when, unless, whenEmpty, whenNotEmpty, unlessEmpty, unlessNotEmpty, ensure |
Jsonable |
toJson, toPrettyJson, fromJson |
Debuggable |
dump, dd |
Macros
You can add your own methods to every collection at runtime via macro().
Collection::macro('toUpperCase', function () { return $this->map(fn($v) => strtoupper($v)); }); Collection::make(['hello', 'world'])->toUpperCase()->all(); // ['HELLO', 'WORLD']
See Macroable the source code for the full list of methods.