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<?php2 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.
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<?php2 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<?php2 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
MethodsThe 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.
__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. The10 // 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 does17 // the '__call' function will return the value of the18 // '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-specific26 // 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.
∎
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.