Hace poco descubrí que PHP5 soporta las closures.

Para quien no lo sepa, una closure consiste en una función que es evaluada en un entorno conteniendo una o más variables dependientes de otro entorno. Cuando es llamada, la función puede acceder a estas variables. Es una explicación un poco liosa que veremos de una manera más clara en los ejemplos. Es un concepto que descubrí con groovy y que resulta bastante interesante.

Lejos de entrar en el paradigma de la programación funcional y los lenguajes declarativos, que la verdad aún no tengo muy claros, lo que sí que nos permite con muy poco de esfuerzo es el conseguir una mayor expresividad en el código, y eso es lo que me gusta de las closures.

Un ejemplo es el típico bucle for/foreach para recorrer un array. La verdad es que siempre que lo recorramos es para ejecutar cierta lógica en su interior; y en el 90% de los casos recorreremos la lista en su totalidad.

Así, y emulando a java, vamos a crear la clase ArrayList.php y dotarla de cierta lógica que explote las closures (esto último más de groovy que de java). El siguiente código es una aproximación y seguro que hay gente de PHP que sabe mucho más que yo y que mejoraría mi código, pero creo que como ejemplo puede valernos. Entre otras cosas, dotamos a la clase ArrayList de un método each, que recibe como parámetro una función a aplicar a cada uno de los elementos de la lista.

<?php
  class ArrayList{
    private $array = NULL;
    
    public function __construct(){
      $this->array = array();
    }
    
    ...
    
    public function each($closure) {
      foreach($this->array as $item){
        $closure($item);
      }
    }
    
    public function eachWithIndex($closure) {
      foreach($this->array as $key => $item){
        $closure($key, $item);
      }
    }
    
    public function collect($closure){
      $result = new ArrayList();
      foreach($this->array as $key => $item){
        $result->add($closure($item));
      }
      return $result;
    }
    
    public function findAll($closure){
      $result = new ArrayList();
      foreach($this->array as $key => $item){
        if($closure($item)){
          $result->add($item);
        }  
      }
      return $result;
    }
    
    public function every($closure){
      foreach($this->array as $key => $item){
        if(!$closure($item)){
          return FALSE;
        }  
      }
      return TRUE;
    }
    
    public function any($closure){
      foreach($this->array as $key => $item){
        if($closure($item)){
          return TRUE;
        }  
      }
      return FALSE;
    }
    
    ...
  }

/* End of file Array.php*/

Ahora es el momento de aclarar la liosa definición de closure con un ejemplo. Supongamos que tenemos una lista de nombres, y queremos hacer dos cosas:

  • Imprimir por pantalla aquellos items que empiecen por J.
  • Además de imprimirlos, queremos contarlos.
  $lista = new ArrayList();
  
  $lista->add('Juan');
  $lista->add('Alex');
  $lista->add('Jorge');
    
  $contador = 0;
    
  $lista->eachWithIndex(
    function($index, $value) use(&$contador){
      if(strpos($value, "J") === 0){
        $contador++;
      }
      echo "$index -> $value\n";
    }
  );
    
  echo "\nHay $contador nombres que empiezan por J\n\n";

Este primer ejemplo es bastante interesante. Como vemos, la la definición de la función incluye un fragmento use(&$contador), que nos permite manipular variables en el punto donde declaramos la función, en lugar de donde la llamamos.

Otra manera realizar la misma llamada es definir una variable que declare la función, y pasarla como parámetro del método de nuestro ArrayList.

  $lista = new ArrayList();
  
  $lista->add('Juan');
  $lista->add('Alex');
  $lista->add('Jorge');
    
  $contador = 0;
  
  $tratarJota = function($index, $value) use(&$contador){
            if(strpos($value, "J") === 0){
              $contador++;
            }
            echo "$index -> $value\n";
          }
    
  $lista->eachWithIndex($tratarJota);
    
  echo "\nHay $contador nombres que empiezan por J\n\n";

Veamos otro ejemplo. Supongamos una lista con 100 números. De ella, queremos crear una nueva lista que contenga, multiplicados por dos, todos los items de la lista original.

  $lista = new ArrayList();
  
  //Inicializamos la lista con números del 1 al 100
  
  $esPar = function($item) { return $item%2 === 0; }
  $porDos = function($item) { return $item*2; }
  
  echo $lista->findAll($esPar)->collect($porDos)->join("; ");
  
  /* imprime 4; 8; 12; 16; 20; 24; 28; 32; 36; 40; 44; 48; 52; 56; 60; 
         64; 68; 72; 76; 80; 84; 88; 92; 96; 100; 104; 108; 112; 
         116; 120; 124; 128; 132; 136; 140; 144; 148; 152; 156; 160; 
         164; 168; 172; 176; 180; 184; 188; 192; 196; 200 */

El siguiente paso es ver cómo integrar esto con los frameworks actuales. Estoy buscando dónde integrar las closures con CodeIgniter para hacer un poquito más con menos.

Fuentes:

Anuncios