How To Use Laravel Macros

May 7, 2019 • ~ 4 minutes read

code

When you come to writing apps that more complex than landing or a blog,
you can fall into a situation, where you need or want to have some functionality of Laravel that doesn't exist yet.
Or, you have a small bit of logic, you want to reuse all over your app.

If so, macros are a handy option for you. It allows you to extend Laravel's internal components with your code.

Let's start with a simple example from official docs

Collection::macro('toUpper', function () {
    return $this->map(function ($value) {
        return Str::upper($value);
    });
});

// will return ['FIRST','SECOND']
collect(['first', 'second'])->toUpper();

As you can see, the macro method takes as arguments a name and an anonymous function to call (optionally, you able to add additional arguments, if you need that). When you call a macro, your code in function would be called from the context of that class (in the example it is Collection class context), allowing you to execute your code along with Laravel's built-in features.

Let's continue with the more real-world example for query builder that I use almost daily.

Builder::macro('searchIn', function ($attributes, $needle) {
    return $this->where(function (Builder $query) use ($attributes,$needle) {
        foreach (array_wrap($attributes) as $attribute) {
            $query->orWhere($attribute, 'LIKE', "%{$needle}%");
        }
    });
});

This macro allows you to search for data in your database in several columns quickly.

Using it, we can replace this code:

$searchTerm = 'Jane';

User::where('name', 'like', "%{$searchTerm}%")
        ->orWhere('email', 'like', "%{$searchTerm}%")
        ->get();

// This piece of code will give us all users which name or email contains `Jane`.

with this one:

$searchTerm = 'Jane';

User::searchIn(['name','email'], $searchTerm)->get();

and results of the query will be EXACTLY SAME.

Our example uses two columns, so visual debt is not so strong, but, imagine you need to search through five columns, and forced to write all that callbacks in a chain.

Codebase may become a mess pretty quickly, and our small macro
will decrease that useless cognitive load from you and your team.

You will appreciate these little things, once your project becomes somewhat sizeable.

Registering a new macro

All macros should be registered inside the boot method in service provider.
The excellent starting point is AppServiceProvider, shipped with a default installation. As an alternative you can create a separate provider, that is entirely up to you.

Using anonimous function

This approach is the most direct and straightforward way to register new macro is putting it right inside the boot method of AppServiceProvider.

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Collection::macro('name it here', function(){
            // Pass your actual code here.
        });
    }
}

And you are done.

If you are placing your macro in provider other then AppServiceProvider, make sure you
added it(provider) to providers array of your's config/app.php file.

Using mixins

Macros awesome, and you may want to use a lot of them. In this case, your boot method service provider probably becomes bloated with different macros, and code start looking messy.
To separate your code a little bit, you can use mixins.
They must be registered just like macros, inside service providers, with the single difference, that mixin itself - is a class, which public & protected methods will be added to the original instance.

For this approach, you should use a mixin static method on the macroable class, and pass your mixin class as an argument.

Let's take a look at an example. Imagine, we have Macros/QueryBuilderMacros.php file in our app directory with the following content.

class QueryBuilderMacros
{
    public function one()
    {
        // macro content
    }

    protected function two()
    {
        // macro content
    }

    private function three()
    {
        // will not become a macro
    }
}

To register the QueryBuilderMacros class with its methods, you should use the following code.

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Builder::mixin(new QueryBuilderMacros());
    }
}

And that's it. Methods one and two will become available on every Builder class instance. Method three declared as private, will stay accessible only for methods in QueryBuilderMacros class, you know because private should stay private:).

Framework macroable classes

Laravel provides you a whole bunch of classes where you can add your macros.
Here is a list of most common facades, valid for version 5.8

By the way, there is a lot of other macroable classes, and you can find them all, walking through
framework codebase.

Make your class macroable

If you distribute packages for PHP, you may want, in some cases, make them macroable.
And folks from Spatie got you covered, thanks to their awesome Macroable trait.

You can read more about it, in this blog post.

Macros collections

There are a bunch of macros collections, created and published by the community, which you can start using right away.
Here are few libs, which warmly met by devs, and gets widely used across them.

Other handful ones available on Packagist.

That's it

Laravel's macros are a compelling feature, which unlocks a bunch of possibilities to extend the framework, and helps to get rid of repeating the same logic across the app.
Use them with care, and your code will stay maintainable in the long run.

Hey, this is my first blog post ever, it can be better; it is not about the latest features available. I know that I am going to work on that)

If you have a free minute, drop me a line, I would appreciate every helpful advice and recommendation, on how to write better.

Have fun!