Generator Syntax

Eine Generatorfunktion sieht genau so aus wie eine normale Funktion mit der Ausnahme, dass ein Generator statt eines Wertes so viele Werte wie nötig zurückgibt (Stichwort: yield).

Wenn eine Generatorfunktion aufgerufen wird, wird ein Objekt zurückgegeben. Wenn Sie über dieses Objekt iterieren (zum Beispiel, via foreach-Schleife), wird PHP die Generatorfunktion so oft aufrufen, wie Werte benötigt werden. Dann wird der Status des Generators gesichert, sodass fortgefahren werden kann, wenn der nächste Wert benötigt wird.

Sobald keine weiteren Werte zurückgegeben werden können, kann die Generatorfunktion einfach beendet werden, und der rufende Code wird fortgesetzt, als gäbe es keine weiteren Werte in einem Array.

Hinweis:

Ein Generator kann keine Werte zurückgeben: macht man dies, wird ein Kompilierungsfehler zurückgegeben. Eine leere return-Anweisung ist eine gültige Syntax innerhalb eines Generators und wird den Generator beenden.

yield-Schlüsselwort

Das Herz einer Generatorfunktion ist das yield-Schlüsselwort. In seiner einfachsten Form sieht das yield-Schlüsselwort wie eine return-Anweisung aus, ausser dass die Ausführung mit der Rückgabe nicht beendet wird, sondern yield stattdessen bei der Schleife über den Generator einen Wert für den Code bereitstellt und solange die Ausführung der Generatorfunktion anhält.

Beispiel #1 Ein einfaches Beispiel zum abliefern (yielding) von Werten

<?php
function generiere_eins_bis_drei() {
    for (
$i 1$i <= 3$i++) {
        
// Hinweis: $i bleibt zwischen den yields erhalten.
        
yield $i;
    }
}

$generator generiere_eins_bis_drei();
foreach (
$generator as $wert) {
    echo 
"$wert\n";
}
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

1
2
3

Hinweis:

Intern werden sequentielle Integer-Schlüssel mit den abgelieferten Werten verknüpft, so wie mit einem nicht-assoziativen Array.

Achtung

Wenn Sie yield in einem Anweisungskontext nutzen (beispielsweise auf der rechten Seite einer Zuweisung), dann müssen Sie die yield-Anweisung in PHP 5 innerhalb von Klammern schreiben. Beispielsweise ist folgender Befehl gültig:

$daten = (yield $wert);

Aber dieser Befehl ist nicht gültig und wird in PHP 5 mit einem Syntaxfehler beendet:

$daten = yield $wert;

Für PHP 7 gilt die Klammerregel nicht.

Diese Syntax kann in Verbindung mit der Generator::send()-Methode verwendet werden.

Produzieren von Werten mit Schlüsseln

PHP unterstützt ebenfalls assoziative Arrays, und Generatoren unterscheiden sich nicht davon. Als Ergänzung zum Produzieren einfacher Werte, wie oben gezeigt, können Sie zur gleichen Zeit auch einen Schlüssel abliefern.

Die Syntax für das Produzieren eines Schlüssel/Wert-Paares ist sehr ähnlich wie die Definition von assoziativen Arrays. Siehe unten.

Beispiel #2 Produzieren eines Schlüssel/Wert-Paares

<?php
/*
 * Die Eingabe sind Semikolon getrennte Felder, mit dem ersten Feld
 * welches als ID und Schlüssel genutzt wird.
 */

$eingabe = <<<'EOF'
1;PHP;mag Dollarzeichen
2;Python;mag Leerzeichen
3;Ruby;mag Blöcke
EOF;

function 
eingabe_parser($eingabe) {
    foreach (
explode("\n"$eingabe) as $zeile) {
        
$felder explode(';'$zeile);
        
$id array_shift($felder);

        
yield $id => $felder;
    }
}

foreach (
eingabe_parser($eingabe) as $id => $felder) {
    echo 
"$id:\n";
    echo 
"    $felder[0]\n";
    echo 
"    $felder[1]\n";
}
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

1:
    PHP
    mag Dollarzeichen
2:
    Python
    mag Leerzeichen
3:
    Ruby
    mag Blöcke
Achtung

Wie oben mit dem Zurückgeben einfacher Werte gezeigt, muss man beim Produzieren eines Schlüssel/Wert-Paares im Zuweisungskontext den yield-Befehl einklammern:

$daten = (yield $schluessel => $wert);

Produzieren von null-Werten

Yield kann ohne ein Argument aufgerufen werden, um einen NULL-Wert mit einem automatischen Schlüssel zurückzugeben.

Beispiel #3 Produzieren von NULLs

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

var_dump(iterator_to_array(generiere_drei_nulls()));
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

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

Produzieren durch Referenzen

Generatorfunktionen sind genauso in der Lage Werte durch Referenzen zurückzugeben, wie durch Werte. Dies kann in gleicher Weise erfolgen, wie beim zurückgeben von Referenzen aus Funktionen: dies geschieht durch Voranstellen eines kaufmännischen Unds zum Funktionsnamen.

Beispiel #4 Produzieren von Werten durch Referenzen

<?php
function &generiere_referenz() {
    
$wert 3;

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

/*
 * Hinweis: wir können $nummer innerhalb der Schleife ändern,
 * und weil der Generator Referenzen zurückgibt, wird $wert
 * innerhalb von generiere_referenz() verändert.
 */
foreach (generiere_referenz() as &$nummer) {
    echo (--
$nummer).'... ';
}
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

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

Generatordelegation via yield from

In PHP 7 ermöglicht die Generatordelegation mittels yield from-Ausdruck Werte von einem anderen Generator, Traversable-Ojekt oder array abliefern zu lassen. Der äußere Generator liefert dann alle Werte vom inneren Generator, Objekt oder Array ab, bis dieser nicht mehr gültig ist und die Ausführung mit dem äußeren Generator fortfährt.

Falls ein Generator mit yield from verwendet wird, gibt der yield from-Ausdruck auch alle Werte zurück, die vom inneren Generator zurückgegeben werden.

Beispiel #5 Grundlegende Verwendung von yield from

<?php
function zaehle_bis_zehn() {
    
yield 1;
    
yield 2;
    
yield from [34];
    
yield from new ArrayIterator([56]);
    
yield from sieben_acht();
    
yield 9;
    
yield 10;
}

function 
sieben_acht() {
    
yield 7;
    
yield from acht();
}

function 
acht() {
    
yield 8;
}

foreach (
zaehle_bis_zehn() as $zahl) {
    echo 
"$zahl ";
}
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

1 2 3 4 5 6 7 8 9 10 

Beispiel #6 yield from und Rückgabewerte

<?php
function zaehle_bis_zehn() {
    
yield 1;
    
yield 2;
    
yield from [34];
    
yield from new ArrayIterator([56]);
    
yield from sieben_acht();
    return 
yield from neun_zehn();
}

function 
sieben_acht() {
    
yield 7;
    
yield from acht();
}

function 
acht() {
    
yield 8;
}

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

$gen zaehle_bis_zehn();
foreach (
$gen as $zahl) {
    echo 
"$zahl ";
}
echo 
$gen->getReturn();
?>

Das oben gezeigte Beispiel erzeugt folgende Ausgabe:

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

User Contributed Notes 9 notes

up
50
Adil lhan (adilmedya at gmail dot com)
3 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
10
info at boukeversteegh dot nl
1 year 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
9
Harun Yasar harunyasar at mail dot com
11 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
3
zilvinas at kuusas dot lt
6 months 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
3
christophe dot maymard at gmail dot com
1 year 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
2
Shumeyko Dmitriy
2 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
Hayley Watson
5 months 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
-2
dejiakala at gmail dot com
1 year 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
-17
denshadewillspam at HOTMAIL dot com
2 years 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