PHP 5.6.22 is available

Generators - Visão Geral

(PHP 5 >= 5.5.0, PHP 7)

Os generators fornecem uma maneira fácil de implementar simples iterators sem a sobrecarga ou complexidade de criar uma classe que implemente a interface Iterator.

Um generator permite que você escreva código que use foreach para iterar em um conjunto de dados sem precisar construir um array em memória, o que pode fazer com que o limite de memória seja ultrapassado ou exigir uma quantidade considerável de tempo de processamento para a geração. Em vez disso, você pode escrever uma função generator, que é o mesmo que uma função normal, exceto que ao invés do retorno ocorrer única vez, um generator pode entregar o resultado quantas vezes forem necessárias para permitir que os valores sejam iterados.

Um exemplo simples para isso é reimplementar a função range(). A função range() padrão tem que gerar um array com cada valor dentro dele e retorná-lo, o que pode resultar em grandes arrays: por exemplo, chamando range(0, 1000000) irá resultar numa utilização de memória de mais de 100 MB.

Como alternativa, nós podemos implementar um generator xrange(), que só precisará de memória suficiente para criar um objeto Iterator e acompanhar o estado atual do generator internamente, que utiliza menos de 1 kilobyte de memória.

Exemplo #1 Implementando range() como um generator

<?php
function xrange($start$limit$step 1) {
    if (
$start $limit) {
        if (
$step <= 0) {
            throw new 
LogicException('Step must be +ve');
        }

        for (
$i $start$i <= $limit$i += $step) {
            
yield $i;
        }
    } else {
        if (
$step >= 0) {
            throw new 
LogicException('Step must be -ve');
        }

        for (
$i $start$i >= $limit$i += $step) {
            
yield $i;
        }
    }
}

/*
 * Note that both range() and xrange() result in the same
 * output below.
 */

echo 'Single digit odd numbers from range():  ';
foreach (
range(192) as $number) {
    echo 
"$number ";
}
echo 
"\n";

echo 
'Single digit odd numbers from xrange(): ';
foreach (
xrange(192) as $number) {
    echo 
"$number ";
}
?>

O exemplo acima irá imprimir:

Single digit odd numbers from range():  1 3 5 7 9
Single digit odd numbers from xrange(): 1 3 5 7 9

Generator objects

Quando uma função geradora é chamada uma primeira vez, um objeto interno de tipo Generator é retornado. Este objeto implementa a interface Iterator que um objeto somente leitura de uma direção implementaria, provendo também métodos que podem ser utilizados para manipular o estado do gerador, incluindo enviar e retornar valores.

add a note add a note

User Contributed Notes 6 notes

up
52
bloodjazman at gmail dot com
2 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
22
dc at libertyskull dot com
2 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.
up
3
info at boukeversteegh dot nl
5 months 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
15
lubaev
2 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
1
montoriusz at gmail dot com
27 days 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
-12
damirchilo at outlook dot com
9 months ago
You can also declare generator functions in a function scope.

<?php
function gen_n($n){
    function
gen_t($len){
        for(
$i = 1; $i < $len; $i++)
            yield
$i;
    }
   
    foreach(
gen_t($n) as $out)
       
printf("%d, ", $out);

   
printf("%d", ++$out);
}

gen_n(15);

?>
To Top