PHP 8.4.1 Released!

Sintaxe do Gerador

Uma função geradora se parece com uma função normal, exceto que ao invés de retornar um valor, um gerador pode executar yield quantas vezes forem necessárias. Qualquer função que contenha yield é uma função geradora.

Quando uma função geradora é 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 os métodos de iteração do objeto toda vez que precisar de um valor, em seguida salva o estado do gerador quando o valor é produzido, de modo que possa ser retomado quando o próximo valor for necessário.

Quando não houver mais valores a serem produzidos, o gerador pode simplesmente retornar, e o código que chamou continua como se um array tivesse ficado sem valores.

Nota:

Um gerador pode retornar valores, que podem ser recuperados usando Generator::getReturn().

A palavra-chave yield

O coração de uma função gerador é 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 gerador e pausa a execução da função do gerador.

Exemplo #1 Um exemplo simples de valores yield

<?php
function gen_one_to_three() {
for (
$i = 1; $i <= 3; $i++) {
// Observe que $i é preservado entre chamadas.
yield $i;
}
}

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

O exemplo acima produzirá:

1
2
3

Nota:

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

Produzindo valores com chaves

O PHP suporta arrays associativos e geradores 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
/*
* A saída são valores separados por ponto e vírgula, com o primeiro
* valor sendo usado como um id ou chave.
*/

$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 produzirá:

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

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 null

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

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

O exemplo acima produzirá:

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

Produzindo valores por referência

Funções geradoras 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;
}
}

/*
* Observe que podemos mudar $number dentro do loop, e
* porque o gerador retorna referências, $value
* dentro de gen_reference() é modificado.
*/
foreach (gen_reference() as &$number) {
echo (--
$number).'... ';
}
?>

O exemplo acima produzirá:

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

Delegação de gerador via yield from

A delegação de gerador permite retornar valores de outro gerador, objeto Traversable ou um array utilizando para isso a instrução yield from. O gerador externo retornará todos os valores do gerador interno, objeto ou array até que o mesmo não seja mais válido, a partir de onde a execução continuará no gerador externo.

Se um gerador é utilizado com yield from, a expressão yield from também retornará qualquer valor retornado pelo gerador interno.

Cuidado

Armazenando em um array (por exemplo, com iterator_to_array())

yield from não redefine as chaves. Ele preserva as chaves retornadas pelo objeto Traversable, ou array. Deste modo alguns valores podem compartilhar uma chave em comum com outro yield ou yield from, o qual, após inserido em um array, irá sobrescrever os valores anteriores com essa chave.

Um caso onde isso importa é a função iterator_to_array() retornando um array com chaves por padrão, levando a resultados possivelmente inesperados. iterator_to_array() possui um segundo parâmetro preserve_keys que pode ser definido como false para coletar todos os valores enquanto ignora as chaves retornadas pelo Generator.

Exemplo #5 yield from com iterator_to_array()

<?php
function inner() {
yield
1; // chave 0
yield 2; // chave 1
yield 3; // chave 2
}
function
gen() {
yield
0; // chave 0
yield from inner(); // chaves 0-2
yield 4; // chave 1
}
// Passe false no segundo parâmetro para obter um array [0, 1, 2, 3, 4]
var_dump(iterator_to_array(gen()));
?>

O exemplo acima produzirá:

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

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

O exemplo acima produzirá:

1 2 3 4 5 6 7 8 9 10

Exemplo #7 yield from e valores retornados

<?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();
?>

O exemplo acima produzirá:

1 2 3 4 5 6 7 8 9 10
adicione uma nota

Notas Enviadas por Usuários (em inglês) 9 notes

up
124
Adil lhan (adilmedya at gmail dot com)
11 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
9 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
8 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
3 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