Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP: Type hinting - Difference between `Closure` and `Callable`

Tags:

php

The difference is, that a Closure must be an anonymous function, where callable also can be a normal function.

You can see/test this with the example below and you will see that you will get an error for the first one:

function callFunc1(Closure $closure) {
    $closure();
}

function callFunc2(Callable $callback) {
    $callback();
}

function xy() {
    echo 'Hello, World!';
}

callFunc1("xy"); // Catchable fatal error: Argument 1 passed to callFunc1() must be an instance of Closure, string given
callFunc2("xy"); // Hello, World!

So if you only want to type hint anonymous function use: Closure and if you want also to allow normal functions use callable as type hint.


The main difference between them is that a closure is a class and callable a type.

The callable type accepts anything that can be called:

var_dump(
  is_callable('functionName'),
  is_callable([$myClass, 'methodName']),
  is_callable(function(){})
); // all true

Where a closure will only accept an anonymous function. Note that in PHP version 7.1 you can convert functions to a closure like so: Closure::fromCallable('functionName').


Example:

namespace foo{
  class bar{
    private $baz = 10;

    function myCallable(callable $cb){$cb()}
    function myClosure(\Closure $cb){$cb()} // type hint must refer to global namespace
  }

  function func(){}
  $cb = function(){};
  $fb = new bar;

  $fb->myCallable(function(){});
  $fb->myCallable($cb);
  $fb->myCallable('func');

  $fb->myClosure(function(){});
  $fb->myClosure($cb);
  $fb->myClosure(\Closure::fromCallable('func'));
  $fb->myClosure('func'); # TypeError
}

So why use a closure over callable?

Strictness because a closure is an object that has some additional methods: call(), bind() and bindto(). They allow you to use a function declared outside of a class and execute it as if it was inside a class:

$inject = function($i){return $this->baz * $i;};
$cb1 = \Closure::bind($inject, $fb);
$cb2 = $inject->bindTo($fb);

echo $cb1->call($fb, 2); // 20
echo $cb2(3);            // 30

You would not like to call methods on a normal function as that will raise fatal errors. So in order to circumvent that you would have to write something like:

if($cb instanceof \Closure){}

To do this check every time is pointless. So if you want to use those methods state that the argument is a closure. Otherwise just use a normal callback. This way; An error is raised on function call instead of your code causing it making it much easier to diagnose.

On a side note: The closure class cannot be extended as its final.