Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Collections Php Laravel Package

mf/collections-php

A lightweight collection library for PHP that brings expressive, chainable array manipulation to any project. Map, filter, reduce, group and transform data with a fluent API, plus optional Laravel integration for familiar collection-style workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require mortal-flesh/collections-php

(Confirm the exact vendor/package string on Packagist — the repo is MortalFlesh/MFCollectionsPHP, and the released tag as of 2023-12 is the one you want. There is no Laravel service provider and no config file; it's plain PHP with zero framework coupling.)

Where to look first

  1. src/Collection/ — the three entry points: the mutable Collection, the ImmutableCollection, and the NamedCollection (string-keyed). Everything else in the package is interface + trait plumbing around these.
  2. The *Interface files next to them — this is the real contract. The concrete classes are thin; the interfaces tell you what the package actually promises.
  3. The T* traits (TCollection, TImmutableCollection, TNamedCollection) — these are the extension point you'll use to build typed collections.

First use case

The sweet spot is boundary code: a controller or job receives a decoded JSON payload / an untyped config array and immediately wraps it so the rest of the class can stop doing isset($data['x']) ? ... : null and array_map gymnastics.

use MortalFlesh\Collections\Collection\Collection;

final class ImportContacts
{
    public function handle(array $payload): void
    {
        $contacts = new Collection($payload['contacts'] ?? []);

        $emails = $contacts
            ->filter(fn (array $c) => filter_var($c['email'] ?? '', FILTER_VALIDATE_EMAIL))
            ->map(fn (array $c) => strtolower($c['email']))
            ->toArray();

        // ...
    }
}

It still satisfies ArrayAccess, Countable, IteratorAggregate and JsonSerializable, so you can pass it straight into anything that expects an array-ish value — count(), foreach, $c[0], json_encode() all work. That "classic array, plus methods" property is the whole pitch, and it's why it survives contact with legacy code better than a full-blown collection library.

Implementation Patterns

1. Wrap at the edge, unwrap at the edge

Pick two seams in the app — HTTP/job input and HTTP/DB output — and convert there. Inside, pass the collection object around. This keeps toArray() calls countable in a code review and stops the "is this an array or a collection right now?" ambiguity that plagues mixed codebases.

// Controller: raw array -> collection
$data = new Collection($request->validated());

// Resource: collection -> array for the response
return new ContactResource($data->toArray());

2. Use ImmutableCollection for anything that leaves a method

If a collection is a property of a DTO, a value object, or a return type, make it immutable. Reads are identical, writes throw — which is exactly the failure mode you want when someone six months from now tries to $dto->tags[] = 'x' on a shared object.

final class Invoice
{
    public function __construct(
        private readonly ImmutableCollection $lines,
    ) {}

    public function lines(): ImmutableCollection { return $this->lines; }
}

Derived collections from map/filter come back as new instances, so an immutable instance stays immutable through a chain — that's the property that makes this safe to hand out.

3. Typed collections via subclassing

The package is intentionally untyped. For domain collections, subclass and add the domain predicates/aggregates you actually call more than twice:

use MortalFlesh\Collections\Collection\Collection;

/** @extends Collection<int, OrderLine> */
final class OrderLineCollection extends Collection
{
    public function total(): int
    {
        $sum = 0;
        foreach ($this as $line) {
            $sum += $line->total();
        }
        return $sum;
    }

    public function billable(): self
    {
        return $this->filter(fn (OrderLine $l) => $l->isBillable());
    }
}

Prefer this over a generic Collection plus a static helper — the type appears in signatures, IDE completion works, and instanceof checks still line up. Add the @extends docblock; without generics in PHP, static analysis needs the hint.

4. Eloquent integration: cast at the attribute

Don't try to make Eloquent return these; cast the JSON column and let the collection live in the domain layer.

final class CollectionCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): Collection
    {
        return new Collection(json_decode($value ?? '[]', true) ?: []);
    }

    public function set($model, $key, $value, $attributes): string
    {
        return json_encode($value instanceof Collection ? $value->toArray() : $value);
    }
}

5. Don't fight Illuminate\Support\Collection — bridge it

In a Laravel app you already have collect(). Use the MF collection for storage/transport and collect() for the heavy fluent work if the team is more fluent there:

$grouped = collect($collection->toArray())->groupBy('team_id');

One bridge line is cheaper than reimplementing groupBy. The reverse also works: new Collection($eloquentCollection->all()).

6. Bind an empty collection in the container for injection

For services that take a collection dependency, bind a factory closure in a service provider so you're not sprinkling new Collection() through constructors — this also gives you one place to swap in a typed subclass later.

Gotchas and Tips

No deep conversion. Wrapping an array of arrays gives you a collection of arrays, not a collection of collections. Reach for ->map(fn ($row) => new Collection($row)) explicitly when you want nesting — and think twice, because recursion here is usually a sign you want DTOs instead.

Objects keep identity; scalars don't. contains() on an array of objects is reference/equality-sensitive, and the semantics depend on whether the underlying lookup is in_array strict or loose. Test it once with your actual value type rather than assuming; it's the single most common surprise when people migrate from in_array($x, $arr, true).

Mutable collections are not copy-on-write in the way you expect. PHP arrays are, but a mutable collection object handed to two callers is one object. The classic bug: a service mutates a collection it was given "just to normalize it" and the caller's data changes. Default to immutable for anything crossing a class boundary; keep mutable collections method-local.

json_encode shape depends on keys. JsonSerializable delegates to the underlying array, so a collection with non-sequential integer or string keys encodes as a JSON object. If your API contract says array, call ->values() (or array_values($c->toArray())) before serializing.

Don't serialize collections into queued job payloads or cache. Serialize toArray() and rebuild on the other side. It keeps payloads readable, survives package upgrades, and avoids coupling your queue to the library's internal property layout.

Hot loops: unwrap once. Each method call is a real call plus an array copy on return. In a loop over 100k rows, $collection->toArray() once up front beats calling collection methods per iteration. This library is for readability, not throughput — profile before optimizing, but know where the cliff is.

Debugging: dd($collection) shows the wrapper object with a private array, not the contents. Get in the habit of dd($collection->toArray()). Same for var_dump and for Telescope's payload view.

Extension points are the traits, not magic methods. If you need custom behavior beyond subclassing, the T* traits are designed to be composed into your own class alongside your own ArrayAccess/Countable/IteratorAggregate implementations. Read the trait source before composing — it's short and it defines the internal property you'll be responsible for.

This is a 10-star, single-maintainer package last released in Dec 2023. That's fine — it's a few hundred lines of sugar over an array, and the surface area is small enough to audit in an afternoon. But: (a) pin the version in composer.json, (b) don't expose its types in a public package API you don't control, and (c) if it ever goes unmaintained, forking it is trivial. If you find yourself needing a feature the package doesn't have, write it in a local subclass rather than waiting on an upstream release.

Don't expect Illuminate\Support\Collection. No pluck, no when, no tap, no each, no higher-order proxies. If your team's muscle memory is Laravel collections, budget a small amount of friction — or standardize on collect() internally and use this package only for DTO/transport storage.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core