Una función generadora es igual que una función normal, con la diferencia de que en vez
de devolver un valor, un generador invoca yield tantas veces como necesita.
Cuando se llama a una función generadora, devuelve un objeto que puede ser
iterado. Cuando se itera sobre ese objeto (por ejemplo, con un
bucle foreach), PHP llamará a la función generadora cada vez que necesite un
valor, y guardará el estado del generador cuando este provea un
valor con yield para que ese estado pueda ser recuperado cuando el próximo valor sea requerido.
Cuando no hay más valores que se puedan proporcionar, la función generadora
puede simplemente terminar, y el código desde el que se la llama continuará como si un array se hubiera quedado
sin valores.
yield keyword
La clave de una función generadora es la palabra reservada yield.
En su forma más simple, la sentencia yield es parecida a la sentencia
return, excepto en que en vez de detener la ejecución de la función y
devolver un valor, yield facilita el valor al bucle que itera sobre el
generador y pausa la ejecución de la función generadora.
Ejemplo #1 Ejemplo sencillo de facilitar valores con yield
<?php
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Observe que $i es preservado entre yields
yield $i;
}
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
echo "$value\n";
}
?>
El resultado del ejemplo sería:
Nota:
Internamente, las claves enteras secuenciales serán asociadas con los valores
sobre los que se usa yield, como un array no asociativo.
Precaución
Si se utiliza yield en el contexto de una expresión (por ejemplo, en el lado derecho
de una asignación), se debe poner la sentencia yield entre
paréntesis en PHP 5. Por ejemplo, esto es válido:
Pero esto no lo es, y resultará en un error de análisis en PHP 5:
Las restricciones parentéticas no se aplican en PHP 7.
Esta sintaxis podría usarse junto con el método
Generator::send().
Utilizar yield para facilitar valores con claves
PHP soporta arrays asociativos, y los generadores no son menos. Además
de facilitar valores simples, como se muestra arriba, también se puede facilitar
una clave al mismo tiempo.
La sintaxis para facilitar un par clave-valor es muy similar a la utilizada para
definir un array asociativo, como se muestra a continuación.
Ejemplo #2 Facilitar un par clave-valor
<?php
/*
* La entrada son campos separados por punto y coma, con el primer
* campo siendo la ID utilizada como clave.
*/
$input = <<<'EOF'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
EOF;
function input_parser($input) {
foreach (explode("\n", $input) as $line) {
$fields = explode(';', $line);
$id = array_shift($fields);
yield $id => $fields;
}
}
foreach (input_parser($input) as $id => $fields) {
echo "$id:\n";
echo " $fields[0]\n";
echo " $fields[1]\n";
}
?>
El resultado del ejemplo sería:
1:
PHP
Likes dollar signs
2:
Python
Likes whitespace
3:
Ruby
Likes blocks
Precaución
Como en el ejemplo anterior, facilitar un par clave-valor
en contexto de expresión requiere que la sentencia yield sea
puesta entre paréntesis:
Facilitar valores nulos
Yield puede ser invocado sin argumentos para facilitar un valor null
con una
clave automática.
Ejemplo #3 Yielding null
s
<?php
function gen_three_nulls() {
foreach (range(1, 3) as $i) {
yield;
}
}
var_dump(iterator_to_array(gen_three_nulls()));
?>
El resultado del ejemplo sería:
array(3) {
[0]=>
NULL
[1]=>
NULL
[2]=>
NULL
}
Facilitar por referencia
Las funciones generadoras son capaces de facilitar valores por referencia igual que lo hacen por
valor. Esto se hace de la misma forma que
devolviendo referencias desde funciones:
poniendo un ampersand (signo &) delante del nombre de la función.
Ejemplo #4 Facilitar valores por referencia
<?php
function &gen_reference() {
$value = 3;
while ($value > 0) {
yield $value;
}
}
/*
* Observe que es posible cambiar $number desde dentro del bucle, y
* dado que el generador está facilitando referencias, $value
* dentro de gen_reference() cambia.
*/
foreach (gen_reference() as &$number) {
echo (--$number).'... ';
}
?>
El resultado del ejemplo sería:
Delegación de generadores mediante yield from
En PHP 7, la delegación de generadores permite producir valores desde otro
generador, objeto Traversable, o
array mediante la palabra reservada yield from.
El generador externo producirá entonces todos los valores desde el generador interno,
object, o array hasta que este ya no sea válido, después de lo cual la ejecuión
continuará en el generador externo.
Si un generador se emplea con yield from, la
expresión yield from también devolverá cualquier valor
devuelto por el generador interno.
Precaución
Almacenamiento en un array (p.ej. con iterator_to_array())
yield from no reinicia las claves. Preserva
las claves devueltas por el objeto Traversable o
array. Por tanto, algunos valores podrían compartir una clave en común con otro
yield o yield from, los cuales, en el momento
de la inserción en el array, sobrescribirán los valores antiguos con esa clave.
Un caso común donde esto importa es cuando iterator_to_array()
devuelve un array con claves por defecto, conduciendo a posibles resultados inesperados.
iterator_to_array() tiene un segundo parámetro,
use_keys
, que puede ser establecido a false
para recoger
todos los valores mientras ignora las claves devueltas por el Generator.
Ejemplo #5 yield from con iterator_to_array()
<?php
function from() {
yield 1; // clave 0
yield 2; // clave 1
yield 3; // clave 2
}
function gen() {
yield 0; // clave 0
yield from from(); // claves 0-2
yield 4; // clave 1
}
// pasar false como segundo parámetro para obtener un array [0, 1, 2, 3, 4]
var_dump(iterator_to_array(gen()));
?>
El resultado del ejemplo sería:
array(3) {
[0]=>
int(1)
[1]=>
int(4)
[2]=>
int(3)
}
Ejemplo #6 Uso básico de yield from
<?php
function contar_hasta_diez() {
yield 1;
yield 2;
yield from [3, 4];
yield from new ArrayIterator([5, 6]);
yield from siete_ocho();
yield 9;
yield 10;
}
function siete_ocho() {
yield 7;
yield from ocho();
}
function ocho() {
yield 8;
}
foreach (contar_hasta_diez() as $num) {
echo "$num ";
}
?>
El resultado del ejemplo sería:
Ejemplo #7 yield from y valores devueltos
<?php
function contar_hasta_diez() {
yield 1;
yield 2;
yield from [3, 4];
yield from new ArrayIterator([5, 6]);
yield from siete_ocho();
return yield from nueve_diez();
}
function siete_ocho() {
yield 7;
yield from ocho();
}
function ocho() {
yield 8;
}
function nueve_diez() {
yield 9;
return 10;
}
$gen = contar_hasta_diez();
foreach ($gen as $num) {
echo "$num ";
}
echo $gen->getReturn();
?>
El resultado del ejemplo sería: