Laravel Macroable is a package, that allows adding extra functionality to a class that is missing in the class definition through a simple trait.
It must not have to be in Laravel’s internal classes. Any class that uses Illuminate\Support\Traits\Macroable
will allow it to extend its functionality.
How to use it?
If you’re not using Laravel, you will have to install the following package.
composer require illuminate/macroable
And require the autoload.php file from the vendor directory.
Let’s define a class.
<?phpuse Illuminate\Support\Traits\Macroable;class Greeting { use Macroable; public function __construct(protected string $name) { } public function sayHello() { return sprintf('Hello %s%s', $this->name, PHP_EOL); } }
In the above class, we have used the Macroable
trait. That’s it in the class definition.
Now to extend the functionality, in our codebase, we can do as
Greeting::macro('greet', function (?string $greeting = null) {
return !is_null($greeting)
? sprintf('%s %s%s', $greeting, $this->name, PHP_EOL)
: $this->sayHello();
});
Here, the ::macro
method allows us to extend the functionality. The first parameter in the macro method is the name of the function that we will use in our code. And the second parameter is a callable.
As you see in the above snippet, $this
keyword was used inside our closure. By using macro, we will have access to the Class/Object we are adding those macros.
And, whatever parameters we add in the closure, we will have to provide those values when we call it.
Check the usage
$greeting = new Greeting('Anik');// Calling class's method echo $greeting->sayHello(); // "Hello Anik"// Calling the greet method, we defined using Class::macro echo $greeting->greet('Good evening'); // "Good evening Anik" echo $greeting->greet('Goodbye'); // "Goodbye Anik" echo $greeting->greet(); // "Hello Anik"
We are just not limited to using macros from an Object context. It is also possible to call as a static method.
Greeting::macro('whatTimeIsIt', function () { return sprintf( 'Hey, it is %s', (new DateTimeImmutable())->format('H:i:s') ); });echo Greeting::whatTimeIsIt(); // "Hey, it is 06:47:52"
When you’re calling those as static methods, you will have access to the class static properties and methods as well.
A method added through the macros can be called from both the class and object context. Based on how you’re calling those methods, it will either have access to the $this
keyword or not.
What is a mixin?
So far, we have added new methods using the ::macro
method and closures. But, if you want to add a few more methods, the ::macro
will go messy, and keeping track of these methods can be cumbersome. Another approach to add macros is using mixin. With mixin, you can tell that “I have XYZ class, and you add all the methods available in that class.”
Let’s try the previous examples using Mixin.
Firstly, let us define a class that will hold all the extended functionality.
<?phpclass GreetingMixin { public function greet() { return function (?string $greeting = null) { return $greeting ? sprintf('%s %s%s', $greeting, $this->name, PHP_EOL) : $this->sayHello(); }; } public function whatTimeIsIt() { return function () { return sprintf( 'Hey, it is %s', (new DateTimeImmutable())->format('H:i:s') ); }; } }
We named our class GreetingMixin
but you can name it whatever you want. Mixin class methods should either be Public or Protected. Private methods are not considered to be added on macros. These methods should not have any parameters and they should return a Closure/Anonymous method. Closures can have any parameters you want and these closures are going to be bound to the actual class to extend the functionality.
Now, we have to connect theGreetingMixin
class to the Greeting
class using the ::mixin
method.
Greeting::mixin(new GreetingMixin());
The second parameter in ::mixin
method receives a bool
flag, default set to true
, determining if you want to replace any existing macro defined previously.
Now, if we call these methods, just like before and will get the exact same result.
$greeting = new Greeting('Anik');// Class method echo $greeting->sayHello(); // "Hello Anik"//Macro methods, added through ::mixin echo $greeting->greet('Good evening'); // "Good evening Anik" echo $greeting->greet('Goodbye'); // "Goodbye Anik" echo $greeting->greet(); // "Hello Anik" echo Greeting::whatTimeIsIt(); // "Hey, it is 07:02:01"
Where to define these macros?
If you’re going to use it in Laravel, you can put it anywhere you want. You can put in the routes file, or in the config files or even in the controller just before using it. You are free to do it. But just make sure that the definitions are executed before you start using the macro methods.
But, what is the point if you add them in the controller? Maybe you’re duplicating it in the next method? Or isn’t it a bad practice to put these things in route files or in config? I just gave you an example.
Putting these definitions in the service provider should be a good practice. If your macros are limited then maybe in the AppServiceProvider is a good place to start. If not, then you can add a new Provider something like AdditionalMacroServiceProvider and add them in the boot method and finally add in your service providers array.
Actual use cases?
Have you ever been through the Laravel’s internal codebase? If yes, then you might have seen it already. Otherwise,
- Illuminate\Http\Request
- Illuminate\Http\Response
- Illuminate\Support\Arr
- Illuminate\Support\Collection
Can be a good place to check. You can find more by looking through the code.
That’s all for this article.
Happy coding. ❤