Laravel, a popular PHP framework known for its elegant syntax and extensive feature set, has gained a strong foothold in the web development community. One of the most intriguing aspects of Laravel lies in its ability to be extended and customized to fit unique project requirements. While Laravel offers a rich set of functionalities out of the box, it also provides developers with the means to enhance its core functionality through a concept called "macroables."
In this article, we will delve into the world of Laravel macroables, exploring their purpose, benefits, and real-world use cases. We will understand how macroables enable developers to extend Laravel's functionality, increase code reusability, and create concise, readable code. Furthermore, we will explore some practical examples that demonstrate the versatility and flexibility offered by macroables.
As usual, get your coffee prepared, and let's get started.
At its core, a macroable is a concept in Laravel that enables developers to dynamically add new methods to existing classes at runtime. It effectively extends the functionality of Laravel's core classes without modifying the framework's source code. This means that developers can augment the behavior of Laravel classes without waiting for updates or needing to create separate helper functions or utility classes.
The term "macroable" is derived from the idea of macros, which are commonly found in programming languages and refer to user-defined extensions that enhance the capabilities of existing constructs. Laravel's macroables provide a similar mechanism, allowing developers to define their own methods on core Laravel classes, such as collections, models, and even the request and response objects.
Laravel defines a Macroable trait within the Illuminate\Support\Traits namespace. If we visit the file src/Illuminate/Macroable/Traits/Macroable.php on the Laravel GitHub repository, we can see that line 17 has the following definition:
protected static $macros = [];
This $macros array serves as an associative array to store user-defined methods, where the key represents the name of the method and the value represents the callback function that will be invoked upon calling the method.
Note: please remember that when you use a trait inside a class, then all defined methods and attributes inside the trait become a part of the class as if they were defined inside of the class itself manually.
On line 26, we can see the definition of the method macro(). This method is responsible for registering new macros, it takes two arguments:
Then it adds a new entry to the $macros array we talked about earlier. It's a simple one-line function that looks like this:
So, if we want to extend the Illuminate\Http\Response class with a few macros, we can go to our app/Providers/AppServiceProvider.php file (or any other service provider) and write the following code inside the boot() method:
And then, this method can be easily invoked in your response as follows:
If we want to add another method, we can repeat the same steps and add a second, third, and fourth method, and so on ... However, with the large number of methods you want to add, things can easily turn into a mess thanks to the repetitive invocation of the macro() method. Luckily, there exists a more organized way to do this using the mixin() method.
The mixin() method simply takes all methods inside class X and adds them to class Y, allowing a more organized approach to register multiple macros at once. If we go to line 40, we can see the following implementation:
Here's what's happening in details:
I'll return to the mixins point later in this article.
Note: the IDE may not give you autocomplete hints on macro methods because they're not actual existent methods inside the class, they get defined during the runtime, and even then, they don't become actual methods inside the class, but they get called magically as we'll see in the next section.
At the end of the day, we should understand that macroables aren't actual methods that sit in the class itself, they're not even methods at all, but they get treated like methods thanks to the following piece of code on line 109:
In a previous article, I talked about the magic methods of PHP in detail, and I mentioned the __call() magic method. This magic method gets invoked whenever you try to invoke a non-existent method on a class object, which makes it a perfect choice for our Macroable trait, right?
When I define a macro called square() on Laravel collections like this:
$numbers = collect([2, 4, 7]);
$numbers->square();
Then what actually happens is that an object of Illuminate\Support\Collection is created, and then PHP tries to invoke the method square() on that object but surprisingly no such method exists, so it falls back to the __call() magic method which delegates the call to one of the defined macros that gets stored in the $macros array as we said earlier, and that's exactly how it works under the hood.
As we mentioned previously, using the macro() method to repeatedly add new macros isn't the best way to achieve your goal because your service provider will get messy with all these repetitions, and here is where mixins come into play.
Let's assume that we want to extend the Illuminate\Support\Collection class with a few macros:
To achieve this, we'll create a new folder inside our app folder and call it Mixins, you can call it any name you want, it doesn't matter as long as it's a descriptive name.
Next step, we create a class inside the new Mixins folder, call it whatever you want, for example, CustomCollectionMixin, and make sure to give it a namespace such as App\Mixins. Then we proceed to define the methods inside of it, these methods as the same ones that will be registered into macros.
Notice how each method returns a callable (or anonymous function), and notice that any parameters required by the macro get defined in the signature of the returned callable, and NOT in the signature of the main method.
Registering this class as a mixin is as simple as calling the mixin() method on the Illuminate\Support\Collection class inside a service provider such as the AppServiceProvider:
And then, you can easily call these methods anywhere on your code on any collection instance
My friend Mohammad Hammade has developed a Laravel package api-response. His goal was to unify the returned responses from the API endpoints, and he achieved this goal by adding new methods to the Response class in Laravel.
As we said earlier, the Response class is another macroable class in Laravel, so he created a service provider in his package and registered some macros inside it to extend the functionality of the response. You can take a look at the source code at: https://github.com/MohammadHammade/api-response/blob/main/src/Providers/ApiResponseServiceProvider.php
In conclusion, Laravel macros are a powerful and flexible feature that allows developers to extend the functionality of Laravel's core classes and components. With macros, you can add custom methods to existing classes, enhancing your code organization and improving code reusability. By encapsulating common tasks and behaviors within macros, you can streamline your development process, simplify complex operations, and create expressive, readable code.