PHP: Indication de type - Différence entre `Closure` et` Callable`

128

J'ai remarqué que je peux utiliser l'un des Closure ou Callablecomme indice de type si nous nous attendions à ce qu'une fonction de rappel s'exécute. Par exemple:

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

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

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

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

Question:

Quelle est la différence ici? En d'autres termes, quand utiliser Closureet quand utiliserCallable OU servent-ils le même objectif?

Dev01
la source

Réponses:

173

La différence est que a Closuredoit être une fonction anonyme, où callablepeut également être une fonction normale.

Vous pouvez voir / tester cela avec l'exemple ci-dessous et vous verrez que vous obtiendrez une erreur pour le premier:

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!

Donc, si vous voulez seulement taper une fonction anonyme, utilisez: Closureet si vous voulez également autoriser les fonctions normales, utilisez callablecomme indice de type.

Rizier123
la source
5
Vous pouvez également utiliser des méthodes de classe avec callable en passant un tableau, par exemple ["Foo", "bar"]pour Foo::barou [$foo, "bar"]pour $foo->bar.
Andrea
17
Offtopic, mais liée: depuis PHP 7.1, vous pouvez facilement convertir à une fermeture: callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/closurefromcallable
nevvermind
Je ne vois toujours pas pourquoi je voudrais appeler la fonction anonyme uniquement. Si je partage le code, je ne devrais pas me soucier d'où vient la fonction. Je considère que l'une des bizarreries de PHP, ils devraient supprimer l'un ou l'autre moyen d'éviter toute confusion. Mais j'aime honnêtement l' approche Closure+ Closure::fromCallable, car la chaîne ou le tableau callablea toujours été bizarre.
Robo Robok
2
@RoboRobok une raison pour exiger uniquement Closure(fonction anonyme) par opposition à callable, serait d'empêcher l'accès au-delà de la portée de la fonction appelée. Par exemple, lorsque vous avez un, private methodvous ne voulez pas que quelqu'un abuse d'un callable. Voir: 3v4l.org/7TSf2
fyrye
58

La principale différence entre eux est qu'un closure est une classe et callableun type .

Le callabletype accepte tout ce qui peut être appelé :

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

Lorsqu'un closuresera seulement accepter une fonction anonyme. Notez que dans la version PHP 7.1 , vous pouvez convertir des fonctions à une fermeture comme ceci: Closure::fromCallable('functionName').


Exemple:

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
}

Alors pourquoi utiliser un closureovercallable ?

Sévérité parce qu'un closureest un objet qui a des méthodes supplémentaires: call(), bind()etbindto() . Ils vous permettent d'utiliser une fonction déclarée en dehors d'une classe et de l'exécuter comme si elle était à l'intérieur d'une classe.

$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

Vous n'aimeriez pas appeler des méthodes sur une fonction normale car cela provoquera des erreurs fatales. Donc, pour éviter cela, vous devriez écrire quelque chose comme:

if($cb instanceof \Closure){}

Faire ce contrôle à chaque fois est inutile. Donc, si vous souhaitez utiliser ces méthodes, indiquez que l'argument est unclosure . Sinon, utilisez simplement un fichier normal callback. Par ici; Une erreur est générée lors de l'appel de fonction au lieu de votre code, ce qui facilite grandement le diagnostic.

Sur une note latérale: la closureclasse ne peut pas être étendue comme sa finale .

Xorifelse
la source
1
Vous pouvez également réutiliser un appelable dans d'autres étendues.
Bimal Poudel
Cela signifie que vous ne devez vous qualifier callabledans aucun espace de noms.
Jeff Puckett
0

Il est à noter que cela ne fonctionnera pas pour les versions PHP 5.3.21 à 5.3.29.

Dans n'importe laquelle de ces versions, vous obtiendrez une sortie comme:

Bonjour le monde! Erreur fatale capturable: l'argument 1 passé à callFunc2 () doit être une instance de> Callable, instance de Closure donnée, appelée dans / in / kqeYD à la ligne 16 et définie dans / in / kqeYD à la ligne 7

Processus terminé avec le code 255.

On peut l'essayer en utilisant https://3v4l.org/kqeYD#v5321

Meilleures salutations,

Rod Elias
la source
2
Au lieu de publier un lien vers le code, vous devriez publier le code ici au cas où quelqu'un d'autre rencontrerait ce problème et le lien que vous avez fourni se rompre. Vous pouvez également fournir la sortie dans votre message pour vous aider.
Vedda
5
En effet, il a callableété introduit dans PHP 5.4. Avant que PHP attend une instance d'une classe nommée callable, comme si vous aviez spécifié un indice pour PDO, DateTimeou \My\Random\ClassName.
Davey Shafik