Menú Cerrar

Generadores de PHP

Hasta no hace demasiado recorría arrays y colecciones de datos varias con foreach sin más. Demasiado tiempo trabajando con versiones antiguas de PHP… Después descubrí que existían los iteradores (lo cuál me hizo dudar de si estaba soñando con PHP o con Java). Los iteradores son clases que ayudan a «iterar» con las colecciones de datos como los arrays, directorios, etc, y tienen métodos para leer el valor actual, avanzar, retroceder, etc. Todos estos métodos además del constructor obligan a escribir más código, pero a veces lo único que se necesita es recorrer un array y poco más.

Los generadores aparecieron en la versión de PHP 5.5 y principalmente tienen dos ventajas: código simplificado y mi favorita, no carga la colección de datos a tratar en memoria. Las desventajas en relación con los iteradores es que sólo recorren una sola vez y en una sola dirección la colección.

La forma fácil de explicar que son los generadores es decir que son como funciones, que en vez de devolver un valor (array) y acabar la ejecución con return, utiliza la palabra reservada yield, que no terminará la ejecución de la función e irá devolviendo datos uno a uno. Estos datos son valores sueltos que juntos formarían un array, pero que al ser generados de uno en uno y tratados conforme se generan, no necesitan almacenarse ocupando memoria de más.

Un generador sencillo sería:

<?php
function cuentaHastaDiez() {
    for ($i = 1; $i <= 10; $i++) {
        yield $i;
    }
}

foreach (cuentaHastaDiez() as $valor) {
    echo "$valor\n";
}
?>

Resultado:

1
2
3
4
5
6
7
8
9
10

Por supuesto se puede complicar hasta el infinito o más bien hasta cubrir la necesidad que se tenga. Ejemplo para mostrar números primos:

<?php
// $n = cuantos número primos devuelve
function numerosPrimos($n)
{
	$c = 1;
	$p = 2;
	$d = 2;
	while ($c <= $n)
	{
	   if ($p % $d == 0)
	   {
	      if ($p == $d)
	      {
	         yield $p;
	         $c++;
	      }
	      $d = 2;
	      $p++;
	   }
	   else
	      $d++;
	}
}

foreach(numerosPrimos(10) as $numero)
	echo "$numero\n";
?>

Resultado:
2
3
5
7
11
13
17
19
23
29

El while seguirá con el bucle mientras no se hayan devuelto con yield todos los números pedidos, cuando termina el bucle termina la ejecución de la función, teniendo ya devueltos todos los valores.

Los generadores resultan sencillos, sólo hay que cambiar el chip de querer amontonar todos los valores para pasarlos por un bucle después.