Laravel Collection Macros &amp; Mixins Deep Dive | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Contextual Macros and Mixins: Extending Laravel Collections Without Bloat        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)

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

  #laravel   #collections   #macros   #php  

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

     16 Aug 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why Extend Collections at All?  ](#why-extend-collections-at-all)
2. [  02   Macros: The Quick Win  ](#macros-the-quick-win)
3. [  03   Mixins: Organising Many Macros  ](#mixins-organising-many-macros)
4. [  04   Typed Domain Collections  ](#typed-domain-collections)
5. [  05   Higher-Order Proxies  ](#higher-order-proxies)
6. [  06   Testing Your Extensions  ](#testing-your-extensions)
7. [  07   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.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fcontextual-macros-and-mixins-extending-laravel-collections-without-bloat&text=Contextual+Macros+and+Mixins%3A+Extending+Laravel+Collections+Without+Bloat) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fcontextual-macros-and-mixins-extending-laravel-collections-without-bloat) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  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. 

      Q02  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. 

      Q03  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. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Filament at Scale: Multi-Panel Auth, Custom Panels, and Table Query Tuning](https://cdn.msaied.com/557/7c7cc76acf702e58f5175e1308414ec8.png) filament laravel multi-tenant 

### Filament at Scale: Multi-Panel Auth, Custom Panels, and Table Query Tuning

Running Filament across multiple panels with distinct auth guards and tuning table queries for large datasets...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/filament-at-scale-multi-panel-auth-custom-panels-and-table-query-tuning-4) [ ![Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Microservice Tax](https://cdn.msaied.com/555/c194fc79e9397fef3bcd3a896eb558fd.png) laravel architecture ddd 

### Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Microservice Tax

Learn how to carve a Laravel application into cohesive bounded contexts using modules, internal contracts, and...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/modular-monolith-in-laravel-enforcing-bounded-contexts-without-a-microservice-tax) [ ![Octane Worker Lifecycle, State Leakage, and Memory Management in Production](https://cdn.msaied.com/554/8cc265358b47e59601a66d1e247eba9a.png) laravel octane performance 

### Octane Worker Lifecycle, State Leakage, and Memory Management in Production

Laravel Octane keeps workers alive across requests, which means static state, resolved singletons, and stale d...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/octane-worker-lifecycle-state-leakage-and-memory-management-in-production-2) 

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

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
