PHP 8.3.21 Released!

Resumen sobre los generadores

(PHP 5 >= 5.5.0, PHP 7, PHP 8)

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.

Ejemplo #1 Implementación de la función range() en forma de generador

<?php
function xrange($start, $limit, $step = 1) {
if (
$start <= $limit) {
if (
$step <= 0) {
throw new
LogicException('El paso debe ser positivo');
}

for (
$i = $start; $i <= $limit; $i += $step) {
yield
$i;
}
} else {
if (
$step >= 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 ";
}
?>

El resultado del ejemplo sería:

Números impares de un solo dígito desde range():  1 3 5 7 9
Números impares de un solo dígito desde xrange(): 1 3 5 7 9

Los objetos Generator

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.

add a note

User Contributed Notes 6 notes

up
178
bloodjazman at gmail dot com
11 years ago
for the protection from the leaking of resources
see RFC https://wiki.php.net/rfc/generators#closing_a_generator

and use finnaly

sample code

function getLines($file) {
$f = fopen($file, 'r');
try {
while ($line = fgets($f)) {
yield $line;
}
} finally {
fclose($f);
}
}

foreach (getLines("file.txt") as $n => $line) {
if ($n > 5) break;
echo $line;
}
up
47
montoriusz at gmail dot com
9 years ago
Bear in mind that execution of a generator function is postponed until iteration over its result (the Generator object) begins. This might confuse one if the result of a generator is assigned to a variable instead of immediate iteration.

<?php

$some_state
= 'initial';

function
gen() {
global
$some_state;

echo
"gen() execution start\n";
$some_state = "changed";

yield
1;
yield
2;
}

function
peek_state() {
global
$some_state;
echo
"\$some_state = $some_state\n";
}

echo
"calling gen()...\n";
$result = gen();
echo
"gen() was called\n";

peek_state();

echo
"iterating...\n";
foreach (
$result as $val) {
echo
"iteration: $val\n";
peek_state();
}

?>

If you need to perform some action when the function is called and before the result is used, you'll have to wrap your generator in another function.

<?php
/**
* @return Generator
*/
function some_generator() {
global
$some_state;

$some_state = "changed";
return
gen();
}
?>
up
18
chung1905 at gmail dot com
5 years ago
In addition to the note of "montoriusz at gmail dot com": https://www.php.net/manual/en/language.generators.overview.php#119275

"If you need to perform some action when the function is called and before the result is used, you'll have to wrap your generator in another function."
You can use Generator::rewind instead (https://www.php.net/manual/en/generator.rewind.php)

Sample code:
<?php
/** function/generator definition **/

echo "calling gen()...\n";
$result = gen();
$result->rewind();
echo
"gen() was called\n";

/** iteration **/
?>
up
28
info at boukeversteegh dot nl
9 years ago
Here's how to detect loop breaks, and how to handle or cleanup after an interruption.

<?php
function generator()
{
$complete = false;
try {

while ((
$result = some_function())) {
yield
$result;
}
$complete = true;

} finally {
if (!
$complete) {
// cleanup when loop breaks
} else {
// cleanup when loop completes
}
}

// Do something only after loop completes
}
?>
up
19
lubaev
11 years ago
Abstract test.
<?php

$start_time
=microtime(true);
$array = array();
$result = '';
for(
$count=1000000; $count--;)
{
$array[]=$count/2;
}
foreach(
$array as $val)
{
$val += 145.56;
$result .= $val;
}
$end_time=microtime(true);

echo
"time: ", bcsub($end_time, $start_time, 4), "\n";
echo
"memory (byte): ", memory_get_peak_usage(true), "\n";

?>

<?php

$start_time
=microtime(true);
$result = '';
function
it()
{
for(
$count=1000000; $count--;)
{
yield
$count/2;
}
}
foreach(
it() as $val)
{
$val += 145.56;
$result .= $val;
}
$end_time=microtime(true);

echo
"time: ", bcsub($end_time, $start_time, 4), "\n";
echo
"memory (byte): ", memory_get_peak_usage(true), "\n";

?>
Result:
----------------------------------
| time | memory, mb |
----------------------------------
| not gen | 2.1216 | 89.25 |
|---------------------------------
| with gen | 6.1963 | 8.75 |
|---------------------------------
| diff | < 192% | > 90% |
----------------------------------
up
14
dc at libertyskull dot com
11 years ago
Same example, different results:

----------------------------------
| time | memory, mb |
----------------------------------
| not gen | 0.7589 | 146.75 |
|---------------------------------
| with gen | 0.7469 | 8.75 |
|---------------------------------

Time in results varying from 6.5 to 7.8 on both examples.
So no real drawbacks concerning processing speed.
To Top