PHP 8.3.21 Released!

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.

Nota:

Un generador puede devolver valores, que pueden ser recuperados utilizando Generator::getReturn().

La palabra clave yield

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.

Ejemplo #1 Un ejemplo sencillo de producción de valores

<?php
function gen_one_to_three() {
for (
$i = 1; $i <= 3; $i++) {
// Note que $i se preserva entre cada producción de valor.
yield $i;
}
}

$generator = gen_one_to_three();
foreach (
$generator as $value) {
echo
"$value\n";
}
?>

El resultado del ejemplo sería:

1
2
3

Nota:

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í:

Ejemplo #2 Producción de un par clave/valor

<?php
/*
* La entrada está compuesta de campos separados por un punto y coma,
* y el primer campo es un ID para usar como clave.
*/

$input = <<<'EOF'
1;PHP;Le gustan los signos de dólar
2;Python;Le gustan los espacios en blanco
3;Ruby;Le gustan los bloques
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
    Le gustan los signos de dólar
2:
    Python
    Le gustan los espacios en blanco
3:
    Ruby
    Le gustan los bloques

Producción de valores nulos

Yield puede ser llamado sin argumento para proporcionar un valor nulo con una clave automática.

Ejemplo #3 Producción de valores nulos

<?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
}

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.

Ejemplo #4 Producción de valores por referencia

<?php
function &gen_reference() {
$value = 3;

while (
$value > 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).'... ';
}
?>

El resultado del ejemplo sería:

2... 1... 0...

Delegación del generador vía yield from

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.

Precaución

Almacenamiento en un array (e.g. con iterator_to_array())

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.

Ejemplo #5 yield from con iterator_to_array()

<?php
function inner() {
yield
1; // clave 0
yield 2; // clave 1
yield 3; // clave 2
}
function
gen() {
yield
0; // clave 0
yield from inner(); // claves 0-2
yield 4; // clave 1
}
// establece en false el 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 count_to_ten() {
yield
1;
yield
2;
yield from [
3, 4];
yield from new
ArrayIterator([5, 6]);
yield from
seven_eight();
yield
9;
yield
10;
}

function
seven_eight() {
yield
7;
yield from
eight();
}

function
eight() {
yield
8;
}

foreach (
count_to_ten() as $num) {
echo
"$num ";
}
?>

El resultado del ejemplo sería:

1 2 3 4 5 6 7 8 9 10

Ejemplo #7 yield from y los valores devueltos

<?php
function count_to_ten() {
yield
1;
yield
2;
yield from [
3, 4];
yield from new
ArrayIterator([5, 6]);
yield from
seven_eight();
return yield from
nine_ten();
}

function
seven_eight() {
yield
7;
yield from
eight();
}

function
eight() {
yield
8;
}

function
nine_ten() {
yield
9;
return
10;
}

$gen = count_to_ten();
foreach (
$gen as $num) {
echo
"$num ";
}
echo
$gen->getReturn();
?>

El resultado del ejemplo sería:

1 2 3 4 5 6 7 8 9 10
add a note

User Contributed Notes 9 notes

up
124
Adil lhan (adilmedya at gmail dot com)
12 years ago
For example yield keyword with Fibonacci:

function getFibonacci()
{
$i = 0;
$k = 1; //first fibonacci value
yield $k;
while(true)
{
$k = $i + $k;
$i = $k - $i;
yield $k;
}
}

$y = 0;

foreach(getFibonacci() as $fibonacci)
{
echo $fibonacci . "\n";
$y++;
if($y > 30)
{
break; // infinite loop prevent
}
}
up
51
info at boukeversteegh dot nl
10 years ago
[This comment replaces my previous comment]

You can use generators to do lazy loading of lists. You only compute the items that are actually used. However, when you want to load more items, how to cache the ones already loaded?

Here is how to do cached lazy loading with a generator:

<?php
class CachedGenerator {
protected
$cache = [];
protected
$generator = null;

public function
__construct($generator) {
$this->generator = $generator;
}

public function
generator() {
foreach(
$this->cache as $item) yield $item;

while(
$this->generator->valid() ) {
$this->cache[] = $current = $this->generator->current();
$this->generator->next();
yield
$current;
}
}
}
class
Foobar {
protected
$loader = null;

protected function
loadItems() {
foreach(
range(0,10) as $i) {
usleep(200000);
yield
$i;
}
}

public function
getItems() {
$this->loader = $this->loader ?: new CachedGenerator($this->loadItems());
return
$this->loader->generator();
}
}

$f = new Foobar;

# First
print "First\n";
foreach(
$f->getItems() as $i) {
print
$i . "\n";
if(
$i == 5 ) {
break;
}
}

# Second (items 1-5 are cached, 6-10 are loaded)
print "Second\n";
foreach(
$f->getItems() as $i) {
print
$i . "\n";
}

# Third (all items are cached and returned instantly)
print "Third\n";
foreach(
$f->getItems() as $i) {
print
$i . "\n";
}
?>
up
20
Hayley Watson
9 years ago
If for some strange reason you need a generator that doesn't yield anything, an empty function doesn't work; the function needs a yield statement to be recognised as a generator.

<?php

function gndn()
{
}

foreach(
gndn() as $it)
{
echo
'FNORD';
}

?>

But it's enough to have the yield syntactically present even if it's not reachable:

<?php

function gndn()
{
if(
false) { yield; }
}

foreach(
gndn() as $it)
{
echo
'FNORD';
}

?>
up
13
zilvinas at kuusas dot lt
9 years ago
Do not call generator functions directly, that won't work.

<?php

function my_transform($value) {
var_dump($value);
return
$value * 2;
}

function
my_function(array $values) {
foreach (
$values as $value) {
yield
my_transform($value);
}
}

$data = [1, 5, 10];
// my_transform() won't be called inside my_function()
my_function($data);

# my_transform() will be called.
foreach (my_function($data) as $val) {
// ...
}
?>
up
13
Harun Yasar harunyasar at mail dot com
9 years ago
That is a simple fibonacci generator.

<?php
function fibonacci($item) {
$a = 0;
$b = 1;
for (
$i = 0; $i < $item; $i++) {
yield
$a;
$a = $b - $a;
$b = $a + $b;
}
}

# give me the first ten fibonacci numbers
$fibo = fibonacci(10);
foreach (
$fibo as $value) {
echo
"$value\n";
}
?>
up
11
christophe dot maymard at gmail dot com
10 years ago
<?php
//Example of class implementing IteratorAggregate using generator

class ValueCollection implements IteratorAggregate
{
private
$items = array();

public function
addValue($item)
{
$this->items[] = $item;
return
$this;
}

public function
getIterator()
{
foreach (
$this->items as $item) {
yield
$item;
}
}
}

//Initializes a collection
$collection = new ValueCollection();
$collection
->addValue('A string')
->
addValue(new stdClass())
->
addValue(NULL);

foreach (
$collection as $item) {
var_dump($item);
}
up
7
Shumeyko Dmitriy
11 years ago
This is little example of using generators with recursion. Used version of php is 5.5.5
[php]
<?php
define
("DS", DIRECTORY_SEPARATOR);
define ("ZERO_DEPTH", 0);
define ("DEPTHLESS", -1);
define ("OPEN_SUCCESS", True);
define ("END_OF_LIST", False);
define ("CURRENT_DIR", ".");
define ("PARENT_DIR", "..");

function
DirTreeTraversal($DirName, $MaxDepth = DEPTHLESS, $CurrDepth = ZERO_DEPTH)
{
if ((
$MaxDepth === DEPTHLESS) || ($CurrDepth < $MaxDepth)) {
$DirHandle = opendir($DirName);
if (
$DirHandle !== OPEN_SUCCESS) {
try{
while ((
$FileName = readdir($DirHandle)) !== END_OF_LIST) { //read all file in directory
if (($FileName != CURRENT_DIR) && ($FileName != PARENT_DIR)) {
$FullName = $DirName.$FileName;
yield
$FullName;
if(
is_dir($FullName)) { //include sub files and directories
$SubTrav = DirTreeTraversal($FullName.DS, $MaxDepth, ($CurrDepth + 1));
foreach(
$SubTrav as $SubItem) yield $SubItem;
}
}
}
} finally {
closedir($DirHandle);
}
}
}
}

$PathTrav = DirTreeTraversal("C:".DS, 2);
print
"<pre>";
foreach(
$PathTrav as $FileName) printf("%s\n", $FileName);
print
"</pre>";
[/
php]
up
1
harl at gmail dot com
9 months ago
If you mix yielding values with keys and yielding values without keys, the result is the same as adding values to an array with or without keys.

<?php
function gen() {
yield
'a';
yield
4 => 'b';
yield
'c';
}

$t = iterator_to_array(gen());
var_dump($t);
?>

The result is an array [0 => 'a', 4 => 'b', 5 => 'c'], just as if you had written

<?php
$t
= [];
$t[] = 'a';
$t[4] = 'b';
$t[] = 'c';
var_dump($t);
?>

With the key given to 'c' being incremented from the previous numeric index.
up
-1
christianggimenez at gmail dot com
5 years ago
Module list of a number from 1 to 100.

<?php

function list_of_modulo(){

for(
$i = 1; $i <= 100; $i++){

if((
$i % 2) == 0){
yield
$i;
}
}
}

$modulos = list_of_modulo();

foreach(
$modulos as $value){

echo
"$value\n";
}

?>
To Top