PHP: Тип підказки - Різниця між "закриттям" та "дзвінким"


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 Closuremust має бути анонімною функцією, де 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"]для Foo::barабо [$foo, "bar"]для $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

58

Основна відмінність між ними полягає в тому , що 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){}

Робити цю перевірку кожен раз безглуздо. Тож якщо ви хочете скористатися цими методами, зазначайте, що аргументом є a closure. В іншому випадку просто використовуйте звичайне callback. Сюди; Під час виклику функції виникає помилка замість коду, що робить його набагато простіше діагностувати.

На стороні записки:closure клас не може бути продовжений , як його остаточним .


1
Ви також можете повторно використовувати дзвінки в інших областях.
Бімаль Пудель

Це означає, що вам не потрібно брати участь callableу будь-якому просторі імен.
Джефф Пукетт

0

Варто зазначити, що це не буде працювати для PHP версій 5.3.21 до 5.3.29.

У будь-якій з цих версій ви отримаєте такий результат:

Привіт Світ! Фатальна помилка, що підлягає відловленню: Аргумент 1, переданий callFunc2 (), повинен бути екземпляром> Callable, екземпляр закриття, наданий в / в / kqeYD у рядку 16 та визначений в / в / kqeYD у рядку 7

Процес, який вийшов із кодом 255.

Можна спробувати це за допомогою https://3v4l.org/kqeYD#v5321

З найкращими побажаннями,


2
Замість того, щоб публікувати посилання на код, слід опублікувати код тут, якщо хтось інший зіткнеться з цією проблемою, а посилання, яке ви надали, перерветься. Ви також можете надати результат у своїй посаді, щоб допомогти.
Веда

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