PHP Closure As Macro
Macros
When we talk about closures we often think about anonymous functions. Functions without name:
If we take a context of a single web request, named functions exist for the request life cycle. Anonymous functions exist only as long as you need them to be. So they can be considered as little macros. In the body of the anonymus function we code some logic, and then we simply execute the macro where we need it.
Here we have a macro to count a square of a number and it exists only for as long as it is needed.
Objects
When we create an anonymous function and assign it to the variable, PHP turns it into the object of
the Closure class. The Closure class is an extraordinary class. We can’t create instances of it
by this code: $closure = new Closure();
. And we can’t extend it with child classes, because it is
marked as final. But this class has an interesting method bindTo()
.
This method allows you to get access to protected and private properties of other objects. It creates a
clone of the closure, but one bound to another object. So if the closure has a reference to $this
,
the scope of $this
can be changed dynamically:
In the code above there is no way to get the value of the $value
property. But we can do it with the help of
the bindTo method and a closure with the reference to $this
:
The bindTo method accepts two parameters. The first one is the object that closure is bound to. The second is optional and provides a new scope for the closure. When we pass the same object as the second parameter, we bind this closure to the object as it is a method of that object.
Laravel’s Implementation of Macros
Lets create a simple class:
This code will fail because of the undefined method say()
. Now we try to use Laravel’s MacroableTrait trait:
This can be achieved with the help of the MacroableTrait. It allows us to dynamically add a method to any PHP class. So, how it works?
The first method to pay attention is macro
:
This method stores passed closure in a static property, indexed by $name
.
There are also two magic methods: __call
and
__callStatic
. They are executed when we try to call a method that does not exist in the object or in the class.
First of all we check, if we have stored a macro with such method name with hasMacro()
method. If true
we create a
new closure and bind it to our class, if it is a static call, or to an object, if not: