Generatoren in PHP – eine 2-GB-Datei lesen, ohne den Speicher zu sprengen
file() lädt die ganze Datei in ein Array – bei großen Dateien ist danach der Speicher voll und das Skript tot. Mit yield gibst du Zeile für Zeile heraus und brauchst konstant wenig Speicher, egal wie groß die Datei ist.
Der Klassiker beim Import einer Kundenliste:
// ⚠️ lädt alles in den Speicher
foreach (file('export.csv') as $zeile) {
verarbeite($zeile);
}Bei 500 Zeilen ist das völlig in Ordnung. Bei 500.000 Zeilen bekommst du „Allowed memory size exhausted" — und die Versuchung, einfach memory_limit hochzudrehen. Das verschiebt das Problem nur bis zur nächsten, größeren Datei.
Die Lösung heißt yield
function zeilen(string $pfad): Generator
{
$fh = fopen($pfad, 'rb');
if ($fh === false) {
throw new RuntimeException('Datei nicht lesbar: ' . $pfad);
}
try {
while (($zeile = fgets($fh)) !== false) {
yield rtrim($zeile, "\r\n");
}
} finally {
fclose($fh); // läuft auch, wenn die Schleife abgebrochen wird
}
}
foreach (zeilen('export.csv') as $zeile) {
verarbeite($zeile);
}Von außen sieht das aus wie ein Array. Innen ist es das Gegenteil: Die Funktion läuft nur bis zum ersten yield, gibt den Wert heraus und friert ein. Beim nächsten Schleifendurchlauf macht sie genau dort weiter. Im Speicher liegt immer nur eine Zeile.
Das finally ist dabei kein Beiwerk. Bricht die Schleife mit break ab, wird der Generator eingesammelt und finally läuft — die Datei bleibt nicht offen.
Mit Schlüsseln – zum Beispiel für CSV
yield kann auch Schlüssel-Wert-Paare herausgeben:
function csvZeilen(string $pfad): Generator
{
$fh = fopen($pfad, 'rb');
$kopf = fgetcsv($fh, 0, ';');
try {
$nr = 1;
while (($werte = fgetcsv($fh, 0, ';')) !== false) {
yield $nr++ => array_combine($kopf, $werte);
}
} finally {
fclose($fh);
}
}
foreach (csvZeilen('kunden.csv') as $nr => $kunde) {
echo $nr . ': ' . $kunde['name'] . PHP_EOL;
}Jede Zeile kommt als fertiges assoziatives Array an — und trotzdem liegt nie mehr als eine davon im Speicher.
Generatoren lassen sich verketten
Das ist der Teil, der richtig Spaß macht: Ein Generator kann aus einem anderen lesen, ohne dass irgendwo eine vollständige Liste entsteht.
function nurAktive(iterable $kunden): Generator
{
foreach ($kunden as $k) {
if (($k['status'] ?? '') === 'aktiv') {
yield $k;
}
}
}
foreach (nurAktive(csvZeilen('kunden.csv')) as $kunde) {
// …
}Drei Schritte hintereinander, konstanter Speicherverbrauch. Mit array_filter hättest du dazwischen zweimal die komplette Liste im RAM.
Wo Generatoren nicht passen
Sie sind einmalig durchlaufbar. Ein zweites foreach über denselben Generator wirft einen Fehler — die Funktion ist ja schon durch. Und count() funktioniert nicht, weil niemand vorher weiß, wie viele Elemente noch kommen. Wenn du die Daten mehrfach brauchst oder zählen musst, ist ein Array die richtige Wahl.
Alles andere — Importe, Exporte, Log-Auswertungen, Zeilen aus einer API-Antwort — läuft bei mir über Generatoren. Es kostet keine zusätzliche Zeile Code und nimmt eine ganze Sorte Problem aus dem Projekt.
Du hast einen Import, der bei großen Dateien wegbricht? Das ist fast immer genau das hier. Schreib mir.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
array_find, array_any und array_all in PHP 8.4 – Arrays durchsuchen ohne Schleife
„Gibt es einen inaktiven Eintrag?", „Sind alle bezahlt?", „Hol mir den ersten Treffer" – dafür hast du bisher eine foreach-Schleife mit break geschrieben. PHP 8.4 macht daraus je eine Zeile, die genau sagt, was sie meint.
Eigene Exceptions in PHP – Fehler, die man auseinanderhalten kann
catch (Exception $e) fängt alles – auch das, was du gar nicht behandeln wolltest. Mit ein paar eigenen Exception-Klassen wird aus „irgendwas ging schief" eine Fehlermeldung, mit der der Nutzer etwas anfangen kann.
Arrays sortieren in PHP – usort und der Spaceship-Operator
Eine Liste nach Datum, dann nach Name, absteigend – ohne Bibliothek und ohne if-Kaskade. usort plus <=> macht daraus zwei Zeilen, die man auch in einem halben Jahr noch versteht.