What are Laravel Macros?

March 21, 2019

What good Laravel Macros bring?

Have you ever been in a situation that you wanted a method or functionality to be part of Laravel’s core, but for one or another reason it isn’t? I get this feeling a lot, especially when writing tests and working with collections. It’s not possible to have everything one needs in a tool or a framework like Laravel. There will always be the need for our own utilities and abstractions to better solve some project-specific problems.

Macros are there to help us exactly with these situations. With Macros we can extend the power of Laravel’s components/classes in a very easy, clean and expressive way.

In the below example, we will add a method named evens() to the Laravel’s Collection class, this method will return only even numbers in an array.

use Illuminate\Support\Collection;

Collection::macro('evens', function() {
    return $this->filter(function($value) {
        return $value % 2 === 0;
    });
});

// Now the above can be used as below:
$collection = collect([3, 5, 8, 2, 1, 6]);

$collection->evens(); 

// [8, 2, 6]

Notice how $this inside Collection::macro is bound to the instance of Illuminate\Support\Collection class. This will help more with our code’s expresiveness by providing more context inside macro(), we will be able to use lots of existing methods on that class. Remember, $this always binds to the instance of the class we are creating Macro for.

Where to define them?

As Macros are added in the runtime, therefore the best place to define them are inside Laravel’s Service Providers. This will make sure that our newly added methods will be available nearly everywhere in our code. We can add them in the AppServiceProvider as shown in the below example or inside a more specific service provider that we may wanna create for this purpose.

<?php

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        Collection::macro('evens', function() {
            return $this->filter(function($value) {
                return $value % 2 === 0;
            });
        });
    }
}

How to know if a component is macro-able?

There are around 20 classes in Laravel which are macro-able. If a class is using Illuminate\Support\Traits\Macroable trait, it’s “Macroable”. See the Illuminate\Support\Arr class as an example:

Some Practical Examples

In one of my projects, I added some utility assertion methods (Macros) for Illuminate\Foundation\Testing\TestResponse class to have some expressive and clean assertions.

Using these assertions I can do:

/** @test */
public function it_creates_a_user()
{
    $response = $this->json('POST', '/api/user', ['name' => 'Ahmad']);

    $response->assertCreated();
}

Instead of:

/** @test */
public function it_creates_a_user()
{
    $response = $this->json('POST', '/api/user', ['name' => 'Ahmad']);

    $response->assertStatus(Response::HTTP_CREATED);
}

Of course, I used above example to describe the concept easily. Macros can be used to simplify much more complicated logic. You can find a more advanced example in below. There are 4 macro examples, you can go through each one of them to understand what they actually do.

That’s it. Thank you for reading this. Let me know if you have any questions or comments in below.

Comments

Comments powered by Disqus