Laravel Macros: An Easy Way to Extend Laravel Components

November 21, 2016 —John Koster

Macros are a way to reduce the lines of code a developer has to write, and can be traditionally thought of as "shortcuts". Laravel generally uses macros to add, or inject, functions into various classes at runtime. Developers familiar with C# can see the similarities between C#'s extension methods and Laravel's macros.

For example, the following C# will add a new method CountWords to the String class:

1namespace ExtensionExample
2{
3 public static class StringExtension
4 {
5 public static int CountWords(this String str)
6 {
7 // Implementation of CountWords in C#
8 // return word count here.
9 }
10 }
11}

Using Laravel's macros a similar end-result can be accomplished. The following example will add a countWords method to the Str support class:

1<?php
2 
3use Illuminate\Support\Str;
4 
5Str::macro('countWords', function($value)
6{
7 return str_word_count($value);
8});

The article Default Laravel Classes That Support Macros details (at the time of this writing) the six Laravel components that ship with direct support for macros.

#Creating a Macro

To create a macro, first locate the desired class to create a macro for. In the following examples, a rot13 macro will be created for the \Illuminate\Support\Str class. The first helper function will be a wrapper of PHP's str_rot13 function, which performs the ROT13 encoding on a given string.

The macro function is easily created like so:

1<?php
2 
3use \Illuminate\Support\Str;
4 
5Str::macro('rot13', function($value)
6{
7 return str_rot13($value);
8});

After the rot13 macro has been defined, code can be written like the following:

1<?php
2 
3$value = Str::rot13('test');

In the above example, $value would be assigned the value grfg.

The Macroable traits internal mechanisms allow macros to be called from both static and instance contexts.

Macros essentially become part of the class they are created for because of the internal mechanisms that power them. Because of this, macro functions have access to all the private and protected members of the class they are created for. With this knowledge, very powerful macro functions can be created to add functionality to the framework that would otherwise be difficult to accomplish.

The following code example highlights this fact by returning the $studlyCache member of the \Illuminate\Support\Str class, which is declared to be protected static:

1<?php
2 
3Str::macro('getStudlyCache', function()
4{
5 return self::$studlyCache;
6});
7 
8// Call the 'studly' function a few times to make sure there
9// are cache entries.
10Str::studly('This is a test');
11Str::studly('This is another test');
12 
13$cacheEntries = Str::getStudlyCache();

After the above code executes, the $cacheEntries variable would have the value:

1array:2 [
2 "This is a test" => "ThisIsATest"
3 "This is another test" => "ThisIsAnotherTest"
4]

Trying to access the $studlyCache member directly would throw an instance of Symfony\Component\Debug \Exception\FatalErrorExcepption with the following message:

1Cannot access protected property Illuminate\Support\Str::$studlyCache

#Macroable Methods

The following methods are available in the Macroable trait:

macro($name, callable $macro)

: The macro method is used to create a macro, and can be examined in the previous code examples.

hasMacro($macro)

: The hasMacro method is used to determine if a given class has a macro with the name $macro defined. It returns true if the $macro is found, otherwise it returns false.

__call($method, $parameters)

: The Macroable trait implements PHP's magic __call method to intercept calls to methods when in object context that do not exist in the current class. If a macro with the given method name is found, it is evaluated and the result returned. If no macro is found with the given name, an instance of BadMethodCallException is thrown.

__callStatic($method, $parameters)

: The Macroable trait implements PHP's magic __callStatic method to intercept calls to a methods when in static context that do not exist on the target class. If a macro with the given method name is found, it is evaluated and the result returned. If no macro is found with the given name, an instance of BadMethodCallException is thrown.

#Important Notes On __call and __callStatic

Because Macroable depends on both the __call and __callStatic methods to be available for its own use, it is currently not possible to use the Macroable trait in a class that also requires returning a value from either __call or __callStatic without modifying the parent class's __call or __callStatic methods to accommodate the macro features. However, accommodating the macro features in a class that requires one, or both of the above magic methods is quite simple:

1<?php
2 
3use Illuminate\Support\Macroable;
4 
5class ExampleClass
6{
7 use Macroable {
8 // Because this class also defines a '__call' method, a new
9 // name has to be given to the traits '__call' method. The
10 // new name in this case is 'macroCall'.
11 __call as macroCall;
12 }
13 
14 public function __call($method, $parameters)
15 {
16 // This will first check if a macro exists, and if it does
17 // the '__call' function will return the value of the
18 // 'macroCall' function (which is defined in the 'Macroable')
19 // trait.
20 if (static::hasMacro($method))
21 {
22 return $this->macroCall($method, $parameters);
23 }
24 
25 // If no macro exists, continue with class-specific
26 // implementation of '__call'.
27 }
28 
29}

The above example is actually utilized within the framework itself with the implementation of \Illuminate\Cache\Repository.

The article Laravel Classes That Support Macros details all of the Laravel components that support macros; the article also contains additional information related to the Eloquent Builder component.

Some absolutely amazing
people

The following amazing people help support this site and my open source projects ♥️
If you're interesting in supporting my work and want to show up on this list, check out my GitHub Sponsors Profile.