PHP 7.0.0 RC 1 Released

Sintaxe do Generator

Uma função generator se parece com uma função normal, exceto que ao invés de retornar um valor, um generator pode entregar o resultado quantas vezes forem necessárias.

Quando uma função generator é chamada, ela retorna um objeto que pode ser iterado. Quando você itera através desse objeto (por exemplo, por um loop foreach), o PHP irá chamar a função generator toda vez que precisar de um valor, em seguida salva o estado do generator quando o valor é produzido, de modo que possa ser retomado quando o próximo valor for necessário.

Uma vez que não há mais valores a serem produzidos, a função generator pode simplesmente sair, e a chamada de código continua como se um array tivesse executado os valores.

Nota:

Um generator não pode retornar um valor: isso resultará num erro de compilação. Um retorno vazio é uma sintaxe válida e fará com que o generator seja encerrado.

A palavra chave yield

O coração de uma função generator é a palavra chave yield. Na sua forma mais simples, uma declaração yield se parece muito com um retorno, exceto que em vez de parar a execução da função e retornar, o yield fornece um valor para o código de loop sobre o generator e pausa a execução da função do generator.

Exemplo #1 Um exemplo simples de valores yield

<?php
function gen_one_to_three() {
    for (
$i 1$i <= 3$i++) {
        
// Note that $i is preserved between yields.
        
yield $i;
    }
}

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

O exemplo acima irá imprimir:

1
2
3

Nota:

Internamente, chaves inteiras sequenciais serão pareadas com os valores entregues, assim como um array não associativo.

Cuidado

Se você usar um yield em um contexto da expressão (por exemplo, a direita de uma atribuição), você deve colocar a declaração yield entre parênteses. Por exemplo, isso é válido:

$data = (yield $value);

Mas isso não é válido terá como resultado um parse error:

$data = yield $value;

A sintaxe pode ser usada em conjunto com o método Generator::send().

Produzindo valores com chaves

O PHP suporta arrays associativos e generators não são diferentes. Além do produzir valores simples, como mostrado acima, você também pode produzir uma chave ao mesmo tempo.

A sintaxe para preparar um par de chave/valor é muito semelhante ao utilizado para definir um array associativo, como mostrado abaixo.

Exemplo #2 Produzindo um par de chave/valor

<?php
/*
 * The input is semi-colon separated fields, with the first
 * field being an ID to use as a key.
 */

$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";
}
?>

O exemplo acima irá imprimir:

1:
    PHP
    Likes dollar signs
2:
    Python
    Likes whitespace
3:
    Ruby
    Likes blocks
Cuidado

Da mesma forma como acontece com o yield de valores simples mostrados anteriormente, produzir um par de chave/valor num contexto da expressão requer que a declaração do yield esteja entre parênteses:

$data = (yield $key => $value);

Produzindo valores nulos

O yield pode ser chamado sem um argumento para produzir um valor NULL com uma chave automática.

Exemplo #3 Produzindo valores NULLos

<?php
function gen_three_nulls() {
    foreach (
range(13) as $i) {
        
yield;
    }
}

var_dump(iterator_to_array(gen_three_nulls()));
?>

O exemplo acima irá imprimir:

array(3) {
  [0]=>
  NULL
  [1]=>
  NULL
  [2]=>
  NULL
}

Produzindo valores por referência

Funções generator são capazes de produzir valores por referência bem como por valor. Isso é feito da mesma forma que retornar referências de funções: incluindo um & no início do nome da função.

Exemplo #4 Produzindo valores por referência

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

    while (
$value 0) {
        
yield $value;
    }
}

/*
 * Note that we can change $number within the loop, and
 * because the generator is yielding references, $value
 * within gen_reference() changes.
 */
foreach (gen_reference() as &$number) {
    echo (--
$number).'... ';
}
?>

O exemplo acima irá imprimir:

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

Objetos Generator

Quando uma função é chamada pela primeira vez, um objeto da classe interna Generator é retornado. Esse objeto implementa a interface Iterator da mesma forma como um objeto iterator que se move só para frente faria.

add a note add a note

User Contributed Notes 7 notes

up
45
Adil lhan (adilmedya at gmail dot com)
2 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
6
info at boukeversteegh dot nl
7 months 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
4
christophe dot maymard at gmail dot com
11 months 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
3
Shumeyko Dmitriy
1 year 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
2
Harun Yasar harunyasar at mail dot com
2 months 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
-1
dejiakala at gmail dot com
10 months ago
Another Fibonacci sequence with yield keyword:

<?php

function getFibonacci($first, $second, $total) {
  yield
$first;
  yield
$second;
  for (
$i = 1, $total -= 2; $i <= $total; $i++) {
   
$sum = $first + $second;
   
$first = $second;
   
$second = $sum;
    yield
$sum;
  }
}

// Generate first 10 numbers of the Fibonacci sequence starting from 0, 1
foreach (getFibonacci(0, 1, 10) as $fibonacci) {
 
// 0 1 1 2 3 5 8 13 21 34
 
echo $fibonacci . " ";
}

?>
up
-11
denshadewillspam at HOTMAIL dot com
1 year ago
Note that you can't use count() on generators.

/**
* @return integer[]
*/
function xrange() {
    for ($a = 0; $a < 10; $a++)
    {
        yield $a;
    }
}

function mycount(Traversable $traversable)
{
    $skip = 0;
    foreach($traversable as $skip)
    {
        $skip++;
    }
    return $skip;
}
echo "Count:" . count(xrange()). PHP_EOL;
echo "Count:" . mycount(xrange()). PHP_EOL;
To Top