Ein Generator ist eine Funktion, die yield enthält. Ihr Aufruf führt den Rumpf nicht aus; er liefert ein Generator-Objekt, über das foreach iterieren kann. Jedes yield $value gibt der Schleife einen Wert und pausiert die Funktion, bis die Schleife den nächsten anfordert, sodass Werte einzeln erzeugt werden, statt zuerst in einem Array gesammelt zu werden.
Die Ausgabe wechselt zwischen Generator und Schleife: Jeder Wert wird erst erzeugt, wenn die Schleife ihn anfordert.
Generatoren sparen Speicher
range(1, 1000000) baut ein Array mit einer Million Ganzzahlen auf, bevor die Schleife beginnt. Ein Generator, der dieselben Zahlen liefert, hält nie mehr als eine davon. Führe es aus und vergleiche, wie viel Speicher jede Version braucht:
Beide Schleifen geben dieselbe Summe aus, 500000500000, aber die Array-Version meldet extra memory 16.0 MB und der Generator extra memory 0.0 MB. Der Abstand wächst mit den Daten. Genau diese Idee macht Generatoren nützlich für große Dateien, Datenbankergebnisse und API-Seiten: einen Eintrag verarbeiten, wegwerfen, den nächsten nehmen.
Schlüssel und Werte mit yield liefern
yield $key => $value setzt den Schlüssel, den foreach ($gen as $key => $value) bekommt. Ohne Schlüssel nummeriert ein Generator seine Werte ab 0, wie eine Liste. Ein typischer Einsatz ist, eine Datei Zeile für Zeile mit Zeilennummern zu lesen:
Es liegt immer nur eine Zeile im Speicher, also verarbeitet dieselbe Funktion auch eine Logdatei mit 2 GB. fopen() und fgets() behandeln die hier verwendeten Dateifunktionen.
Unendliche Folgen und lazy Pipelines
Ein Generator kann endlos laufen, weil er nur so weit läuft, wie der Verbraucher anfragt. Stoppe mit break oder pack ihn in einen weiteren Generator, der die ersten paar Werte nimmt. Kleine Generatoren hintereinander ergeben eine Pipeline, in der jeder Schritt lazy ist:
fibonacci() endet nie von selbst, und trotzdem ist das Programm fertig: take() hört nach der gewünschten Anzahl auf zu fragen, und nichts darüber hinaus wird je berechnet.
yield from: an einen anderen Generator delegieren
yield from reicht jeden Wert eines Arrays, eines Iterators oder eines anderen Generators durch. So teilst du einen Generator in kleinere auf oder läufst rekursiv durch eine verschachtelte Struktur:
yield from behält die inneren Schlüssel, also liefern beide Arrays die Schlüssel 0 und 1. iterator_to_array() überschreibt dann a und b. Übergib false als zweites Argument, wann immer nur die Werte zählen.
send() und getReturn()
Ein Generator kann auch Werte empfangen. $x = yield; pausiert und wartet; $gen->send($value) setzt den Generator fort, mit $value als Ergebnis dieses yield. Und ein Generator kann mit return einen Endwert liefern, den du mit getReturn() liest, sobald er fertig ist.
Die meisten brauchen send() nie; lazy Lesen mit foreach ist der Alltagseinsatz. Es ist der Baustein hinter Coroutine-Bibliotheken.
Über einen Generator lässt sich nur einmal iterieren
Generatoren laufen vorwärts und lassen sich nicht zurückspulen. Sobald ein foreach einen verbraucht hat, wirft eine zweite Schleife über dasselbe Objekt einen Fehler:
Die Lösung ist, die Generatorfunktion für jeden Durchgang neu aufzurufen, foreach (colors() as $c), oder die Werte einmal mit iterator_to_array(colors(), false) zu sammeln, wenn du sie wirklich zweimal brauchst. Wenn du das oft tust, wäre ein einfaches Array für diese Daten die richtige Wahl gewesen.
Häufig gestellte Fragen
Was macht yield in PHP?
yield $value gibt einen Wert an den Code, der über die Funktion iteriert, und pausiert die Funktion genau dort. Der nächste Schleifendurchlauf setzt sie nach dem yield fort. Jede Funktion, die yield enthält, liefert beim Aufruf ein Generator-Objekt, statt zu laufen.
Warum einen Generator statt eines Arrays verwenden?
Ein Array hält jedes Element gleichzeitig im Speicher; ein Generator erzeugt ein Element nach dem anderen. Eine Schleife über range(1, 1000000) legt alle eine Million Ganzzahlen an, während ein Generator, der sie mit yield liefert, den Speicher flach hält. Generatoren erlauben außerdem unendliche Folgen und hören früh auf zu arbeiten, wenn die Schleife abbricht.
Was ist yield from in PHP?
yield from $source reicht jeden Wert eines anderen Generators, eines Arrays oder eines Traversable weiter, als hätte der aktuelle Generator sie selbst geliefert. Als Ausdruck ergibt es den Rückgabewert des inneren Generators.
Kann ich zweimal über einen Generator iterieren?
Nein. Ein Generator läuft nur vorwärts. Nachdem ein foreach fertig ist, wirft eine erneute Schleife Exception: Cannot traverse an already closed generator. Ruf die Generatorfunktion erneut auf, um einen neuen Generator zu bekommen, oder speichere die Werte mit iterator_to_array(), wenn du sie zweimal brauchst.
Wie wandle ich einen Generator in ein Array um?
iterator_to_array($gen) sammelt alle Werte und behält die Schlüssel des Generators. Wiederholen sich die Schlüssel, etwa nach yield from, überschreiben spätere Werte frühere, also übergibst du false als zweites Argument, um eine einfache Liste zu bekommen: iterator_to_array($gen, false).