Anonymous functions, in php known as Closures
, come in handy very often. One application in particular is very useful – extending classes, thanks to the capability to bind the closure to an instance and class scope.
However, binding to instance may prove a bit tricky, so let’s go through it together.
Let’s start with a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php class Foo { private $prop = 'bar'; } $closure = function () { return $this->prop; }; $closureInFooScope = $closure->bindTo($foo, get_class($foo)); |
Unfortunately above code will show following error Warning: Cannot bind an instance to a static closure
.
While it’s rather unlikely that you use it in a static context like above, but chances are you do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// a trait making class extendable trait Macroable { public static function macro($name, $callback) { ... } public function __call($method, $params) { // $callback->bindTo($this) is called here } } class Foo { use Macroable; private $firstName; private $lastName; public static function boot() { static::macro('fullName', function () { return $this->firstName . ' ' . $this->lastName; }); } } |
OK, it’s not very sophisticated example, but you get the idea already (you can find real use-case in eg. Laravel Metable). Here, the closure is created again in a static context and it makes perfect sense in this case, but it means we’ll get the same error as soon as macro is called: $foo->fullName()
.
Important thing to remember though is that php doesn’t care about the context, in which we invoke the process but only where the closure is created. That being said, a workaround for this is as simple as having additional class, providing instance context:
1 2 3 4 5 6 7 8 9 10 11 |
class ClosureFactory { public function getClosure() { return function () { return $this->firstName . ' ' . $this->lastName; }; } } |
then we can rewrite our boot
method:
1 2 3 4 5 6 |
public static function boot() { static::macro('fullName', (new ClosureFactory)->getClosure()); } |
and make it work!
Having a bindTo
method that in fact can’t do its job is a huge drawback in php 5.x, but we can easily work around the problem.
Wait, did I say php5? Right, because awaited and upcoming PHP7 fixes it. Speaking of, worth reading walkthrough to the Seven: oreilly.com/web-platform/free/files/upgrading-to-php-seven.pdf