PHP:类型提示-`Closure`和`Callable`之间的区别


128

我注意到,如果我们希望某些回调函数能够运行,则可以使用ClosureCallable作为类型提示。例如:

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

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

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

callFunc1($function); // Hello, World!
callFunc2($function); // Hello, World!

题:

这里有什么区别?换句话说,何时使用Closure以及何时使用CallableOR,它们具有相同的目的?

Answers:


173

不同之处在于,a Closure必须是匿名函数,在这里callable也可以是普通函数。

您可以使用下面的示例查看/测试它,您将看到第一个错误:

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!

因此,如果您只想键入提示匿名函数,请使用:Closure并且如果您还希望允许普通函数callable用作类型提示。


5
您也可以通过传递数组使用可调用的类方法,例如["Foo", "bar"]for Foo::bar[$foo, "bar"]for $foo->bar
安德里亚

17
Offtopic,但相关的:自PHP 7.1,你现在可以轻松地转换为封闭:callFunc1(Closure::fromCallable("xy"))wiki.php.net/rfc/closurefromcallable
nevvermind

我仍然不明白为什么我只想调用匿名函数。如果共享代码,则不必关心函数的来源。我认为,PHP的一个怪癖应该消除一种或另一种方式以避免混淆。但老实说,我喜欢Closure+ Closure::fromCallable方法,因为像字符串或数组一样callable一直很奇怪。
Robo Robok

2
@RoboRobok仅要求Closure(匿名函数)而不是的原因之一callable是防止访问超出被调用函数的范围。例如,当您拥有时,您private method不希望滥用的人访问它callable。请参阅:3v4l.org/7TSf2
fyrye

58

它们之间的主要区别是a closure是一个callable一个类型

callable类型接受可以被称为的任何东西:

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

其中a closure接受匿名函数。请注意,在PHP版本7.1中,您可以将函数转换为闭包,如下所示: Closure::fromCallable('functionName')


例:

namespace foo{
  class bar{
    private $val = 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
}

那么,为什么要使用closureover callable

严,因为closure是有一些额外的方法的对象:call()bind()bindto()。它们允许您使用在类外部声明的函数,并像在类内部一样执行该函数。

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

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

您不希望在普通函数上调用方法,因为这会引发致命错误。因此,为了避免这种情况,您必须编写类似以下内容的内容:

if($cb instanceof \Closure){}

每次执行此检查都是没有意义的。因此,如果要使用这些方法,请声明参数为closure。否则,只需使用正常callback。这条路; 函数调用而不是代码引发错误,从而使诊断变得更加容易。

在一个侧面说明:closure类不能扩展为最终


1
您也可以在其他范围内重用可调用对象。
Bimal Poudel,

这意味着您不必具有callable任何名称空间的资格。
杰夫·普基特

0

值得一提的是,这不适用于PHP 5.3.21至5.3.29版本。

在任何这些版本中,您都将获得类似以下的输出:

你好,世界!可捕获的致命错误:传递给callFunc2()的参数1必须是> Callable的实例,已给出Closure的实例,在第16行的/ in / kqeYD中调用并在第7行的/ in / kqeYD中定义

进程退出,代码为255。

可以使用https://3v4l.org/kqeYD#v5321进行尝试

最好的祝福,


2
除了发布代码链接之外,您还应该在此处发布代码,以防其他人遇到此问题并且您提供的链接中断。您也可以在帖子中提供输出以提供帮助。
韦达

5
这是因为callablePHP 5.4中引入了。在此之前,PHP期待一个名为类的实例callable,就像你指定一个提示的PDODateTime\My\Random\ClassName
戴维·沙菲克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.