PHP: Type hinting - Diferença entre `Closure` e` Callable`

128

Percebi que posso usar um Closureou outro Callabletipo de dica se esperávamos que alguma função de retorno de chamada fosse executada. Por exemplo:

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

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

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

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

Questão:

Qual a diferença aqui? Em outras palavras, quando usar Closuree quando usar CallableOU eles servem ao mesmo propósito?

Dev01
fonte

Respostas:

173

A diferença é que a Closuredeve ser uma função anônima, onde callabletambém pode ser uma função normal.

Você pode ver / testar isso com o exemplo abaixo e verá que receberá um erro no primeiro:

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!

Portanto, se você deseja apenas digitar dica, use a função anônima: Closuree se deseja também permitir funções normais, use callablecomo dica.

Rizier123
fonte
5
Você também pode usar métodos de classe com callable passando uma matriz, por exemplo, ["Foo", "bar"]para Foo::barou [$foo, "bar"]para $foo->bar.
Andrea
17
Offtopic, mas relacionados: desde o PHP 7.1, agora você pode facilmente converter para um Encerramento: callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/closurefromcallable
nevvermind
Ainda não vejo por que gostaria de chamar apenas a função anônima. Se eu compartilhar o código, não me importo de onde vem a função. Considero que uma das peculiaridades do PHP deve remover uma ou outra maneira de evitar confusão. Mas sinceramente gosto da abordagem Closure+ Closure::fromCallable, porque string ou array callablesempre foi estranho.
Robo Robok
2
O @RoboRobok um motivo para exigir apenas Closure(função anônima), ao contrário callable, seria impedir o acesso além do escopo da função chamada. Por exemplo, quando você tem um private method, não deseja acessar por alguém que esteja abusando de um callable. Veja: 3v4l.org/7TSf2
fyrye
58

A principal diferença entre eles é que a closureé uma classe e callableum tipo .

O callabletipo aceita qualquer coisa que possa ser chamada :

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

Quando um closurevai única aceitar uma função anônima. Note que no PHP versão 7.1 você pode converter funções para um fecho assim: Closure::fromCallable('functionName').


Exemplo:

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
}

Então, por que usar um closureover callable?

Rigor porque um closureé um objecto que tem alguns métodos adicionais: call(), bind()e bindto(). Eles permitem que você use uma função declarada fora de uma classe e execute-a como se estivesse dentro de uma 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

Você não gostaria de chamar métodos com uma função normal, pois isso gerará erros fatais. Portanto, para contornar você teria que escrever algo como:

if($cb instanceof \Closure){}

Para fazer isso, verifique sempre que não faz sentido. Portanto, se você deseja usar esses métodos, declare que o argumento é a closure. Caso contrário, basta usar um normal callback. Deste jeito; Um erro é gerado na chamada de função em vez do seu código, tornando-o muito mais fácil de diagnosticar.

Em uma nota lateral: A closureturma não pode ser estendida como final .

Xorifelse
fonte
1
Você também pode reutilizar uma chamada em outros escopos.
Bimal Poudel 06/0318
Isso significa que você não precisa se qualificar callableem nenhum espaço para nome.
21818 Jeff Puckett
0

Vale ressaltar que isso não funcionará nas versões 5.3.21 a 5.3.29 do PHP.

Em qualquer uma dessas versões, você obterá uma saída como:

Olá Mundo! Erro fatal detectável: o argumento 1 passado para callFunc2 () deve ser uma instância de> Callable, instância de Closure fornecida, chamada em / in / kqeYD na linha 16 e definida em / in / kqeYD na linha 7

Processo encerrado com o código 255.

Pode-se tentar isso usando https://3v4l.org/kqeYD#v5321

Cumprimentos,

Rod Elias
fonte
2
Em vez de postar um link para o código, você deve postar o código aqui caso outra pessoa encontre esse problema e o link que você forneceu seja interrompido. Você também pode fornecer a saída em sua postagem para ajudar.
Vedda
5
Isso ocorre porque callablefoi introduzido no PHP 5.4. Antes que o PHP está esperando uma instância de uma classe chamada callable, como se você tivesse especificado uma dica para PDO, DateTimeou \My\Random\ClassName.
Davey Shafik