PHP: Suggerimento di tipo - Differenza tra `Closing` e` Callable`


128

Ho notato che posso usare uno dei due Closureo Callablecome suggerimento per il tipo se ci aspettavamo l'esecuzione di una funzione di callback. Per esempio:

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

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

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

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

Domanda:

Qual è la differenza qui? In altre parole, quando usare Closuree quando usare CallableO servono allo stesso scopo?

Risposte:


173

La differenza è che a Closuredeve essere una funzione anonima, dove callablepuò anche essere una funzione normale.

Puoi vedere / provare questo con l'esempio qui sotto e vedrai che otterrai un errore per il primo:

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!

Quindi, se vuoi solo digitare un suggerimento, usa la funzione anonima: Closuree se vuoi consentire anche le normali funzioni, usa callablecome un suggerimento sul tipo.


5
È inoltre possibile utilizzare i metodi di classe con richiamabile passando un array, ad esempio ["Foo", "bar"]per Foo::baro [$foo, "bar"]per $foo->bar.
Andrea,

17
Offtopic, ma legato: da PHP 7.1, è ora possibile convertire facilmente ad una chiusura: callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/closurefromcallable
nevvermind

Non vedo ancora perché dovrei voler chiamare solo la funzione anonima. Se condivido il codice, non mi dovrebbe importare da dove proviene la funzione. Ritengo che una delle stranezze di PHP, dovrebbero rimuovere l'uno o l'altro modo per evitare confusione. Ma onestamente mi piace l' approccio Closure+ Closure::fromCallable, perché string o array come callableè sempre stato strano.
Robo Robok,

2
@RoboRobok un motivo per richiedere solo Closure(funzione anonima) invece callabledi impedire l'accesso al di là dell'ambito della funzione chiamata. Ad esempio quando hai un private methodnon vuoi che qualcuno acceda a un callable. Vedi: 3v4l.org/7TSf2
fyrye,

58

La principale differenza tra loro è che a closureè una classe e callableun tipo .

Il callabletipo accetta tutto ciò che può essere chiamato :

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

Quando una closuresarà solo accettare una funzione anonima. Si noti che in PHP versione 7.1 è possibile convertire le funzioni ad una chiusura in questo modo: Closure::fromCallable('functionName').


Esempio:

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
}

Quindi perché usare un closureover callable?

Severità perché closureè un oggetto che ha alcuni metodi aggiuntivi: call(), bind()e bindto(). Consentono di utilizzare una funzione dichiarata al di fuori di una classe ed eseguirla come se fosse all'interno di una 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

Non si desidera chiamare i metodi su una funzione normale in quanto ciò causerà errori fatali. Quindi, per evitare che dovresti scrivere qualcosa del tipo:

if($cb instanceof \Closure){}

Fare questo controllo ogni volta è inutile. Quindi, se si desidera utilizzare tali metodi, dichiarare che l'argomento è a closure. Altrimenti basta usare un normale callback. Per di qua; Viene generato un errore nella chiamata di funzione anziché nel codice, causando una diagnosi molto più semplice.

Nota a margine: la closureclasse non può essere estesa come finale .


1
Puoi riutilizzare un callable anche in altri ambiti.
Bimal Poudel,

Questo significa che non devi qualificarti callablein nessuno spazio dei nomi.
Jeff Puckett,

0

Vale la pena ricordare che questo non funzionerà per le versioni da PHP 5.3.21 a 5.3.29.

In una di quelle versioni otterrai un output come:

Ciao mondo! Errore irreversibile irreversibile: l'argomento 1 passato a callFunc2 () deve essere un'istanza di> Callable, istanza di chiusura fornita, chiamata in / in / kqeYD sulla riga 16 e definita in / in / kqeYD sulla riga 7

Processo terminato con il codice 255.

Puoi provarlo usando https://3v4l.org/kqeYD#v5321

I migliori saluti,


2
Invece di pubblicare un link al codice, dovresti pubblicare il codice qui nel caso in cui qualcun altro incontri questo problema e il link che hai fornito si rompe. Puoi anche fornire l'output nel tuo post per aiutarti.
Vedda,

5
Questo perché è callablestato introdotto in PHP 5.4. Prima che PHP si aspetta un'istanza di una classe denominata callable, proprio come se avessi specificato un suggerimento per PDO, DateTimeo \My\Random\ClassName.
Davey Shafik,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.