Posted

While writing my slides for the PHP UK Conference I looked much more closely at the changes to closures in PHP 5.4.

The biggest change that people have been asking for, is access to $this when the closure is defined inside of an object method. This has been added:

Simple enough. However, it didn’t stop there; there was also the addition of Closure::bind() and Closure->bindTo(). These methods are identical except one is a static method into which the closure is passed, the second an instance method on the closure itself.

These methods both take two arguments (on top of the closure for the static version): $newthis and $newscope.

What this means is that unlike the regular object model the concept of $this and lexical scope (what is in scope for the function with regards to private/protected methods inside objects) are completely separated.

You can change $this to be a different object, while leaving the scope as the current object which if you then pass the current object in gives the different object access to the private/protected methods of the current method. That sounds complicated right? It is. A more likely scenario is to keep $this the same while changing the scope to another object (or static) such that you can pretend you’re accessing it from that other object and not hit private/protected methods.

Either of these scenarios is great for unit testing — I’m sure we’ll see this creep into PHPUnit sooner rather than later (I’m pretty sure Sebastian was promoting access to private/protected methods in reflection at some point — this makes that moot).

However, this can be seriously confusing, consider the following code that intentionally busts the OO model we know and love:

We have two classes, Bar with two methods, public function A() { } and private B() { }, we then have Bat which uses the __call() magic method to act as a proxy for Bar, as well as public function C() and private function D().

Inside __call() we instantiate an instance of Bar as $bar and then if __call() is able to call the requested function on $bar it simply does so, otherwise it creates a closure, passing in both the requested function name and $bar. Then using Closure::bind() we leave $this as the current object, but change the lexical scope to be that of $bar. The result of this is a new closure that is assigned to $bustOO

What this means is that inside the context of the new closure, any call on $this is referencing the object in which it was originally instantiated, but any calls on the passed in $bar will be the same as calling $this from inside $bar itself.

This means that the call to the private $bar->B() will now work. Also, the call to $this->C() works (calling Bat->C()), but things get tricky when we call $this->D().

When this happens, because the lexical scope is $bar, it cannot access Bat->D(), which means it calls Bat->__call(). Inside __call() the is_callable(array($bar, $function)) check fails (the method doesn’t exist on then tries to call $bar->D() through the callback, resulting in Fatal error: Call to undefined method Bar::D().

The full output looks like this:

Talk about head spinning!