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

后端 未结 3 1843
有刺的猬
有刺的猬 2020-12-04 07:45

I noticed that I can use either of Closure or Callable as type hint if we expected some callback function to run. For example:

func         


        
相关标签:
3条回答
  • 2020-12-04 07:58

    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.

    0 讨论(0)
  • 2020-12-04 07:58

    It's worth mentioning that this won't work for PHP versions 5.3.21 to 5.3.29.

    In any of those versions you will get an output like:

    Hello, World! Catchable fatal error: Argument 1 passed to callFunc2() must be an instance of > Callable, instance of Closure given, called in /in/kqeYD on line 16 and defined in /in/kqeYD on line 7

    Process exited with code 255.

    One can try that out using https://3v4l.org/kqeYD#v5321

    Best regards,

    0 讨论(0)
  • 2020-12-04 08:15

    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.

    0 讨论(0)
提交回复
热议问题