Bei der Verarbeitung großer Datenmengen stößt PHP schnell an Speichergrenzen, wenn alle Werte gleichzeitig in einem Array gehalten werden. Generatoren lösen dieses Problem, indem sie Werte einzeln zurückgeben, anstatt ein komplettes Array im Speicher aufzubauen. Das Schlüsselwort yield macht aus einer normalen Funktion eine Generatorfunktion. Generatoren wurden in PHP 5.5 eingeführt und mit PHP 7.0 um yield from erweitert. In diesem Tutorial lernst du, wie du Generatoren erstellst, mit foreach durchläufst und für speichereffiziente Datenverarbeitung einsetzt.

Der Einstieg beginnt mit der Frage, was ein Generator überhaupt ist und wie er sich von einer normalen Funktion unterscheidet.
Was ist ein Generator in PHP?
Ein Generator ist eine spezielle Funktion, die das yield-Keyword verwendet. Im Gegensatz zu einer normalen Funktion, die alle Werte auf einmal mit return zurückgibt, liefert ein Generator einen Wert nach dem anderen. Nach jedem yield pausiert die Funktion ihre Ausführung und setzt beim nächsten Zugriff genau dort fort, wo sie aufgehört hat.
Generatoren implementieren automatisch das Iterator-Interface. Dadurch können sie direkt mit foreach durchlaufen werden, genau wie ein Array.
Die yield-Syntax
Das yield-Keyword wird innerhalb einer Funktion wie return verwendet, gibt aber nur einen einzelnen Wert zurück und pausiert die Ausführung.
<?php
function zahlenGenerator(int $von, int $bis): Generator
{
for ($i = $von; $i <= $bis; $i++) {
yield $i;
}
}
foreach (zahlenGenerator(1, 5) as $zahl) {
echo $zahl . ' ';
}
/* Ausgabe: 1 2 3 4 5 */
Bei jedem Schleifendurchlauf wird der Generator aufgerufen, gibt den nächsten Wert zurück und pausiert. Die lokalen Variablen der Funktion bleiben zwischen den Aufrufen erhalten. Der Zustand der Funktion wird eingefroren und beim nächsten Aufruf exakt an der Stelle fortgesetzt, an der yield ausgeführt wurde. Erst wenn die Funktion vollständig durchgelaufen ist, endet der Generator.
Einen Generator mit foreach durchlaufen
Generatoren werden am häufigsten mit foreach verwendet. Für den aufrufenden Code verhält sich ein Generator wie ein Array. Der entscheidende Unterschied ist, dass die Werte nicht im Voraus berechnet und gespeichert werden, sondern bei Bedarf erzeugt werden.
<?php
function fibonacci(): Generator
{
$a = 0;
$b = 1;
while (true) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
$zaehler = 0;
foreach (fibonacci() as $zahl) {
echo $zahl . ' ';
if (++$zaehler >= 10) break;
}
/* Ausgabe: 0 1 1 2 3 5 8 13 21 34 */
Dieser Generator erzeugt eine endlose Fibonacci-Folge. Da die Werte erst bei Bedarf generiert werden, ist das kein Problem. Die Schleife bricht nach 10 Werten ab.
Generator vs. Array: Der Speichervorteil
Der Hauptvorteil von Generatoren gegenüber Arrays ist die Speichereffizienz. Ein Array hält alle Werte gleichzeitig im Speicher. Ein Generator erzeugt jeweils nur einen Wert.
<?php
/* Array: alle 1 Million Werte im Speicher */
function alleZahlenArray(): array
{
$ergebnis = [];
for ($i = 0; $i < 1000000; $i++) {
$ergebnis[] = $i;
}
return $ergebnis;
}
/* Generator: immer nur ein Wert im Speicher */
function zahlenStream(): Generator
{
for ($i = 0; $i < 1000000; $i++) {
yield $i;
}
}
/* Beide lassen sich mit foreach durchlaufen,
aber der Generator braucht kaum Speicher */
Die Array-Variante verbraucht bei einer Million Integer-Werten etwa 32 MB Speicher. Der Generator verbraucht weniger als 1 KB, unabhängig von der Anzahl der Werte. Dieser Unterschied wird bei größeren Datenmengen noch dramatischer. In Produktionsumgebungen mit begrenztem Speicher kann der Einsatz von Generatoren den Unterschied zwischen einem funktionierenden und einem abbrechenden Skript ausmachen.
yield mit Schlüssel-Wert-Paaren
Generatoren können nicht nur einfache Werte, sondern auch Schlüssel-Wert-Paare liefern, ähnlich einem assoziativen Array.
<?php
function csvZeilen(string $datei): Generator
{
$handle = fopen($datei, 'r');
$zeile = 0;
while (($daten = fgetcsv($handle)) !== false) {
yield $zeile => $daten;
$zeile++;
}
fclose($handle);
}
foreach (csvZeilen('daten.csv') as $nr => $felder) {
echo "Zeile $nr: " . implode(', ', $felder) . PHP_EOL;
}
Die Syntax yield $key => $value gibt sowohl den Schlüssel als auch den Wert zurück. In der foreach-Schleife können beide wie gewohnt verwendet werden.
yield from: Werte delegieren
Mit yield from kann ein Generator die Wertausgabe an einen anderen Generator oder ein iterierbares Objekt delegieren. Das ermöglicht es, mehrere Generatoren zu einem zusammenzusetzen.
<?php
function geradeZahlen(): Generator
{
yield 2;
yield 4;
yield 6;
}
function ungeradeZahlen(): Generator
{
yield 1;
yield 3;
yield 5;
}
function alleZahlen(): Generator
{
yield from geradeZahlen();
yield from ungeradeZahlen();
}
foreach (alleZahlen() as $zahl) {
echo $zahl . ' ';
}
/* Ausgabe: 2 4 6 1 3 5 */
yield from wurde in PHP 7.0 eingeführt. Es kann nicht nur an andere Generatoren delegieren, sondern auch an Arrays, Traversable-Objekte oder jeden anderen iterierbaren Typ.
Generatoren und return
In einer Generatorfunktion beendet return den Generator. Ein Rückgabewert kann optional angegeben werden, ist aber nicht über foreach erreichbar. Stattdessen muss er mit der Methode getReturn() des Generator-Objekts abgerufen werden.
<?php
function summieren(array $zahlen): Generator
{
$summe = 0;
foreach ($zahlen as $zahl) {
$summe += $zahl;
yield $zahl;
}
return $summe;
}
$gen = summieren([10, 20, 30]);
foreach ($gen as $wert) {
echo $wert . ' ';
}
/* Ausgabe: 10 20 30 */
echo 'Summe: ' . $gen->getReturn();
/* Ausgabe: Summe: 60 */
getReturn() darf erst aufgerufen werden, nachdem der Generator vollständig durchgelaufen ist. Ein früherer Aufruf wirft eine Exception. Der Rückgabewert von return in einem Generator unterscheidet sich also grundlegend von yield: yield gibt Werte an die Schleife weiter, return speichert einen Endwert, der separat abgerufen werden muss. Dieses Muster eignet sich beispielsweise, um eine Zusammenfassung wie eine Gesamtsumme oder einen Zähler am Ende der Iteration bereitzustellen.
Werte an Generatoren senden mit send()
Die Methode send() ermöglicht es, Werte in einen laufenden Generator einzuspeisen. Der gesendete Wert wird zum Rückgabewert des aktuellen yield-Ausdrucks innerhalb des Generators.
<?php
function akkumulator(): Generator
{
$summe = 0;
while (true) {
$wert = yield $summe;
if ($wert === null) break;
$summe += $wert;
}
}
$gen = akkumulator();
$gen->current();
echo $gen->send(10) . PHP_EOL;
/* Ausgabe: 10 */
echo $gen->send(20) . PHP_EOL;
/* Ausgabe: 30 */
echo $gen->send(5) . PHP_EOL;
/* Ausgabe: 35 */
Die send()-Methode ist ein fortgeschrittenes Feature und wird in der Praxis seltener benötigt. Sie findet Verwendung in Coroutinen und asynchronen Programmiermustern.
Praxisbeispiel: Große Datei zeilenweise lesen
Einer der häufigsten Anwendungsfälle für Generatoren ist das Lesen großer Dateien. Statt die gesamte Datei in den Speicher zu laden, wird sie zeilenweise verarbeitet.
<?php
function dateiZeilen(string $pfad): Generator
{
$handle = fopen($pfad, 'r');
while (($zeile = fgets($handle)) !== false) {
yield trim($zeile);
}
fclose($handle);
}
/* Auch eine 2-GB-Logdatei verbraucht kaum Speicher */
foreach (dateiZeilen('/var/log/access.log') as $zeile) {
if (str_contains($zeile, 'ERROR')) {
echo $zeile . PHP_EOL;
}
}
Dieses Muster eignet sich für Logdateien, CSV-Importe, XML-Streams und jede andere Situation, in der Daten sequentiell verarbeitet werden. Selbst bei Dateien mit mehreren Gigabyte bleibt der Speicherverbrauch konstant niedrig, da immer nur eine einzige Zeile im Speicher gehalten wird.
Wann Generatoren verwenden?
Generatoren eignen sich besonders für folgende Szenarien:
- Verarbeitung großer Dateien (CSV, Logs, XML)
- Iteration über große Datenbankresultate
- Erzeugung endloser Sequenzen (Fibonacci, IDs, Zeitreihen)
- Lazy Evaluation, wenn nicht alle Werte benötigt werden
- Zusammensetzen mehrerer Datenquellen mit
yield from
Für kleine Datenmengen, die komplett im Speicher gehalten werden können, bieten Generatoren keinen Vorteil. Arrays sind in solchen Fällen einfacher und erlauben zufälligen Zugriff, Zählen mit count() und die Nutzung von Array-Funktionen.
Es ist auch möglich, einen Generator in ein Array umzuwandeln, wenn alle Werte auf einmal benötigt werden. Die Funktion iterator_to_array() sammelt alle Werte eines Generators in einem Array. Dabei geht der Speichervorteil allerdings verloren, da alle Werte gleichzeitig im Speicher gehalten werden.
<?php
function kleineSequenz(): Generator
{
yield 'a';
yield 'b';
yield 'c';
}
$array = iterator_to_array(kleineSequenz());
/* ['a', 'b', 'c'] */
Generatoren können auch in Kombination mit SPL-Iteratoren verwendet werden. Da Generatoren das Iterator-Interface implementieren, lassen sie sich mit Klassen wie LimitIterator oder CachingIterator kombinieren, um die Iteration weiter zu steuern. Diese Kombination ermöglicht es, die Flexibilität der SPL-Iteratoren mit der Speichereffizienz von Generatoren zu verbinden.
sequenceDiagram
participant F as foreach-Schleife
participant G as Generator
F->>G: Naechsten Wert anfordern
G-->>F: yield 1 (pausiert)
F->>G: Naechsten Wert anfordern
G-->>F: yield 2 (pausiert)
F->>G: Naechsten Wert anfordern
G-->>F: yield 3 (pausiert)
F->>G: Naechsten Wert anfordern
G-->>F: Generator beendet
Typische Fehler und Stolperfallen
Beim Arbeiten mit Generatoren gibt es einige häufige Fehlerquellen:
- Generatoren können nur einmal durchlaufen werden. Ein zweites
foreach über denselben Generator gibt keine Werte aus. Für mehrmaliges Durchlaufen muss der Generator neu erstellt werden. yield in verschachtelten Funktionen macht nur die äußere Funktion zum Generator. Eine innere Funktion mit yield ist ein eigenständiger Generator. - Generatoren sind keine Arrays. Funktionen wie
count(), array_map() oder array_filter() funktionieren nicht direkt mit Generatoren. Statt count() muss iterator_count() verwendet werden. return in einem Generator beendet ihn, der Wert ist nur über getReturn() abrufbar und wird nicht über foreach ausgegeben. - Die Verwechslung von
yield mit return: yield pausiert die Funktion und setzt später fort, return beendet sie endgültig.
Fazit
Generatoren mit yield sind ein mächtiges Werkzeug für speichereffiziente Datenverarbeitung in PHP. Sie ermöglichen es, große Datenmengen Wert für Wert zu verarbeiten, ohne den gesamten Datensatz im Speicher zu halten. Mit yield from lassen sich Generatoren delegieren und zusammensetzen. Für das Lesen großer Dateien, Datenbankabfragen und endlose Sequenzen sind Generatoren die richtige Wahl. Wer die Funktionsweise von yield versteht, hat ein zentrales Konzept moderner PHP-Programmierung gemeistert. In Kombination mit SPL-Iteratoren und der Funktion iterator_to_array() bieten Generatoren maximale Flexibilität für unterschiedliche Verarbeitungsszenarien.