Generators Resumen sobre los generadores Los generadores proporcionan una manera sencilla de implementar iteradores sin el costo ni la complejidad de desarrollar una clase que implemente la interfaz Iterator. Un generador ofrece un medio conveniente para proporcionar datos a las bucles &foreach; sin tener que construir un array en memoria de antemano, lo cual podría llevar al programa a exceder un límite de memoria o requerir un tiempo de procesamiento considerable para generarlos. En su lugar, se puede utilizar una función generadora, que es idéntica a una función normal, excepto que en lugar de devolver una sola vez, un generador puede utilizar &yield; tantas veces como sea necesario, para proporcionar los valores a recorrer. Al igual que con los iteradores, el acceso aleatorio a los datos no es posible. Un ejemplo sencillo de este mecanismo es la reimplementación de la función range en forma de generador. La función estándar range debe generar un array que contenga cada valor y devolverlo, lo cual puede llevar a arrays de gran tamaño: por ejemplo, la llamada al código range(0, 1000000) puede consumir significativamente más de 100 MB de memoria. Como alternativa, se puede implementar un generador xrange(), que solo necesitará memoria para la creación de un objeto Iterator, y deberá mantener internamente el estado actual del generador, lo cual resulta en un consumo de memoria inferior a 1 KB. Implementación de la función <function>range</function> en forma de generador = 0) { throw new LogicException('El paso debe ser negativo'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; } } } /* * Es de notar que las funciones range() y xrange() producen el * mismo resultado, a continuación. */ echo 'Números impares de un solo dígito desde range(): '; foreach (range(1, 9, 2) as $number) { echo "$number "; } echo "\n"; echo 'Números impares de un solo dígito desde xrange(): '; foreach (xrange(1, 9, 2) as $number) { echo "$number "; } ]]> &example.outputs; Los objetos <classname>Generator</classname> Cuando se llama a una función generadora, se devuelve un objeto de la clase interna Generator. Este objeto implementa la interfaz Iterator de la misma manera que lo haría un objeto iterador que solo avanza, y proporciona los métodos que pueden ser llamados para manipular el estado del generador, incluyendo el envío de valores y sus retornos. Sintaxis de un Generador Una función generadora se asemeja a una función normal, excepto que en lugar de devolver un valor, un generador &yield; devuelve tantos valores como sea necesario. Todas las funciones que contienen &yield; son funciones generadoras. Cuando se llama a una función generadora, devuelve un objeto que se puede recorrer. Cuando se recorre este objeto (por ejemplo, a través de una bucle &foreach;), PHP llamará a los métodos de iteración del objeto cada vez que necesite un valor, luego guardará el estado del generador cuando genere un valor, para que pueda ser reanudado cuando se requiera el siguiente valor. Cuando no haya más valores para proporcionar, la función generadora puede simplemente devolver, y el código de llamada continuará como si un array no tuviera más valores. Un generador puede devolver valores, que pueden ser recuperados utilizando Generator::getReturn. La palabra clave <command>yield</command> La palabra clave yield es el núcleo de una función generadora. En su forma más simple, una instrucción yield se asemeja a una instrucción return, excepto que en lugar de detener la ejecución de la función y devolver, yield proporciona un valor al código que recorre el generador, y pausa la ejecución de la función generadora. Un ejemplo sencillo de producción de valores &example.outputs; Internamente, se asociarán claves enteras secuenciales con los valores producidos, de la misma manera que para un array no asociativo. Provisión de valores con claves PHP también soporta arrays asociativos, y los generadores no son diferentes. Además de proporcionar valores simples, como hemos visto anteriormente, también se pueden proporcionar claves simultáneamente. La sintaxis para producir un par clave/valor es similar a la utilizada para definir un array asociativo; así: Producción de un par clave/valor $fields; } } foreach (input_parser($input) as $id => $fields) { echo "$id:\n"; echo " $fields[0]\n"; echo " $fields[1]\n"; } ]]> &example.outputs; Producción de valores nulos Yield puede ser llamado sin argumento para proporcionar un valor &null; con una clave automática. Producción de valores &null; &example.outputs; NULL [1]=> NULL [2]=> NULL } ]]> Producción de valores por referencia Las funciones generadoras pueden producir valores por referencia. Esto se hace de la misma manera que el retorno por referencia desde funciones : añadiendo un ET comercial (&) al nombre de la función. Producción de valores por referencia 0) { yield $value; } } /* * Note que es posible cambiar $number en el bucle, * y, dado que el generador proporciona referencias, $value * en gen_reference() también cambia. */ foreach (gen_reference() as &$number) { echo (--$number).'... '; } ]]> &example.outputs; Delegación del generador vía <command>yield from</command> La delegación del generador permite obtener los valores de otro generador, de un objeto Traversable, o de un array utilizando la palabra clave yield from. El generador externo obtendrá así todos los valores del generador interno, del objeto, o del array mientras no sea inválido, después de lo cual, la ejecución continuará en el generador externo. Si un generador se utiliza con la expresión yield from, la expresión yield from también devolverá cualquier valor devuelto por el generador interno. Almacenamiento en un array (e.g. con <function>iterator_to_array</function>) yield from no reinicia las claves. Preserva las claves devueltas por el objeto Traversable, o array. Por lo tanto, algunos valores pueden compartir una clave común con otros yield o yield from, que, al insertarse en un array, sobrescribirá los valores anteriores con esa clave. Un caso frecuente en el que esto es importante es iterator_to_array devolviendo un array con clave por defecto, lo que puede llevar a resultados potencialmente inesperados. iterator_to_array tiene un segundo parámetro preserve_keys que puede ser definido en &false; para recolectar todos los valores ignorando las claves devueltas por el Generator. <command>yield from</command> con <function>iterator_to_array</function> &example.outputs; int(1) [1]=> int(4) [2]=> int(3) } ]]> Uso básico de <command>yield from</command> &example.outputs; <command>yield from</command> y los valores devueltos getReturn(); ]]> &example.outputs; Comparación de los generadores con los objetos <classname>Iterator</classname> La principal ventaja de los generadores es su simplicidad. Menos código debe ser escrito que cuando se trata de implementar una clase Iterator, y generalmente es más legible. Por ejemplo, la función y la clase siguientes son equivalentes: fileHandle = fopen($fileName, 'r')) { throw new RuntimeException('Imposible abrir el fichero: "' . $fileName . '"'); } } public function rewind() { fseek($this->fileHandle, 0); $this->line = fgets($this->fileHandle); $this->i = 0; } public function valid() { return false !== $this->line; } public function current() { return $this->line; } public function key() { return $this->i; } public function next() { if (false !== $this->line) { $this->line = fgets($this->fileHandle); $this->i++; } } public function __destruct() { fclose($this->fileHandle); } } ]]> Sin embargo, esta flexibilidad tiene un costo: los generadores son iteradores que solo avanzan, y no pueden ser reinicializados una vez que su recorrido haya comenzado. Esto también significa que el mismo generador no puede ser utilizado varias veces: el generador deberá ser reconstruido llamando nuevamente a la función generadora. &reftitle.seealso; Iteración de Objeto