Laravel Collection Macros &amp; Mixins Deep Dive | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Contextual Macros and Mixins: Extending Laravel Collections Without Bloat

 Contextual Macros and Mixins: Extending Laravel Collections Without Bloat
==========================================================================

 Learn how to add domain-specific behaviour to Laravel's Collection class using macros, mixins, and higher-order proxies — keeping your codebase expressive without polluting global state.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 16 Aug 2026 · Updated 16 Aug 2026 · 3 min read

ShareCopy linkCopied

 ![Contextual Macros and Mixins: Extending Laravel Collections Without Bloat](https://cdn.msaied.com/556/0c5a2892229d005cb3b747c868df5bb6.png) 

  On this page +1. [Why Extend Collections at All?](#why-extend-collections-at-all)
2. [Macros: The Quick Win](#macros-the-quick-win)
3. [Mixins: Organising Many Macros](#mixins-organising-many-macros)
4. [Typed Domain Collections](#typed-domain-collections)
5. [Higher-Order Proxies](#higher-order-proxies)
6. [Testing Your Extensions](#testing-your-extensions)
7. [Key Takeaways](#key-takeaways)

 Why Extend Collections at All?
------------------------------

Laravel's `Collection` class covers the 90 % case, but every domain has its own vocabulary. Repeating `->filter(fn($u) => $u->isActive())->values()` across ten service classes is a smell. Macros and mixins let you encode that vocabulary once and test it in isolation.

---

Macros: The Quick Win
---------------------

`Collection` uses the `Macroable` trait, so you can attach a closure at boot time:

```php
// app/Providers/CollectionServiceProvider.php

use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;

class CollectionServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Collection::macro('active', function (): Collection {
            /** @var Collection $this */
            return $this->filter(fn($item) => $item->is_active)->values();
        });

        Collection::macro('keyedById', function (): Collection {
            return $this->keyBy('id');
        });
    }
}

```

Register the provider in `bootstrap/providers.php` (Laravel 11+) and you can write:

```php
$users->active()->keyedById();

```

The closure's `$this` is the collection instance — no static tricks needed.

---

Mixins: Organising Many Macros
------------------------------

Once you have more than a handful of macros, a mixin class keeps things tidy. Each public method returns a `Closure`:

```php
// app/Collections/UserCollectionMixin.php

class UserCollectionMixin
{
    public function active(): Closure
    {
        return function (): Collection {
            return $this->filter(fn($u) => $u->is_active)->values();
        };
    }

    public function admins(): Closure
    {
        return function (): Collection {
            return $this->filter(fn($u) => $u->role === 'admin')->values();
        };
    }

    public function totalRevenue(): Closure
    {
        return function (): int|float {
            return $this->sum('revenue_cents') / 100;
        };
    }
}

```

Register it with one line:

```php
Collection::mixin(new UserCollectionMixin());

```

IDE support is the catch. Add a `@mixin` docblock or generate an IDE helper via `barryvdh/laravel-ide-helper` to keep autocomplete intact.

---

Typed Domain Collections
------------------------

For stricter guarantees, extend `Collection` directly and override `offsetSet`:

```php
// app/Collections/OrderCollection.php

use Illuminate\Support\Collection;
use App\Models\Order;

/**
 * @extends Collection
 */
class OrderCollection extends Collection
{
    public function pending(): static
    {
        return $this->filter(fn(Order $o) => $o->status->isPending())->values();
    }

    public function totalGross(): int
    {
        return $this->sum('gross_amount_cents');
    }
}

```

Tell Eloquent to use it on the model:

```php
class Order extends Model
{
    public function newCollection(array $models = []): OrderCollection
    {
        return new OrderCollection($models);
    }
}

```

Now `Order::where(...)->get()` returns an `OrderCollection` automatically — no casting required at the call site.

---

Higher-Order Proxies
--------------------

Laravel ships higher-order proxies for a fixed set of methods (`map`, `filter`, `each`, etc.). You cannot add new proxy targets, but you can combine them with your macros cleanly:

```php
$orders->pending()->each->markAsProcessing();
// equivalent to
$orders->pending()->each(fn(Order $o) => $o->markAsProcessing());

```

The proxy delegates the method call to every item in the collection — useful for side-effect pipelines.

---

Testing Your Extensions
-----------------------

Macros and typed collections are trivial to unit-test with Pest:

```php
it('filters active users', function () {
    $users = collect([
        (object) ['is_active' => true],
        (object) ['is_active' => false],
    ]);

    expect($users->active())->toHaveCount(1);
});

it('returns an OrderCollection from eloquent', function () {
    $orders = Order::factory(3)->create();
    expect(Order::all())->toBeInstanceOf(OrderCollection::class);
});

```

Keep macro registration in a service provider so tests that boot the application pick it up automatically.

---

Key Takeaways
-------------

- Use **macros** for one-off, cross-domain helpers; use **mixins** to group related macros by domain.
- Use **typed collection subclasses** when you want static analysis, strict typing, and IDE autocomplete without extra packages.
- Register everything in a dedicated `CollectionServiceProvider` — not `AppServiceProvider` — to keep boot logic focused.
- Higher-order proxies work seamlessly alongside custom macros for expressive side-effect pipelines.
- Write a Pest unit test for every macro; they are pure functions and test in milliseconds.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [collections](https://www.msaied.com/public/articles?search=collections)
- [macros](https://www.msaied.com/public/articles?search=macros)
- [php](https://www.msaied.com/public/articles?search=php)

 Frequently asked questions 
---------------------------

  Do Collection macros affect Eloquent's LazyCollection?No. `LazyCollection` is a separate class that also uses `Macroable`, so you must register macros on it independently with `LazyCollection::macro(...)` if you need the same behaviour on lazy result sets.

   Will a typed OrderCollection break when I call collect() helpers that return a new instance?Methods like `filter` and `map` call `$this-&gt;newInstance()` internally, which preserves the subclass type. However, `collect()` the global helper always returns a base `Collection`, so avoid wrapping a typed collection in it.

   How do I get IDE autocomplete for macros without a build step?Add a `/\*\* @method Collection active() \*/` docblock to a stub file or use `barryvdh/laravel-ide-helper` with `php artisan ide-helper:generate`. For typed subclasses, PHPStan and Psalm pick up the `@extends` generic annotation directly.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleModular Monolith in Laravel: Enforcing Bounded Contexts Without a Microservice Tax](https://www.msaied.com/public/articles/modular-monolith-in-laravel-enforcing-bounded-contexts-without-a-microservice-tax) [Next articleFilament at Scale: Multi-Panel Auth, Custom Panels, and Table Query Tuning](https://www.msaied.com/public/articles/filament-at-scale-multi-panel-auth-custom-panels-and-table-query-tuning-4)  

   On this page
-------------

1. [Why Extend Collections at All?](#why-extend-collections-at-all)
2. [Macros: The Quick Win](#macros-the-quick-win)
3. [Mixins: Organising Many Macros](#mixins-organising-many-macros)
4. [Typed Domain Collections](#typed-domain-collections)
5. [Higher-Order Proxies](#higher-order-proxies)
6. [Testing Your Extensions](#testing-your-extensions)
7. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
