php[world] 2015 Call for Speakers

Generator Syntax

Eine Generator Funktion sieht genau so aus wie eine normale Funktion mit Ausnahme, dass nicht ein Wert zurückgeliefert wird, sondern ein Generator liefert so viele Werte zurück, wie nötig sind (Stichwort: yield).

Wenn eine Generator Funktion aufgerufen wird, wird ein Objekt zurück geliefert. Wenn Sie über dieses Objekt iterieren (zum Beispiel, via einer foreach Schleife), wird PHP die Generator Funktion so oft aufrufen, wie Werte benötigt werden, dann wird der Status des Generators gesichert, so dass fortgefahren werden kann, wenn der nächste Wert benötigt wird.

Sobald keine weiteren Werte zurückgeliefert werden können, kann die Generator Funktion 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ückliefern: macht man dies, wird ein Kompilierungs Fehler zurückgeliefert. Eine leere return Anweisung ist eine gültige Syntax innerhalb eines Generators und wird den Generator beenden.

yield Schlüsselwort

Das Herz einer Generator Funktion 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 vielmehr einen Wert für den Code bereitstellt über den der Generator schleift, und solange die Generator Funktion pausiert.

Beispiel #1 Ein einfaches Beispiel zum produzieren (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 produzieren Werten verknüpft, so wie mit einem nicht-assoziativem array.

Achtung

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

$daten = (yield $wert);

und diese Beispiel ist nicht gültig, und wird mit einen parse Fehler beendet:

$daten = yield $wert;

Diese Syntax wird in logischen UND-Funktionen bei der Generator::send() Methode genutzt.

Produzieren von Werten mit Schlüsseln

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

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 Dollar Zeichen
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 Dollar Zeichen
2:
    Python
    mag Leerzeichen
3:
    Ruby
    mag Blöcke
Achtung

Wie oben gezeigt mit dem zurückliefern einfacher Werte, muss man beim produzieren eines Schlüssel/Wert Paares im Zuweisungs Kontext den yield Befehl einklammern:

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

Produzieren von null Werten

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

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

Generator Funktionen sind genauso in der Lage Werte durch Referenzen zurückzuliefern wie durch Werte. Dies kann in gleicher weise erfolgen wie zurückliefern von Referenzen aus Funktionen: dies geschieht durch voranstellen eines kaufmännisches 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ückliefert, 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... 

Generator Objekte

Beim ersten Aufruf einer Generator Funktion wird ein Objekt der internen Generator Klasse zurückgeliefert. Dieses Objekt implementiert das Iterator Interface in gleicher weise wie es ein forward-only iterator Objekt machen würde.

add a note add a note

User Contributed Notes 6 notes

up
43
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
3
info at boukeversteegh dot nl
4 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
3
christophe dot maymard at gmail dot com
8 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
-1
dejiakala at gmail dot com
7 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
-13
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