Monday, 28th August 2017

Understanding Laravel Macros

Laravel has had a concept of macros since version 4.2, I am going to show you how to create them and what changes are coming to macros in Laravel 5.5 One place you may have seen/used macros in Laravel is ‘Response Macros’ which are documented in the official docs.

Response::macro('caps', function ($value) {
  return Response::make(strtoupper($value));
});

// return response()->caps('foo'); // returns 'FOO'
// return Response::caps('foo'); // returns 'FOO'

Macros provide a way to add functionality to classes you don’t own, in the above example we are adding a caps function to the Response class which can be accessed either statically or non-statically.

In order to use macros the class must use the Macroable Trait, which you can take a look at the Laravel 5.4 version here.

So let's add our own macro to the Str class which will check the length of a string named isLength. In a real application you would probably want to define your macros in a service provider, but for now I have simply added mine to my routes/web.php file.

use Illuminate\Support\Str;

Str::macro('isLength', function($str, $length) {
    return static::length($str) == $length;
});

dump(Str::isLength('foo', 4)); // false
dump(Str::isLength('foo', 3)); // true

The function above simply compares the length of the first parameter is the value of the second parameter. Macros can have number of parameters that you decide. It's important to note that we are calling static::length in the macro, as macros still have full access to methods on the original class.

What we have done so far has worked in Laravel for a while, and continues to work in Laravel 5.5, however 5.5 introduces class-based macros (known as mixins). Let's take a look.

We will continue with our string length example, and create a class called StringLengthMixin which will have two methods for string length comparison; isLongerThan and isShorterThan.

Initially we may assume that we could do something like this:

class StringLengthMixin {
    public function isLongerThan($str, $length)
    {
        return static::length($str) > $length;
    }

    public function isShorterThan($str, $length)
    {
        return static::length($str) < $length;
    }
}

Str::mixin(new StringLengthMixin);

dump(Str::isLongerThan('foobar', 5));

Unfortunately this does not work, and we get an error:

Error Screenshot

So it looks like the arguments are not being passed to our method. Our Mixin methods actually have to return a function which will be passed the arguments by Laravel like so:

class StringLengthMixin {
    public function isLongerThan()
    {
        return function($str, $length) {
            return static::length($str) > $length;
        };
    }

    public function isShorterThan()
    {
        return function($str, $length) {
            return static::length($str) < $length;
        };
    }
}

Str::mixin(new StringLengthMixin);

dump(Str::isLongerThan('foobar', 5)); // true
dump(Str::isShorterThan('foobar', 3)); // false

This is not ideal, as PHP does not support mixins as a language feature this is the best we can get for now.

You can see the Laravel 5.5 version of the Macroable Trait here. Laravel 5.5 is not released at the time of this post, so the Macroable code could change, but the release is imminent, so change is unlikely.

Conclusion

You now know how to extend some of Laravel’s classes with macros (not all classes use the trait so you should check that first). If you are creating a custom Laravel package then it might be worth adding the trait to your classes to make it easy for your users to add functionality. If you want to add functionality to your own classes then you should simply add the methods you need, macros exist to add functionality to classes that you don’t own.

Tweet me with any questions.

© 2020 Ashley Clarke. All rights reserved.