PHP: Podpowiedź typu - Różnica między `Closure` a` Callable`

128

Zauważyłem, że mogę użyć podpowiedzi typu Closurelub Callablejako podpowiedzi typu, jeśli spodziewaliśmy się uruchomienia funkcji wywołania zwrotnego. Na przykład:

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

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

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

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

Pytanie:

Jaka jest różnica? Innymi słowy, kiedy używać Closurei kiedy używać CallableLUB służą temu samemu celowi?

Dev01
źródło

Odpowiedzi:

173

Różnica polega na tym, że a Closuremusi być funkcją anonimową, gdzie callablerównież może być zwykłą funkcją.

Możesz zobaczyć / przetestować to na poniższym przykładzie, a zobaczysz, że otrzymasz błąd dla pierwszego:

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!

Więc jeśli chcesz tylko wpisać wskazówkę jako funkcję anonimową, użyj: Closurei jeśli chcesz również zezwolić normalnym funkcjom na użycie callablejako wskazówki dotyczącej typu.

Rizier123
źródło
5
Możesz także używać metod klasowych z możliwością wywołania przez przekazanie tablicy, np. ["Foo", "bar"]For Foo::barlub [$foo, "bar"]for $foo->bar.
Andrea
17
Offtopic, ale związane: od PHP 7.1, można teraz łatwo przekonwertować do zamknięcia: callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/closurefromcallable
nevvermind
Nadal nie rozumiem, dlaczego miałbym chcieć wywoływać tylko funkcję anonimową. Jeśli udostępnię kod, nie powinno mnie obchodzić, skąd pochodzi funkcja. Uważam, że jeden z dziwactw PHP, powinien usunąć ten lub inny sposób, aby uniknąć nieporozumień. Ale szczerze podoba mi się podejście Closure+ Closure::fromCallable, ponieważ łańcuch lub tablica, jak callablezawsze, były dziwne.
Robo Robok
2
@RoboRobok Jednym z powodów wymagania tylko Closure(funkcja anonimowa) w przeciwieństwie do callable, byłoby uniemożliwienie dostępu poza zakres wywoływanej funkcji. Na przykład, jeśli masz domenę, do private methodktórej nie chcesz mieć dostępu ktoś nadużywający domeny callable. Zobacz: 3v4l.org/7TSf2
fyrye
58

Główną różnicą między nimi jest to, że closurejest to klasa i typu .callable

callableTyp akceptuje wszystko, co można o nazwie :

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

Gdzie closurebędzie tylko zaakceptować anonimową funkcję. Zauważ, że w PHP wersji 7.1 można zamienić funkcje do zamknięcia tak: Closure::fromCallable('functionName').


Przykład:

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
}

Więc po co używać closureovera callable?

Surowość, ponieważ closurejest obiektem, który posiada kilka dodatkowych metod: call(), bind()i bindto(). Pozwalają na użycie funkcji zadeklarowanej poza klasą i wykonanie jej tak, jakby była wewnątrz klasy.

$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

Nie chciałbyś wywoływać metod w normalnej funkcji, ponieważ spowoduje to krytyczne błędy. Aby więc obejść to musiałbyś napisać coś takiego:

if($cb instanceof \Closure){}

Wykonywanie tego sprawdzania za każdym razem jest bezcelowe. Więc jeśli chcesz użyć tych metod, powiedz, że argumentem jest closure. W przeciwnym razie użyj zwykłego callback. Tą drogą; Podczas wywołania funkcji zamiast kodu wywoływany jest błąd, co znacznie ułatwia diagnozowanie.

Na marginesie:closure klasa nie może zostać przedłużony za jego ostateczny .

Xorifelse
źródło
1
Możesz ponownie użyć wywoływanego w innych zakresach.
Bimal Poudel
Oznacza to, że nie musisz kwalifikować się callablew żadnej przestrzeni nazw.
Jeff Puckett,
0

Warto wspomnieć, że to nie zadziała dla wersji PHP od 5.3.21 do 5.3.29.

W każdej z tych wersji otrzymasz takie dane wyjściowe:

Witaj świecie! Catchable fatal error: Argument 1 przekazany do callFunc2 () musi być instancją> Callable, podana instancja Closure, wywołana w / in / kqeYD w linii 16 i zdefiniowana w / in / kqeYD w linii 7

Proces zakończył się kodem 255.

Można to wypróbować, korzystając z https://3v4l.org/kqeYD#v5321

Z poważaniem,

Rod Elias
źródło
2
Zamiast publikować link do kodu, umieść kod tutaj, na wypadek gdyby ktoś inny napotkał ten problem i podany przez Ciebie link się zepsuje. Możesz również podać wyniki w swoim poście, aby pomóc.
Vedda
5
Dzieje się tak, ponieważ callablezostało wprowadzone w PHP 5.4. Przed tym PHP spodziewa instancję klasy o nazwie callable, tak jak gdybyś podano podpowiedź dla PDO, DateTimelub \My\Random\ClassName.
Davey Shafik