PHP: Invocar funciones anónimas desde una variable

Posted by victor on March 12th, 2013
php-logoLas funciones anónimas o closures son funciones que permite crear callback,  es decir, funciones que se pasa como argumento a otra y se ejecutan dentro de esta última. Se denominan funciones anónimas porque no tienen un nombre por el que hacerles referencia salvo, claro está, que se asignen a un variable.
echo preg_replace_callback('/hola/', function ($coincidencia) {
    return strtoupper($coincidencia[0]);
}, 'hola-mundo');

// Como resultado se obtiene HOLA-mundo
En el ejemplo anterior, a la función preg_replace_callback() se le pasa como segundo argumento una función anónima que es invocada tras procesar la expresión regular y a la que como argumento le llegan un array con todas las coincidencias. Este tipo de funciones están disponibles desde PHP >= 5.3.0. Para crear funciones y pasarlas como argumento en versiones de PHP >= 4.0.1, se debe emplear create_function():
echo preg_replace_callback('/hola/', create_function ('$coincidencia', 
    'return strtoupper($coincidencia[0]);'), 'hola-mundo');

// Como resultado se obtiene HOLA-mundo
Las funciones anónimas nos permite generar un código más limpio y ordenado aunque todo esto, al final, depende del programador por lo que conviene usar cada herramienta en el momento adecuado.

Invocar funciones anónimas desde una variable de clase

Si ejecutamos el siguiente código obtendremos un error: Fatal error:  Call to undefined method MyClass::lambda():
class MiClase {
  var $miFuncion;
  function __construct() {
    $this->miFuncion = function() {echo 'Yo! Symfony';}; // Declaración correcta
  }
}

$obj = new MiClase();
$obj->miFuncion();
La declaración de la función anónima es correcta, el problema viene en la ejecución debido a que los métodos y las propiedades de una clase se encuentran en espacios distintos lo que permite tener nombres de propiedades y métodos iguales sin que surja ningún problema:
$obj->miFuncion; // Se accede como propiedad
$obj->miFuncion(); // Se accede como función
Disponemos de varias alternativas para invocar la función anónima asignada a una propiedad de clase:

Asignar nuestra función a una variable local

class MiClase {
  public $miFuncion;
  function __construct() {
    $this->miFuncion = function() {echo 'Yo! Symfony';}; // Declaración correcta
  }
}

$obj = new MiClase();
$miFuncion = $obj->miFuncion;
$miFuncion();

Método mágico __invoke():

El método __invoke() es un magic method de PHP que se emplea al llamar a métodos como funciones. Las funciones anónimas son instancias de Closure y este implementa el método __invoke() por lo que podemos usarlo directamente:
class MiClase {
  public $miFuncion;
  function __construct() {
    $this->miFuncion = function() {echo 'Yo! Symfony';}; // Declaración correcta
  }
}

$obj = new MiClase();
$obj->miFuncion->__invoke();

Mediante call_user_func()

El método call_user_func() está disponible desde PHP >= 4.0 y permite invocar la función pasada como argumento:
class MiClase {
  public $miFuncion;
  function __construct() {
    $this->miFuncion = function() {echo 'Yo! Symfony';}; // Declaración correcta
  }
}

$obj = new MiClase();
call_user_func($obj->miFuncion);
Si añadimos un segundo parámetro a call_user_func se pasará como argumento a miFuncion aunque en este caso no es necesario. Emplearemos call_user_func_array() en lugar de la anterior si deseamos pasar más de un parámetro como argumento. Ten en cuenta que siempre se pasan por referencia.

Mediante la método mágico __call()

Mediante el siguiente patrón logramos poder llamar directamente a nuestra función anónima:
class MiClase {
  private $miFuncion;
  function __construct() {
    $this->miFuncion = function() {echo 'Yo! Symfony';}; // Declaración correcta
  }

  public function __call($nombre, $argumentos)
  {
    return call_user_func_array($this->$nombre, $argumentos);
  }
}

$obj = new MiClase();
$obj->miFuncion(); // Ahora si podemos llamar directamente

Mediante Traits

Los Traits, disponibles desde PHP >= 5.4, nos van a permitir encapsular el  __call() anterior y reutilizarlo en cualquier clase:
trait FuncionAnonima
{
  public function __call($nombre, $argumentos)
  {
    return call_user_func_array($this->$nombre, $argumentos);
  }
}

class MiClase {
  use FuncionAnonima;
  private $miFuncion;
  function __construct() {
    $this->miFuncion = function() {echo 'Yo! Symfony';}; // Declaración correcta
  }
}

$obj = new MiClase();
$obj->miFuncion(); // Ahora si podemos llamar directamente
  PHP: Uso del operador $this en closures (PHP >= 5.4.0).

Comments

comments powered by Disqus