PHP 7.1: create a Closure from a callable

September 13, 2016php, php-7.1, english
 This has been written a few years ago and might not be up-to-date…

Cet article est aussi disponible en français.
This is the 7th post in a series about PHP 7.1.


Traditionally, a callable is often handled, in PHP, as a string. For example, we can use the array_map() function, invoking the 'trim' callable on all items of an array.


This approach suffers from a major problem: the callable’s validity is only checked when it’s called!

For example, let’s consider the following portion of code:

function my_function($param)
{
    printf("%s(%s)\n", __FUNCTION__, $param);
}

// No problem is detected here, despite the typo
$nom = 'my_functlon';

// Here, a great number of lines of code
// and many more

// It’s only here we have a problem:
// Warning: call_user_func() expects parameter 1 to be a valid callback, function 'my_functlon' not found or invalid function name
call_user_func($nom, 42);

The typo in the function’s name is not detected when we commit it … but only several lines of code farther, when we try executing it.

In a real case, split over dozens or hundreds of files, maybe also adding a few configuration files in the mix, I’ll let you imagine how hard it could become to debug! Maybe you’ve been in this situation before, actually?


PHP 7.1 solves this problem by adding a new Closure::fromCallable() method. It receives a callable as a parameter, validates it, and returns a Closure referencing it. It is this Closure you will later work with.

We can rewrite the previous example, exploiting this feature. An exception, instance of TypeError, is thrown if the callable you specified is not valid:

function my_function() {
    var_dump(__FUNCTION__);
}

$closure = Closure::fromCallable('my_function');
$closure();  // string(11) "my_function"


$closure = Closure::fromCallable('plop');
// TypeError: Failed to create closure from callable: function 'plop' not found or invalid function name

The nice thing is, if there is a typo in the function’s name (or if it’s not defined because you didn’t include the file where it’s declared), it is immediately detected. You don’t have to wait until you try executing it:

function my_function($param)
{
    printf("%s(%s)\n", __FUNCTION__, $param);
}

// The typo / error is immediately detected
// Fatal error: Uncaught TypeError: Failed to create closure from callable: function 'my_functlon' not found or invalid function name
$callable = Closure::fromCallable('my_functlon');

// Here, a great number of lines of code
// and many more

// No problem here: if there was no error earlier,
// then $callable is always valid
call_user_func($callable, 42);

This same problem, the lack of validation of callables at declaration-time, was also present for other kinds of callables, like those pointing to a method of an object:

class MyClass
{
    protected $data;

    public function __construct($param)
    {
        $this->data = $param;
    }

    public function myMethod()
    {
        printf("%s() -> %s\n", __METHOD__, $this->data);
    }
}


// PHP doesn’t raise any error here, even if it is on this line
// we did a typo!
$callable = [new MyClass(42), 'myMehtod'];


// No error there either!
call_callable($callable);


function call_callable($callable)
{
    // We only get an error here!
    // Fatal error: Uncaught Error: Call to undefined method MyClass::myMehtod()
    $callable();
}

Good news: with PHP 7.1, we can also use Closure::fromCallable() in this case. We also benefit from the validation of the callable when the Closure is created, long before it’s executed:

class MyClass
{
    protected $data;

    public function __construct($param)
    {
        $this->data = $param;
    }

    public function myMethod()
    {
        printf("%s() -> %s\n", __METHOD__, $this->data);
    }
}


// The error is detected right away
// Fatal error: Uncaught TypeError: Failed to create closure from callable: class 'MyClass' does not have a method 'myMehtod'
$callable = Closure::fromCallable([new MyClass(42), 'myMehtod']);


// No problem here: $callable is necessarily valid
call_callable($callable);


// Added bonus: we can use a type-declaration
function call_callable(callable $callable)
{
    $callable();
}

Well, PHP 7.1 brings us a new method, which will help validate callables earlier, in turn facilitating error detection and handling!


‣ The RFC: Closure from callable function