PHP 5.3 Closures and Reflection

4 minute read

Note

This post is outdated (2009). Specifically, the Callable typehint and Closure typehints should be preferred to is_callable && is_object. You can also use $this inside anonymous functions from 5.4 as well.

One of the things I’ve been really looking forward to in PHP 5.3 is seeing what I could do with closures. There’s some handy obvious use cases (strategy object substitutes, for instance) but I’m interested in some more dynamic things so I grabbed a fresh set of packages from DotDeb and started hitting closures with the reflection API.

I’m pleased to report that things Just Work(tm). Couple of examples below:

$func = function($one, $two = 'test') {
    echo 'test function ran'.PHP_EOL;
};
$info = new ReflectionFunction($func);
var_dump(
    $info->getName(), 
    $info->getNumberOfParameters(), 
    $info->getNumberOfRequiredParameters()
);

Which returns:

string(9) "{closure}"
int(2)
int(1)

I’m glad it’s there, but the closure’s trailing semicolon still tastes a bit weird. Anyways, couple of interesting things here. First off, the function reflection API seems to work. No surprise, but a relief nonetheless.

Secondly, if you look at the returned name for a closure it’s always “{closure}”. This remains the same irregardless of how many closures you currently have in scope. The PHP C source code doesn’t seem to be setting the name to a constant anywhere* so I wouldn’t rely on this as a method to detect closures since it may be subject to change. The manual also states that while closures are implemented using the closure class, that’s not a reliable way to detect a closure either. This leads to the question of what IS the recommended method of detecting a closure. To be honest, I have no idea. The docs are still mum on this issue, there doesn’t seem to be any is_function/lambda/closure function in the new library and I can’t find any examples. Luckily, is_callable() does work so you can do some validity checking but that doesn’t differentiate between the string or array callback types. Little weird, but I’m hoping there’ll be something more about this soon. EDIT: The best solution I’ve found so far is (is_callable($func) && is_object($func)).

Okay, let’s try a more blinged out example:

$name = 'Ross';
$func2 = function($one, $two = 'test') use ($name) {
    return true;
};
 
$info2 = new ReflectionFunction($func2);
var_dump($info2, 
    $info2->getParameters(),
    $info2->getName(),
    $info2->isInternal(),
    $info2->isDisabled(),
    $info2->getFileName(),
    $info2->getStartLine(),
    $info2->getEndline(),
    $info2->returnsReference(),
    $info2->getDocComment()
);

which returns

//getParameters()
object(ReflectionFunction)#2 (1) {
  ["name"]=>
  string(9) "{closure}"
}
array(2) {
  [0]=>
  &object(ReflectionParameter)#3 (1) {
    ["name"]=>
    string(3) "one"
  }
  [1]=>
  &object(ReflectionParameter)#4 (1) {
    ["name"]=>
    string(3) "two"
  }
}
string(9) "{closure}"   //getName()
bool(false)             //isInternal()
bool(false)             //isDisabled()
string(32) "/path/closure.php" //getFileName()
int(72)                 //getStartLine()
int(74)                 //getEndline()
bool(false)             //returnsReference()
string(27) "/**         //getDocComment()
 * Docblock Comment
 */"

Most of that speaks for itself. It’s cool that docblock reflection works, especially with some of the docblock DSL work going on (as seen in the Doctrine 2.0 teaser, for example). Also, in case it’s not immediately obvious, you can return a reference from a closure, you just need to put the ampersand between the parantheses and the function keyword (whitespace isn’t required, you can just use “function&($etc)”.

The reflection parameters work fine except that I can’t seem to reflect on anything handed in through the “use” clause. That’s a bit of a shame, but considering that you can’t really manipulate the variables imported, it doesn’t really matter. The important thing is that we can reflect on the arguments (and their type hints) which paves the way for closure dependency injection and other outright tomfoolery.

Moving on to classes, let’s try assigning a closure directly to a property.

class ClassName {
    public $test = function($test) { echo 'test'; };
}

Unfortunately, that returns an error:

PHP Parse error:  syntax error, unexpected T_FUNCTION in

I’m admittedly a bit disappointed here, I was really hoping this would work.

There’s a good IBM Developer Works article on some of this, but some of the things shown in the examples no longer work for me. For instance, the following example:

class ClassName {
    public function __construct() {
        $this->test = function($arg) { 
            return function () { echo 'grr'; };
        };
    }
}
 
$c = new ClassName();
$c->test();*/
 
class Dog
{
    private $_name;
    protected $_color;
 
    public function __construct($name, $color)
    {
         $this->_name = $name;
         $this->_color = $color;
    }
 
    public function greet($greeting)
    {
         return function() use ($greeting) {
             echo "$greeting, I am a {$this->_color} dog named {$this->_name}.";
         };
    }
}
 
$dog = new Dog("Rover","red");
$dog->greet("Hello");

When the IBM article was written the returned closure would have been executed, displaying the cute dog message. Unfortunately, this was cut before 5.3.0 final was out and the function now only returns the closure. The changes seem to be summarized in the rfc:closures:removal-of-this which basically says you can’t use $this inside a closure which makes some sense. Keep an eye out when returning closures because if you accidentally use $this the error won’t spring up until the function is executed which could be a long ways away by then; it’s not a big deal though since the error message gives the original line holding $this. The RFC also says a few other goodies were hauled out then as well like “static” and the fascinatingly odd getClosure() method which would’ve returned an object method as a closure.

The returned closure, by the way looks like this when var_dump’ed:

object(Closure)#2 (1) {
  ["static"]=>
  array(1) {
    ["greeting"]=>
    string(5) "Hello"
  }
}

Neat, it keeps holding the imported variables. I wonder…

$func = $dog->greet("Hello");
$info = new ReflectionClass($func);
var_dump($info->getProperties());

Nope.

array(0) {
}

Oh well. ;)

Updated: