PHP: Tipphinweis - Unterschied zwischen "Closure" und "Callable"

128

Ich habe festgestellt, dass ich entweder einen Closureoder einen CallableTyphinweis verwenden kann, wenn wir erwartet haben, dass eine Rückruffunktion ausgeführt wird. Beispielsweise:

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

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

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

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

Frage:

Was ist der Unterschied hier? Mit anderen Worten, wann Closureund wann zu verwenden CallableODER sie dienen dem gleichen Zweck?

Dev01
quelle

Antworten:

173

Der Unterschied ist, dass a Closureeine anonyme Funktion sein muss, wobei callableauch eine normale Funktion sein kann.

Sie können dies anhand des folgenden Beispiels sehen / testen und Sie werden sehen, dass Sie beim ersten einen Fehler erhalten:

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!

Wenn Sie also nur eine anonyme Hinweisfunktion eingeben möchten, verwenden Sie: Closureund wenn Sie auch normale Funktionen callableals Typhinweis zulassen möchten .

Rizier123
quelle
5
Sie können auch Klassenmethoden mit aufrufbar verwenden, indem Sie ein Array übergeben, z . B. ["Foo", "bar"]für Foo::baroder [$foo, "bar"]für $foo->bar.
Andrea
17
Offtopic, aber verwandt: Seit PHP 7.1 können Sie jetzt problemlos in ein Closure konvertieren : callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/closurefromcallable
nevvermind
Ich verstehe immer noch nicht, warum ich nur anonyme Funktionen aufrufen möchte. Wenn ich den Code teile, ist es mir egal, woher die Funktion kommt. Ich bin der Meinung, dass eine der Macken von PHP die eine oder andere Methode entfernen sollte, um Verwirrung zu vermeiden. Aber ich mag den Closure+ Closure::fromCallable-Ansatz ehrlich , weil String oder Array wie callableimmer komisch waren.
Robo Robok
2
@RoboRobok Ein Grund dafür, nur Closure(anonyme Funktion) zu verlangen callable, wäre, den Zugriff über den Umfang der aufgerufenen Funktion hinaus zu verhindern. Wenn Sie beispielsweise eine private methodhaben, möchten Sie nicht, dass jemand auf eine Person zugreift, die eine missbraucht callable. Siehe: 3v4l.org/7TSf2
fyrye
58

Der Hauptunterschied zwischen ihnen besteht darin, dass a closureeine Klasse und callableein Typ ist .

Der callableTyp akzeptiert alles, was aufgerufen werden kann :

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

Wo eine closurewird nur eine anonyme Funktion übernehmen. Beachten Sie, dass Sie in PHP Version 7.1 Funktionen wie folgt in einen Abschluss konvertieren können : Closure::fromCallable('functionName').


Beispiel:

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
}

Warum also ein closureOver verwenden callable?

Strikt weil eine closureist ein Objekt , das einige zusätzliche Methoden hat: call(), bind()und bindto(). Mit ihnen können Sie eine außerhalb einer Klasse deklarierte Funktion verwenden und so ausführen, als ob sie sich innerhalb einer Klasse befindet.

$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

Sie möchten keine Methoden für eine normale Funktion aufrufen, da dies schwerwiegende Fehler verursacht. Um dies zu umgehen, müsste man so etwas schreiben wie:

if($cb instanceof \Closure){}

Dies jedes Mal zu überprüfen ist sinnlos. Wenn Sie diese Methoden verwenden möchten, geben Sie an, dass das Argument a ist closure. Ansonsten einfach einen normalen verwenden callback. Diesen Weg; Beim Funktionsaufruf wird anstelle Ihres Codes ein Fehler ausgegeben, der die Diagnose erheblich erleichtert.

Nebenbei bemerkt: Die closureKlasse kann nicht als endgültig erweitert werden .

Xorifelse
quelle
1
Sie können einen Callable auch in anderen Bereichen wiederverwenden.
Bimal Poudel
Dies bedeutet, dass Sie sich callablein keinem Namespace qualifizieren müssen.
Jeff Puckett
0

Es ist erwähnenswert, dass dies für die PHP-Versionen 5.3.21 bis 5.3.29 nicht funktioniert.

In jeder dieser Versionen erhalten Sie eine Ausgabe wie:

Hallo Welt! Auffangbarer schwerwiegender Fehler: Argument 1, das an callFunc2 () übergeben wird, muss eine Instanz von> Callable sein, eine Instanz von Closure, die in Zeile 16 in / in / kqeYD aufgerufen und in Zeile 7 in / in / kqeYD definiert ist

Prozess mit Code 255 beendet.

Man kann das mit https://3v4l.org/kqeYD#v5321 ausprobieren

Freundliche Grüße,

Rod Elias
quelle
2
Anstatt einen Link zum Code zu veröffentlichen, sollten Sie den Code hier veröffentlichen, falls jemand anderes auf dieses Problem stößt und der von Ihnen bereitgestellte Link unterbrochen wird. Sie können auch die Ausgabe in Ihrem Beitrag bereitstellen, um zu helfen.
Vedda
5
Dies liegt daran callable, dass in PHP 5.4 eingeführt wurde. Zuvor erwartet PHP eine Instanz einer Klasse mit dem Namencallable , so als ob Sie einen Hinweis für angegeben haben PDO, DateTimeoder \My\Random\ClassName.
Davey Shafik