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?

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.


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 .


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,


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.