sábado, 17 de agosto de 2013

Funciones anónimas en PHP (closures)

En 5.3 aparece un nuevo tipo de concepto: la función anónima o cláusula (closure). Conocida de los programadores de javascript. Una cláusula  es una función que se ejecuta en su propio contexto. Lo que significa que el contexto en el que la función es definido permanecerá hasta el cierre de la misma. Con lo que podemos declarar una función sin nombre y la podremos almacenar en una variable. Y para llamar a la cláusula llamaremos a la variable seguida de los paréntesis de apertura y cierra.
La sintaxis para definir un 'closure' es muy simple:

 function(){  
   //implementacion  
 }  

Hay que tener en cuenta 3 cuestiones de las funciones anónimas y las variables:

1. Las variables definidas en un contexto superior no pueden ser accedidas dentro de las funciones anónimas a no ser que hayan sido definidas como globales. Pero podemos importarlas para poder utilizarlas dentro de una función anónima. Para ello se utiliza la palabra reservada use y entre paréntesis el nombre de las variables.

 $cadena = "Esto es un ejemplo!";  
 $clausula = function() use ($cadena) {   
   echo $cadena;   
 }; 
 $clausula(); //Esto es un ejemplo!

2. Las funciones anónimas aceptan parámetros al igual que las funciones normales.

 $cadena = "Esto es un ejemplo!";  
 $clausula = function($cadena) {  
       echo $cadena;  
     };   
 $clausula($cadena);  //Esto es un ejemplo!

3. Las variables dentro de una cláusula  no están vinculados necesariamente a copias de las variables en el ámbito padre, pero se pueden vincular a las propias variables. Al igual que una función normal, los parámetros de una cláusula, por defecto, serán pasados por 'valor'. Lo que significa que si dentro de la cláusula actualizamos el valor del parámetro fuera de ella no se actualizará. Evidentemente podremos solucionar esto pasando el valor por referencia. Para ello antepondremos el carácter & al parámetro.

 $cadena = "Esto es un ejemplo!";  
 $clausula = function(&$cadena) {  
       $cadena .= " - modificado!!";  
     };  
 $clausula2 = function() use (&$cadena) {  
       $cadena .= " - modificado 2!!";  
     };  
 $clausula($cadena);  
 echo $cadena . "<br/>";// ESto es un ejemplo! - modificado!!  
 $clausula2();  
 echo $cadena;// Esto es un ejemplo! - modificado - modificado2!!  

En los ejemplos que hemos visto hasta ahora, las funciones se han almacenado en variables. Este es un uso habitual, sin embargo también podemos prescindir de una variable y utilizar la función anónima directamente.
Vamos a ver un ejemplo de utilizar una función anónima en el retorno de otra función:

 function anyadePrefijo($cadena) {  
   return function($prefijo) use ($cadena) {  
         echo $prefijo . $cadena;  
       };  
 }  
 $cad = "una prueba";  
 $c = anyadePrefijo($cad);  
 echo $c("Esto es ");  

Y otro uso muy común es utilizar una función anónima como callback. En el siguiente ejemplo vamos a utilizar una función anónima como callback para multiplicar los elementos de la misma posición de cada uno de los arrays.

 $multiplicador = function($a, $b) {  
   return $a * $b;  
 };  
 $numeros = range(1, 10);  
 $numeros2 = range(1, 10);  
 $lista = array_map($multiplicador, $numeros,$numeros2);  
 echo implode(" ", $lista);  


Funciones anónimas y objetos

Las cláusulas no son solo útiles en la programación procedimental, también es posible sacar provecho de ellas en la programación orientada a objetos.
En PHP 5.3, cuando aparecieron las cláusulas, no se tenía acceso a $this desde el interior de una de estas funciones. Por lo que se tenía que asignar $this a una variable temporal que sería la que se pasaría a la función anónima.

 class Animal {  
   private $_nombre;  
   protected $_tipo;  
   public function __construct($nombre, $tipo) {  
     $this->_nombre = $nombre;  
     $this->_tipo = $tipo;  
   }  
   public function saludo($cadena) {  
     $self = $this;  
     return function() use ($cadena, $self) {  
           return "$cadena, soy un {$self->_tipo} llamado {$self->_nombre}.";  
         };  
   }  
 }  
 $a = new Animal("Jacky", "Perro");  
 $saludo = $a->saludo("Hola");  
 echo $saludo();  

En PHP 5.4 ya se permitía acceder a $this dentro de una función anónima.

 class Animal {  
   private $_nombre;  
   protected $_tipo;  
   public function __construct($nombre, $tipo) {  
     $this->_nombre = $nombre;  
     $this->_tipo = $tipo;  
   }  
   public function saludo($cadena) {  
     return function() use ($cadena) {  
           return "$cadena, soy un {$this->_tipo} llamado {$this->_nombre}.";  
         };  
   }  
 }  
 $a = new Animal("Jacky", "Perro");  
 $saludo = $a->saludo("Hola");  
 echo $saludo();  

Esto se debe a que la variable $this es importada automáticamente. Este hecho ocupa un poco más de memoria en el servidor. Por lo que podemos desactivar la importación automática declarando la función anónima como estática.

 return static function() use ...{   
    //...  
 };   

Para finalizar vamos a ver un ejemplo con clases, funciones anónimas y las diferencias de pasar parámetros por referencia o por valor.

 class Test {  
   public $multiplicador;  
   public function __construct($multiplicador) {  
     $this->multiplicador = $multiplicador;  
   }  
   public function getClausula() {  
     $mul = &$this->multiplicador;  
     return function( $numero ) use( &$mul ) {  
           echo $mul . "<br/>";  
           return $mul * $numero;  
         };  
   }  
 }  
 $test = new Test(5);  
 $m = $test->getClausula();  
 echo $m(8) . "<br/>"; // 40  
 $test->multiplicador = 3;  
 echo $m(8) . "<br/>"; // 24   

La diferencia de usar los dos símbolos de referencia ('ampersand') a no usarlos es que mediante ellos la variable $mul que está dentro de la cláusula no es una copia. Si quitásemos algún 'ampersand' y por lo tanto la variable $mul de dentro de  la cláusula se convirtiera en una copia, el resultado sería que en ambas impresiones obtendríamos el mismo resultado: 40.

Entradas relacionadas

PHP orientado a objetos - Métodos mágicos - Parte 2

No hay comentarios:

Publicar un comentario