¿Cómo implemento una devolución de llamada en PHP?

181

¿Cómo se escriben las devoluciones de llamada en PHP?

Nick Stinemates
fuente
1
Voy a vincular otra pregunta a esta, porque estaba tratando de llamar a un cierre.
akinuri

Respuestas:

173

El manual usa los términos "devolución de llamada" y "invocable" indistintamente, sin embargo, "devolución de llamada" se refiere tradicionalmente a una cadena o valor de matriz que actúa como un puntero de función , haciendo referencia a una función o método de clase para una futura invocación. Esto ha permitido algunos elementos de programación funcional desde PHP 4. Los sabores son:

$cb1 = 'someGlobalFunction';
$cb2 = ['ClassName', 'someStaticMethod'];
$cb3 = [$object, 'somePublicMethod'];

// this syntax is callable since PHP 5.2.3 but a string containing it
// cannot be called directly
$cb2 = 'ClassName::someStaticMethod';
$cb2(); // fatal error

// legacy syntax for PHP 4
$cb3 = array(&$object, 'somePublicMethod');

Esta es una forma segura de usar valores invocables en general:

if (is_callable($cb2)) {
    // Autoloading will be invoked to load the class "ClassName" if it's not
    // yet defined, and PHP will check that the class has a method
    // "someStaticMethod". Note that is_callable() will NOT verify that the
    // method can safely be executed in static context.

    $returnValue = call_user_func($cb2, $arg1, $arg2);
}

Las versiones modernas de PHP permiten que los primeros tres formatos anteriores se invoquen directamente como $cb(). call_user_funcy call_user_func_arrayapoyar todo lo anterior.

Ver: http://php.net/manual/en/language.types.callable.php

Notas / Advertencias:

  1. Si la función / clase tiene un espacio de nombres, la cadena debe contener el nombre completo. P.ej['Vendor\Package\Foo', 'method']
  2. call_user_funcno soporta que pasa no-objetos por referencia, por lo que puede utilizar cualquiera call_user_func_arrayo, en versiones posteriores de PHP, excepto la devolución de llamada a un var y utilizar la sintaxis directa: $cb();
  3. Los objetos con un __invoke()método (incluidas las funciones anónimas) se incluyen en la categoría "invocable" y se pueden usar de la misma manera, pero personalmente no los asocio con el término heredado de "devolución de llamada".
  4. El legado create_function()crea una función global y devuelve su nombre. Es un contenedor para eval()y las funciones anónimas deben usarse en su lugar.
Steve Clay
fuente
De hecho, usar la función es la forma correcta de hacerlo. Al usar una variable y luego simplemente llamarla, como se sugiere en la respuesta aceptada, es genial, es feo y no escalará bien con el código.
icco
44
Cambió la respuesta aceptada. De acuerdo con los comentarios, esta es una gran respuesta.
Nick Stinemates el
Sería útil para los lectores que vienen de la programación de Python señalar en la respuesta que de 'someGlobalFunction'hecho es una función definida.
TMOTTM
1
A partir de PHP 5.3, hay cierres, ver la respuesta de Bart van Heukelom . Es mucho más simple y "estándar" que todo este desorden heredado.
realmente agradable
Sí, mencioné funciones anónimas en mi respuesta, pero el OP solicitó "devolución de llamada" (en 2008) y estas devoluciones de llamada de estilo antiguo todavía se usan mucho en toneladas de bases de código PHP.
Steve Clay
74

Con PHP 5.3, ahora puede hacer esto:

function doIt($callback) { $callback(); }

doIt(function() {
    // this will be done
});

Finalmente una buena manera de hacerlo. Una gran adición a PHP, porque las devoluciones de llamada son impresionantes.

Bart van Heukelom
fuente
1
Esta debe ser la mejor respuesta.
GROVER.
30

La implementación de una devolución de llamada se realiza así

// This function uses a callback function. 
function doIt($callback) 
{ 
    $data = "this is my data";
    $callback($data); 
} 


// This is a sample callback function for doIt(). 
function myCallback($data) 
{ 
    print 'Data is: ' .  $data .  "\n"; 
} 


// Call doIt() and pass our sample callback function's name. 
doIt('myCallback');

Muestra: Los datos son: estos son mis datos

Nick Stinemates
fuente
21
Oh Dios mío. ¿Es ese el estándar? ¡Eso es terrible!
Nick Retallack
Hay algunas otras formas de hacerlo como se muestra arriba. Realmente pensé lo mismo.
Nick Stinemates
3
@ Nick Retallack, no veo qué es tan horrible al respecto. Para los lenguajes que conozco, como JavaScript y C #, todos pueden estructurar su función de devolución de llamada en dicho patrón. Viniendo de JavaScirpt y C #, realmente no estoy acostumbrado a call_user_func (). Me hace sentir que tengo que adaptarme a PHP, en lugar de al revés.
Antony
44
@Antony Me opuse al hecho de que las cadenas son punteros de función en este lenguaje. Publiqué ese comentario hace tres años, así que ya estoy bastante acostumbrado, pero creo que PHP es el único lenguaje que conozco (aparte de las secuencias de comandos de shell) donde este es el caso.
Nick Retallack
1
@Antony Prefiero usar la misma sintaxis que uso en javascript. Por lo tanto, ni siquiera entiendo por qué la gente quiere usar call_user_func()cuando tienen una sintaxis que les permite llamar dinámicamente funciones y hacer devoluciones de llamada. ¡Estoy de acuerdo contigo!
botenvouwer
9

Un ingenioso truco que he encontrado recientemente es usar PHP create_function()para crear una función anónima / lambda para un solo uso. Es útil para funciones PHP como array_map(), preg_replace_callback(), o usort()que el uso devoluciones de llamada para un tratamiento personalizado. Parece más o menos como lo hace eval()debajo de las cubiertas, pero sigue siendo una buena forma de estilo funcional para usar PHP.

yukondude
fuente
66
Desafortunadamente, el recolector de basura no juega muy bien con esta construcción produciendo posibles pérdidas de memoria. Si está fuera de rendimiento, evite create_function ().
fuxia
1
Ay. Gracias por el aviso.
yukondude
¿Le importaría actualizar la respuesta con la versión PHP 7.4 (funciones de flecha) y agregar una advertencia sobre obsoleto create_function()?
Dharman
7

Querrás verificar lo que sea que tu llamada sea válida. Por ejemplo, en el caso de una función específica, querrá verificar y ver si la función existe:

function doIt($callback) {
    if(function_exists($callback)) {
        $callback();
    } else {
        // some error handling
    }
}
SeanDowney
fuente
¿Qué sucede si la devolución de llamada no es una función, sino una matriz que contiene un objeto y un método?
d -_- b
3
o más bienis_callable( $callback )
Roko C. Buljan
1
Ambas son buenas sugerencias: será necesario verificar su propia implementación particular de cómo hacer una devolución de llamada. Tenía la esperanza de advertir contra llamar a algo que no existía causando un fatal. Hice la respuesta más general
SeanDowney,
5

create_functionno funcionó para mí dentro de una clase. Tuve que usar call_user_func.

<?php

class Dispatcher {
    //Added explicit callback declaration.
    var $callback;

    public function Dispatcher( $callback ){
         $this->callback = $callback;
    }

    public function asynchronous_method(){
       //do asynch stuff, like fwrite...then, fire callback.
       if ( isset( $this->callback ) ) {
            if (function_exists( $this->callback )) call_user_func( $this->callback, "File done!" );
        }
    }

}

Luego, para usar:

<?php 
include_once('Dispatcher.php');
$d = new Dispatcher( 'do_callback' );
$d->asynchronous_method();

function do_callback( $data ){
   print 'Data is: ' .  $data .  "\n";
}
?>

[Editar] Se agregó un paréntesis faltante. Además, agregó la declaración de devolución de llamada, lo prefiero de esa manera.

goliatone
fuente
1
¿La clase Dispatcher no requiere un atributo para que $ this-> callback = $ callback funcione?
James P.
@james poulson: PHP es un lenguaje dinámico, por lo que funciona. Pero estaba siendo flojo. Por lo general, declaro propiedades, facilita la vida de todos. Tu pregunta me hizo mirar ese código nuevamente y detectar un error de sintaxis, tú. Gracias
goliatone
No sabía que esto era posible. Gracias por la respuesta :) .
James P.
¿No sería más seguro declarar la función do_callback antes de crear el objeto Dispatcher y llamar al método asíncrono?
eyectamenta
3

Me estremezco cada vez que uso create_function()en php.

Los parámetros son una cadena separada por coma, todo el cuerpo de la función en una cadena ... Argh ... Creo que no podrían haberlo hecho más feo, incluso si lo intentaran.

Desafortunadamente, es la única opción cuando no vale la pena crear una función con nombre.

Palmadita
fuente
1
Y, por supuesto, es una evaluación de cadena en tiempo de ejecución, por lo que no se verifica la sintaxis válida ni nada en el momento de la compilación.
hobbs
Esta respuesta ha quedado desactualizada durante los últimos 2 años. create_function()ahora está en desuso y no debe usarse.
Dharman
2

Para aquellos a quienes no les importa romper la compatibilidad con PHP < 5.4, les sugiero que utilicen sugerencias de tipo para hacer una implementación más limpia.

function call_with_hello_and_append_world( callable $callback )
{
     // No need to check $closure because of the type hint
     return $callback( "hello" )."world";
}

function append_space( $string )
{
     return $string." ";
}

$output1 = call_with_hello_and_append_world( function( $string ) { return $string." "; } );
var_dump( $output1 ); // string(11) "hello world"

$output2 = call_with_hello_and_append_world( "append_space" );
var_dump( $output2 ); // string(11) "hello world"

$old_lambda = create_function( '$string', 'return $string." ";' );
$output3 = call_with_hello_and_append_world( $old_lambda );
var_dump( $output3 ); // string(11) "hello world"
Kendall Hopkins
fuente
La advertencia create_function() ha sido DEPRECADA a partir de PHP 7.2.0. Confiar en esta función es altamente desaconsejado.
Dharman