PHP: yield Generator

Artikel geschrieben am 15.12.2021 um 14:38 Uhr.

Den Generator-Syntax kann man schlechter erklären, als in einem Beispiel demonstrieren. Zumindest ich finde es deutlich leichter zu begreifen anhand eines Beispiels. Wir wollen ein Wertungssystem und benötigen dafür flott eine Liste mit allen Werten von der Zahl '0' bis '9'.

$Liste = range('0', '9');
var_dump($Liste); // => array(10) { [0] => int(0), [1] => int(1), [...], [9] => int(9) }

Das gleiche Ziel kann man mit einer Generator-Funktion erreichen, die man wie folgt aufbaut.

function rangeWertungssystem()
{
	for( $i = 0; $i < 10; $i++ )
	{
		yield $i;
	}
	
	return 10;
}
$Liste = \iterator_to_array(\rangeWertungssystem());

Natürlich ist das Beispiel nicht unbedingt eines, indem man eine Generator-Funktion als sinnvoller ansehen würde, aber die Funktionsweise ist damit recht anschaulich.
Interessanter wird es und das ist vermutlich die Hauptanwendung einer Liste, sie mit einer Schleife zu durchlaufen und ihre Werte auszugeben. Während man im normalen Fall die $Liste durchgehen würde, kann man im Falle des Generators direkt diesen nutzen. Das yield-Schlüsselwort ist sowas wie eine Markierung, die definiert, das dieser Wert als Element zählt.

$Generator = \rangeWertungssystem();
foreach( $Generator as $value )
{
	echo $value."/";
}
echo $Generator->getReturn();
// => 0/1/2/3/4/5/6/7/8/9/10

Wie man sieht erhält man die einzelnen Elemente, die mit yield definiert werden. Zusätzlich ist es möglich über die Methode ->getReturn() noch einen zusätzlichen Wert, z.B. die Anzahl aller Elemente o.ä., zu erhalten.

Als Anwendungsgebiet ist mir sofort die Verwendung bei der Rückgabe einer Datenbankabfrage eingefallen. Zwar implementiert \mysqli_result ein Iterator-Interface, d.h. man kann es direkt in einer foreach-Schleife nutzen, aber der Wert wird immer ein Ergebnis der fetch_assoc()-Methode sein!
Schreibt man eine eigene Generator-Funktion, dann könnte man nicht nur die Abfrage, ob das Ergebnis gültig ist, unterbringen, sondern gleichzeitig auch innerhalb der Schleife selbst bestimmen, ob es fetch_assoc, fetch_row oder gar fetch_object sein soll. Gerade in einer Applikation mit einem Datenbank-Mapper kann es enorme Vorteile haben, direkt Objekte zu erhalten. Außerdem können diverse Einstellungen einfach als Parameter an diese Generator-Funktion übermittelt werden.
Natürlich wird es bestimmte Einschränkungen und Fälle geben, in denen diese Variante nicht genutzt werden sollte, aber den Syntax und seine funktionsweise zu kennen ist definitiv wertvoll, falls man in der Entwicklung eine schnelle und einfache Implementierung eines Iterators benötigt. Lässt der Anwendungsfall eine Verwendung zu, dann hat man damit eine übersichtliche und kurze Implementierung eines sonst meist schwerer nachvollziehbaren Ableitung.

Möchte man dem yield-Syntax nicht nur einen Wert sondern ein Wertepaar zurückliefern, dann geht das über die bekannte Array-Definiton: $data = (yield $key => $value);.

Beispiel: Fibonacci-Folge

function erstelleFibonacciFolge()
{
    $i = 0;
    $k = 1;
    yield $k;
    while(true)
    {
        $k = $i + $k;
        $i = $k - $i;
        yield $k;       
    }
}

$Schleifendurchlaeufe = 0;
foreach( erstelleFibonacciFolge() as $fibonacciZahl )
{
    echo $fibonacciZahl . "\n";
	
	# Schleifenzähler, damit keine unendliche Rekursion stattfindet.
    $Schleifendurchlaeufe++;
    if($Schleifendurchlaeufe > 30)
    {
        break;
    }
}