One of the features of Laravel is the ability to extend functionality through custom methods thanks to a concept called "macros".
In this article, we will look at what they are, how to create them, and how they can improve our development.
Understanding Macros
A macro is the injection of custom code into the core of a framework, without the need for complex inheritance or modification.
To implement macros we use the Macroable trait, which has two main methods macro()
and mixin()
.
The point is that by using PHP's magic methods __call
and __callStatic
, we add additional methods to existing application classes at runtime without changing the core.
Advantages of macros and mixins
-
Code reuse: By encapsulating custom logic in macros, you can apply it to different parts of your project without duplicating code.
-
Code cleanliness: Macros help keep your code clean and user-friendly by bringing logic into separate service classes.
-
Improved performance: Macros allow you to extend Laravel core functionality more efficiently and maintain compatibility with future framework updates, reducing implementation time.
Creating Macro & Where should I define them?
- It is a good idea to add this to the
App\Providers\AppServiceProvider
method in theboot()
method, since it is called after all other service providers are registered and you have access to all other services.
Str::macro('toLower', fn($str) => strtolower($str));
- The second way is to create a new service provider
App\Providers\MacroableServiceProvider
and register it in AppServiceProvider. For example,
class MacroableServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// TODO
}
}
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->app->register(MacroableServiceProvider::class);
}
}
Macros can be defined in any class that defines Macroable
. The framework provides the ability to extend the capabilities of a large number of classes.
Proprietary classes also have this capability, just add a few lines of code.
use Illuminate\Support\Traits\Macroable;
$class = new class {
use Macroable;
public function hello()
{
return 'world';
}
};
$class::macro('hey', fn() => 'girl!');
dd(
$class->hello(), // world
$class->hey(), // girl!
);
Below is a list of popular classes from Laravel core that utilize the concept of macros:
Illuminate\Http\Request
Illuminate\Http\Response
Illuminate\Support\Arr
Illuminate\Support\Collection
Illuminate\Support\Str
Illuminate\Routing\Router
Illuminate\Validation\Rule
Illuminate\Database\Query\Builder
The list is not complete. You can use the project search with the keyword
use Macroable
to find more.
Mixin & Strong Macro
The concept of macros allows multiple user-defined functions to be registered at once via a mixin class. Each method of which must return a closure. The name of the method acts as the name of the macro.
Registration of mixins is done in the same way as with a macro, using the mixin()
method from the base class.
Let's look at the user authentication rule extension for an example.
public function boot(): void
{
\Illuminate\Auth\RequestGuard::mixin(new class {
public function loginUsingId(): \Closure
{
return function($id) {
/** @var Authenticatable $loggedInUser */
$loggedInUser = $this->authenticate();
// TODO
};
}
});
}
Grouping macros into separate classes, according to some principles, will help to understand the project more easily and make it easier to mashup.
Conclusion
Whether you are building a small website or a complex web application, Laravel macros can be a valuable addition to a developer's toolkit.