Sunday, 9 August 2015

PHP 7: specifying the context for closure to use

G'day:
Here's a cool new feature of PHP 7 that doesn't seem to have been spouted off about as much as other things like speed (oh, I don't know if anyone has mentioned: did you know PHP 7 is faster than PHP 5? I'm not sure if anyone's mentioned that. In the last 5min, anyhow) and "spaceship operators". In PHP 7 one can specify the context to which a closure should bind to with a single method call.

Here's an example. The code below just calls a closure using the usual approach:

<?php
//demo.php

class A {
    private $v = "A OBJECT CONTEXT\n";
    private $closureToInject;

    function __construct($b){
        $this->b = $b;
        $this->injectClosure();
    }

    private function injectClosure(){
        $this->closureToInject = function(){
            return $this->v;
        };
        $this->b->injected = $this->closureToInject;
    }

    function callDirectly(){
        return ($this->b->injected)();
    }
}

class B {
    private $v = "B OBJECT CONTEXT\n";
    public $injected;
}


$b = new B();
$a = new A($b);

echo $a->callDirectly();
  • The bulk of this is just getting two disconnected environments which each have their own $v property: one in the A class, and one in the B class.
  • B has a public property which we can inject a closure into if we so choose (we do).
  • In A we define a function expression (which uses closure) which simply echoes the value of the $v property.
  • And then we call said function.




This outputs:

>php demo.php
A OBJECT CONTEXT

>

It's important to note that because the function expression was defined within the context of the A class code, then it uses that context to perform the closure. It encloses the $this->v of the A object. It doesn't matter that the function is then injected into an instance of B which has its own $this->v, this is not the context that the closure of $this->v has used.

However sometimes it would be handy to a) define a function with a function expression; b) not be restricted to binding it to only the context it was defined within. One can actually achieve this with older versions, of PHP, but PHP7 has tidied it up.

All we need to do is this (this is an additional method in the A class):

function callWithContext(){
    return $this->b->injected->call($this->b);
}

call() is a method of the Closure class which allows one to specify the context that closure should use, instead of the defining context. So running this code results in:

>php demo.php
B OBJECT CONTEXT

>

Cool. Sure this is only a refinement to the bindTo() approach already available in PHP (I was unaware of this until today), but there's more horsing around with that, which call() dispenses with. I guess it's more cool to me as I've always missed being able to do this with function expressions in CFML. All function expressions in CFML bind to their defining context, and that's take. One can use a function statement to define a function which does not do this, but there are other limitations on those which renders them irrelevant as part of a solution to this challenge. It'd be good if CFML had this feature too, I reckon.

Anyway, a fairly inconsequential PHP 7 feature, but one I quite like. And can't find fault with ;-)

--
Adam