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 returnstrue
if the$macro
is found, otherwise it returnsfalse
.__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 ofBadMethodCallException
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 ofBadMethodCallException
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.
∎