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

Dev*_*v01 108 php

我注意到如果我们期望一些回调函数运行,我可以使用ClosureCallable作为类型提示.例如:

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

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

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

callFunc1($function); // Hello, World!
callFunc2($function); // Hello, World!
Run Code Online (Sandbox Code Playgroud)

题:

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

Riz*_*123 147

区别在于,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!
Run Code Online (Sandbox Code Playgroud)

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

  • Offtopic,但相关:自PHP 7.1起,您现在可以轻松转换为Closure:`callFunc1(Closure :: fromCallable("xy"))`.https://wiki.php.net/rfc/closurefromcallable (17认同)
  • 您也可以通过传递一个数组来使用可调用的类方法,例如````''Foo","bar"]`用于`Foo :: bar`或`[$ foo,"bar"]`用于`$ foo-> bar `. (5认同)
  • 我仍然不明白为什么我只想调用匿名函数。如果我共享代码,我不应该关心函数来自哪里。我认为这是 PHP 的怪癖之一,他们应该删除一种或另一种方式以避免混淆。但老实说,我很喜欢 `Closure` + `Closure::fromCallable` 方法,因为字符串或数组作为 `callable` 一直很奇怪。 (2认同)
  • @RoboRobok只要求`Closure`(匿名函数)而不是`callable`的原因之一是防止访问超出被调用函数的范围。例如,当您拥有“私有方法”时,您不希望滥用“可调用”方法的人访问它。参见:https://3v4l.org/7TSf2 (2认同)
  • 对于任何尝试为“$closure”或“$callback”输入参数分配默认函数的人,您需要将“null”设置为默认值,并在函数本身内部检查“null”,然后分配默认值如果参数为“null”,则函数在那里。无法提供默认的“Closure”或“callable”。这是因为默认值只能是标量值(string、int、float、null)或数组。这是没有记录的,所以希望这可以帮助人们避免过度搜索。 (2认同)

Xor*_*lse 53

它们之间的主要区别在于,closure是一个阶级callable一个类型.

callable类型接受任何可以调用的东西:

var_dump(
  is_callable('functionName'),
  is_callable([$myClass, 'methodName']),
  is_callable(function(){})
);
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

那么,为什么使用closurecallable

严,因为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
Run Code Online (Sandbox Code Playgroud)

您不希望在正常函数上调用方法,因为这会引发致命错误.所以为了规避你必须写下这样的东西:

if($cb instanceof \Closure){}
Run Code Online (Sandbox Code Playgroud)

要做到这一点,每次检查都是毫无意义的.所以如果你想使用那些方法声明参数是a closure.否则只需使用正常callback.这条路; 函数调用而不是代码引发错误,导致它更容易诊断.

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

  • Upvoted指出了类与类型. (5认同)