somnambulist/collection
A PHP/Laravel collection package that extends the standard Collection with type-safe wrappers, extra helper methods, and immutable-style operations for cleaner, more predictable data manipulation.
composer require somnambulist/collection
There is no service provider, no facade, no config file — it is a single-purpose, dependency-free library. Everything lives under Somnambulist\Collection\ and the entry point is nearly always the same:
use Somnambulist\Collection\Collection;
$c = Collection::create($payload); // or new Collection($payload)
Where to look first:
README.md — the behaviour list is effectively the API index; methods are grouped by concern (map, filter, sort, mutate, extract, debug).src/Behaviours/* — the class is assembled from traits (Map, Filter, Sort, Mutate, Extract, Query, Compare, Debug, …). When in doubt about a method's semantics, read the trait; it is short and readable.src/Contracts/ — CollectionInterface and the typed/immutable variants, which tell you what you can rely on when type-hinting.First use case: replace a block of nested array_map / foreach / usort / isset($a['b']['c']) code in a service that massages an API or CSV payload. Build the collection at the boundary (Collection::create($response->json())), then chain filter(), pluck(), keyBy(), sortBy(), toArray() in a single readable expression. That is the 80% use case; everything else is refinement.
Normalise at the edges, chain in the middle. Convert arrays to collections where they enter your application (HTTP clients, repositories, file readers) and convert back (toArray(), toJson()) where they leave. Passing a Collection through your service layer is fine and often nicer than array, but do not expose it from a public API contract unless you deliberately want to depend on the package.
Dot notation for nested payloads. get(), set(), has(), forget() and pluck() accept 'a.b.c' paths. This is the single biggest day-to-day win over vanilla arrays for config-like or JSON-shaped data, and it composes well with only()/except() when shaping a response.
Report-style transformation. groupBy() + map() + sum()/avg()/count() covers most ad-hoc reporting without touching SQL, and mapWithKeys() + keyBy() is the standard way to reindex a list of DTO-as-array rows into an id-keyed map.
Typed collections for domain lists. Subclass TypedCollection (or ImmutableTypedCollection) and declare the element type. You get insertion-time validation, a named type you can hint against, and — more importantly — a natural home for domain queries (ActiveUsers::fromRepository()->withExpiredSubscriptions()). This is where the package stops being "Laravel collections without Laravel" and starts earning its keep: the type class is your domain-specific collection, not a generic bag.
Immutability as a design tool. Use ImmutableCollection for value objects, configuration snapshots, and anything shared between services. Mutation attempts throw instead of silently corrupting a reference someone else holds. Reach for the mutable Collection only inside a single, contained method.
Conditional pipelines. when() / unless() / whenEmpty() keep a chain flat when part of the transformation is conditional; tap() for side-effect logging mid-chain; pipe() to hand off to a function that expects a plain array or a differently-typed result. This keeps service methods as one linear expression rather than a series of reassignments.
Integration tips. With Eloquent, Collection::create($models->all()) is usually enough — you rarely need the Laravel collection. With Doctrine ArrayCollection, convert via ->toArray() at the boundary. In Laravel, do not register anything; just import the class and avoid shadowing the global collect() helper. In Symfony, it works as a plain service dependency with no bundle.
Testing. Assert on toArray() rather than poking at internals; typed collections double as fixtures that fail loudly if a test builds the wrong element type.
set, forget, push, transform) act in place, while map, filter, sort variants typically return new instances. Assume nothing; if a chain looks like it should have changed the original, it probably didn't. Standardise on immutable collections in shared code and the question disappears.. will not be found by get('a.b'). Keep payload keys dot-free, or bypass path resolution for those lookups.where(), contains(), unique() follow the usual loose/strict split — the *Strict variants exist for a reason. Comparisons of 0, '0', null and false are the classic source of "why did this filter match?".merge, constructor with a large array, deserialisation) are worth a test — a badly-typed element slipping in via an array merge is the failure mode to guard against.dd(), dump(), or debug bar integration — zero dependencies means zero magic. Debug with var_dump($c->toArray()) or json_encode($c) (JsonSerializable is implemented, which is also why it drops neatly into API responses).foreach will be faster — build the collection after the loop, not inside it.src/Behaviours, or implementing CollectionInterface. There is no runtime macro() registry, so keep extensions in your own namespace and don't monkey-patch.How can I help you explore Laravel packages today?