PHP: Sugerencia de tipo - Diferencia entre `Closure` y` Callable`

128

Noté que puedo usar cualquiera Closureo Callablecomo sugerencia de tipo si esperábamos que se ejecutara alguna función de devolución de llamada. Por ejemplo:

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

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

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

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

Pregunta:

¿Cuál es la diferencia aquí? En otras palabras, ¿cuándo usar Closurey cuándo usar CallableO sirven para el mismo propósito?

Dev01
fuente

Respuestas:

173

La diferencia es que Closuredebe ser una función anónima, donde callabletambién puede ser una función normal.

Puede ver / probar esto con el siguiente ejemplo y verá que obtendrá un error para el primero:

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!

Entonces, si solo desea escribir sugerencia, utilice la función anónima: Closurey si también desea permitir que las funciones normales se usen callablecomo sugerencia de tipo.

Rizier123
fuente
55
También puede usar métodos de clase con invocables pasando una matriz, por ejemplo, ["Foo", "bar"]para Foo::baro [$foo, "bar"]para $foo->bar.
Andrea
17
Offtopic, pero relacionado: a partir de PHP 7.1, ahora se puede convertir fácilmente a un cierre: callFunc1(Closure::fromCallable("xy")). wiki.php.net/rfc/closurefromcallable
nevvermind
Todavía no veo por qué querría llamar solo a la función anónima. Si comparto el código, no debería importarme de dónde viene la función. Considero que una de las peculiaridades de PHP, deberían eliminar una u otra forma para evitar confusiones. Pero, sinceramente, me gusta el enfoque Closure+ Closure::fromCallable, porque la cadena o matriz como callablesiempre ha sido extraña.
Robo Robok el
2
@RoboRobok una razón para requerir solo Closure(función anónima) en lugar de callable, sería evitar el acceso más allá del alcance de la función llamada. Por ejemplo, cuando tiene un private methodno desea que alguien que acceda a él acceda a él callable. Ver: 3v4l.org/7TSf2
fyrye
58

La principal diferencia entre ellos es que a closurees una clase y callableun tipo .

El callabletipo acepta cualquier cosa que se pueda llamar :

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

Cuando una closurese sólo se aceptará una función anónima. Tenga en cuenta que en PHP versión 7.1 puede convertir funciones a un cierre de este modo: Closure::fromCallable('functionName').


Ejemplo:

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
}

Entonces, ¿por qué usar un closureover callable?

Rigor porque un closurees un objeto que tiene algunos métodos adicionales: call(), bind()y bindto(). Le permiten usar una función declarada fuera de una clase y ejecutarla como si estuviera dentro de una clase.

$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

No le gustaría llamar a los métodos en una función normal, ya que generará errores fatales. Entonces, para evitar eso, tendrías que escribir algo como:

if($cb instanceof \Closure){}

Hacer esto comprobar cada vez no tiene sentido. Entonces, si desea utilizar esos métodos, indique que el argumento es a closure. De lo contrario, solo use un normal callback. De esta manera; Se genera un error en la llamada de función en lugar de su código, lo que hace que sea mucho más fácil de diagnosticar.

En una nota al margen: La closureclase no se puede extender como su final .

Xorifelse
fuente
1
También puede reutilizar un invocable en otros ámbitos.
Bimal Poudel
Esto significa que no tiene que calificar callableen ningún espacio de nombres.
Jeff Puckett
0

Vale la pena mencionar que esto no funcionará para las versiones de PHP 5.3.21 a 5.3.29.

En cualquiera de esas versiones obtendrá un resultado como:

¡Hola Mundo! Error fatal detectable: el argumento 1 pasado a callFunc2 () debe ser una instancia de> invocable, instancia de cierre dada, llamada in / in / kqeYD en la línea 16 y definida en / in / kqeYD en la línea 7

Proceso salido con el código 255.

Uno puede probar eso usando https://3v4l.org/kqeYD#v5321

Atentamente,

Rod Elias
fuente
2
En lugar de publicar un enlace al código, debe publicar el código aquí en caso de que alguien más se encuentre con este problema y el enlace que ha proporcionado se rompa. También puede proporcionar el resultado en su publicación para ayudar.
Vedda
55
Esto se debe a que callablese introdujo en PHP 5.4. Antes de que PHP está esperando una instancia de una clase llamada callable, como si usted había especificado una sugerencia para PDO, DateTimeo \My\Random\ClassName.
Davey Shafik