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

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

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

  [ home ](https://www.msaied.com)    [ articles ](https://www.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%2Fwww.msaied.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%2Fwww.msaied.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://www.msaied.com/articles) 

 [ ![Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts](https://cdn.msaied.com/584/3ffe135721c65ab3f9b40401dc3c41de.png) laravel ai llm 

### Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts

Learn how to stream LLM responses in Laravel, enforce token budgets, and lock structured output to typed PHP c...

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

 23 Aug 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/streaming-ai-responses-in-laravel-token-budgets-structured-output-and-agent-contracts) [ ![Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules](https://cdn.msaied.com/583/7301900aac0f2ec5d1347df11ce92188.png) laravel php8.3 enums 

### Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules

Go beyond basic enum casting. Learn how to wire PHP 8.3 backed enums into Eloquent, route model binding, and c...

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

 23 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/typed-php-83-enums-as-eloquent-casts-route-parameters-and-validation-rules) [ ![Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice](https://cdn.msaied.com/582/564b2d098d2b94b53d4f5319ac77d58b.png) livewire laravel performance 

### Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice

Lazy components, islands, and deferred loading in Livewire v3 let you ship fast initial pages without sacrific...

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

 23 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/livewire-v3-lazy-components-islands-and-deferred-loading-in-practice-1) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.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://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.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)
