PHP: подсказка типов - разница между Closure и Callable


128

Я заметил , что я могу использовать любой из Closureили в Callableкачестве типа намек , если мы ожидали какую - то функцию обратного вызова для запуска. Например:

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

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

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

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

Вопрос:

Какая здесь разница? Другими словами, когда использовать, Closureа когда использовать CallableИЛИ, они служат одной и той же цели?

Ответы:


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
Вы также можете использовать методы класса с callable, передавая массив, например ["Foo", "bar"]for Foo::barили [$foo, "bar"]for $foo->bar.
Андреа

17
Оффтоп, но связанные: с PHP 7.1, теперь вы можете легко конвертировать в Closure: 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

59

Основное различие между ними состоит в том , что closureэто класс и типа .callable

callableТип принимает все , что может быть под названием :

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

В случае , если 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
}

Так зачем использовать closureовер 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.

В любой из этих версий вы получите следующий результат:

Привет мир! Уловимая фатальная ошибка: аргумент 1, переданный в callFunc2 (), должен быть экземпляром> Callable, заданным экземпляром Closure, вызванным в / in / kqeYD в строке 16 и определенным в / in / kqeYD в строке 7

Процесс завершился с кодом 255.

Это можно попробовать, используя https://3v4l.org/kqeYD#v5321.

С уважением,


2
Вместо того, чтобы размещать ссылку на код, вы должны разместить здесь код на тот случай, если кто-то еще столкнется с этой проблемой и предоставленная вами ссылка прервется. Вы также можете предоставить результат в своем сообщении, чтобы помочь.
Vedda

5
Это потому, что callableбыло введено в PHP 5.4. До этого PHP ожидает экземпляр класса с именем callable, так же , как если бы вы указали намек на PDO, DateTimeили \My\Random\ClassName.
Дэйви Шафик,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.