Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Lernpfade
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
Artfiles.de
Bietet Serviceorientierte...
https://www.Artfiles.de
 
 
 

PHP RecursiveArrayIterator: Arrays flach durchlaufen

Sie befinden sich: Home > Php Tutorial > PHP RecursiveArrayIterator:...

PHP RecursiveArrayIterator: Arrays flach durchlaufen
Eintrag am:
10.08.2026
Hits / Besucher:
6
Sprache:
  Deutsch
Tutorial Art:
eigenes
Eingetragen von:
Merkliste:
 
Beschreibung

Eine API-Antwort geht drei Ebenen tief, und das gewohnte foreach erwischt davon nur die oberste. Wer an dieser Stelle PHP RecursiveArrayIterator findet und die Klasse in eine Schleife steckt, bekommt exakt dasselbe Ergebnis wie vorher und hält sie für kaputt. Das ist sie nicht. PHP RecursiveArrayIterator ist nur die eine Hälfte der Lösung, und dieses Tutorial liefert die andere Hälfte gleich mit.

Illustration zum Tutorial: PHP RecursiveArrayIterator: Arrays flach durchlaufen

Das Bild zeigt den Weg von der verschachtelten Struktur zur flachen Liste. Wie dieser Weg im Code aussieht, beginnt beim Problem, das die meisten hierher führt.

Warum verschachtelte Schleifen an ihre Grenze stoßen

Solange ein Array zwei Ebenen hat, kommt man mit zwei ineinandergesteckten Schleifen aus. Das Problem beginnt an dem Tag, an dem die Daten eine Ebene tiefer werden. Dann reicht die innere Schleife mit ihrem is_array() nicht mehr, und es kommt eine dritte dazu, dann eine vierte.

<?php

$daten = ['a' => 1, 'b' => ['c' => 2, 'd' => ['e' => 3]]];

foreach ($daten as $schluessel => $wert) {
if (!is_array($wert)) {
echo $schluessel . ' = ' . $wert . PHP_EOL;
continue;
}

foreach ($wert as $k2 => $w2) {
if (is_array($w2)) {
echo $k2 . ' = ... noch eine Ebene tiefer' . PHP_EOL;
continue;
}
echo $k2 . ' = ' . $w2 . PHP_EOL;
}
}

/* Ausgabe:
a = 1
c = 2
d = ... noch eine Ebene tiefer der Wert 3 fehlt */

Der eigentliche Mangel steckt nicht in der Zeilenzahl, sondern darin, dass die Tiefe der Daten im Code fest verdrahtet ist. Jede neue Ebene zwingt zu einer Änderung an der Schleife. Genau diese Kopplung löst die Standardbibliothek von PHP mit ihren Iteratoren auf, und PHP RecursiveArrayIterator ist dort der Einstiegspunkt. Wer die Grundlagen auffrischen will, findet sie im Tutorial zu Schlüssel und Wert eines Arrays ausgeben mit foreach und bei den mehrdimensionalen Arrays.

PHP RecursiveArrayIterator und RecursiveIteratorIterator im Zusammenspiel

Der Name der Klasse ist irreführend, und daran scheitern die meisten beim ersten Versuch. PHP RecursiveArrayIterator arbeitet selbst nicht rekursiv. Er erweitert ArrayIterator um zwei Methoden: hasChildren() beantwortet die Frage, ob das aktuelle Element Kinder hat, und getChildren() liefert einen Iterator dafür. Mehr tut er nicht. In einem normalen foreach verhält er sich deshalb wie ein gewöhnliches Array.

<?php

$daten = [
'a' => 1,
'b' => ['c' => 2, 'd' => ['e' => 3]],
];

/* Sieht rekursiv aus, ist es aber nicht */
foreach (new RecursiveArrayIterator($daten) as $schluessel => $wert) {
echo $schluessel . ' => '
. (is_array($wert) ? 'Array' : $wert) . PHP_EOL;
}

/* Ausgabe:
a => 1
b => Array nur die oberste Ebene */

Der Teil, der tatsächlich hinabsteigt, heißt RecursiveIteratorIterator. Er ist der Treiber. Bei jedem Element fragt er die Quelle, ob es Kinder gibt, und wenn ja, steigt er hinein und arbeitet dort weiter. Die Rollenverteilung ist damit klar: PHP RecursiveArrayIterator ist die Quelle und weiß, welches Element Kinder hat. Der RecursiveIteratorIterator ist der Antrieb. Allein nützt keiner von beiden etwas, erst das Paar ergibt eine Funktion.

<?php

$daten = [
'a' => 1,
'b' => ['c' => 2, 'd' => ['e' => 3]],
];

$quelle = new RecursiveArrayIterator($daten);
$lauf = new RecursiveIteratorIterator($quelle);

foreach ($lauf as $schluessel => $wert) {
echo $schluessel . ' = ' . $wert . PHP_EOL;
}

/* Ausgabe:
a = 1
c = 2
e = 3 alle Blätter, egal wie tief */

Zwei Zeilen Aufbau, danach ein ganz gewöhnliches foreach. Das Entscheidende ist, was fehlt: die Tiefe der Struktur taucht nirgends im Code auf. Kommt morgen eine vierte Ebene dazu, bleibt dieser Code unverändert richtig.

Die drei Durchlaufmodi und ihre Ausgabe

Der zweite Konstruktorparameter des Treibers steuert, welche Elemente überhaupt bei der Schleife ankommen. An der Quelle ändert das nichts, denn PHP RecursiveArrayIterator beantwortet nur die Frage nach den Kindern, nicht die nach der Reihenfolge. Die drei Konstanten begreift man am schnellsten, wenn dieselbe Eingabe dreimal durchläuft und die Ausgaben nebeneinanderstehen.

<?php

$daten = ['a' => 1, 'b' => ['c' => 2, 'd' => ['e' => 3]]];

$modi = [
'LEAVES_ONLY' => RecursiveIteratorIterator::LEAVES_ONLY,
'SELF_FIRST' => RecursiveIteratorIterator::SELF_FIRST,
'CHILD_FIRST' => RecursiveIteratorIterator::CHILD_FIRST,
];

foreach ($modi as $name => $modus) {
echo $name . ':' . PHP_EOL;

$lauf = new RecursiveIteratorIterator(
new RecursiveArrayIterator($daten),
$modus
);

foreach ($lauf as $schluessel => $wert) {
/* Bei SELF_FIRST und CHILD_FIRST ist $wert
bei Zwischenknoten selbst ein Array */
echo ' ' . $schluessel . ' => '
. (is_array($wert) ? 'Array' : $wert) . PHP_EOL;
}
}

/* LEAVES_ONLY: a, c, e
SELF_FIRST: a, b(Array), c, d(Array), e
CHILD_FIRST: a, c, e, d(Array), b(Array) */

Der Unterschied ist sofort sichtbar. LEAVES_ONLY ist der Standard und liefert ausschließlich Blätter, also Werte, die selbst kein Array sind. Die Namen der Zwischenebenen tauchen gar nicht auf. Wer einen Kategoriebaum mit Einrückung ausgeben will und sich wundert, warum die Oberkategorien fehlen, hat genau hier die falsche Stellschraube.

Konstante Was wird geliefert Reihenfolge Typischer Einsatz
LEAVES_ONLY Nur Blätter, keine Zwischenknoten Von oben nach unten, Standard Werte einsammeln, Konfiguration flach ausgeben
SELF_FIRST Blätter und Zwischenknoten Knoten vor seinen Kindern Baum mit Einrückung rendern, Menüstruktur
CHILD_FIRST Blätter und Zwischenknoten Knoten nach seinen Kindern Summen von unten nach oben bilden, Teilbäume abräumen

Bei SELF_FIRST und CHILD_FIRST ist der Wert eines Zwischenknotens selbst ein Array. Ein direktes echo darauf erzeugt einen Hinweis vom Interpreter und gibt nur das Wort Array aus. Eine Abfrage mit is_array() vor der Ausgabe ist in diesen beiden Modi deshalb Pflicht.

Tiefe und Pfad mit getDepth() und getSubIterator()

Der Treiber weiß jederzeit, wo er gerade steht. getDepth() liefert die aktuelle Ebene, nullbasiert. Ein Blatt ganz oben hat die Tiefe 0, ein Wert zwei Ebenen darunter die Tiefe 2. Damit allein lassen sich schon Einrückungen bauen.

Wichtiger ist die zweite Methode. Der Schlüssel im foreach ist immer nur der letzte Teil des Weges. Wer host sieht, weiß nicht, ob der Wert unter datenbank.verbindung oder unter cache steht. getSubIterator($ebene) liefert den Iterator der jeweiligen Ebene, und ein ->key() darauf ergibt den Schlüssel dieser Ebene. Eine Schleife von 0 bis einschließlich getDepth() setzt daraus den vollständigen Pfad zusammen. Damit gibt PHP RecursiveArrayIterator im Verbund mehr Auskunft über die Struktur als jede handgeschriebene Schleife.

Praxis: verschachtelte Konfiguration flach ausgeben

Damit ist der Anwendungsfall erreicht, wegen dem die meisten Leser hier landen. Eine mehrstufige Konfiguration soll als flache Liste erscheinen, damit ein Administrator auf einen Blick sieht, welcher Wert an welcher Stelle steht. Ohne Pfadangabe ist so eine Liste wertlos, weil zwei Einträge beide name heißen können. PHP RecursiveArrayIterator und der Treiber liefern zusammen genau die Angaben, die dafür fehlen.

<?php

$konfig = [
'app' => [
'name' => 'Shop',
'debug' => false,
],
'datenbank' => [
'verbindung' => [
'host' => 'localhost',
'port' => 3306,
],
'name' => 'shop_db',
],
];

$lauf = new RecursiveIteratorIterator(
new RecursiveArrayIterator($konfig)
);

foreach ($lauf as $wert) {
$pfad = [];

/* getDepth ist nullbasiert, deshalb kleiner gleich */
for ($ebene = 0; $ebene <= $lauf->getDepth(); $ebene++) {
$pfad[] = $lauf->getSubIterator($ebene)->key();
}

echo implode('.', $pfad) . ' = '
. var_export($wert, true) . PHP_EOL;
}

/* Ausgabe:
app.name = 'Shop'
app.debug = false
datenbank.verbindung.host = 'localhost'
datenbank.verbindung.port = 3306
datenbank.name = 'shop_db' */

Beide name-Einträge sind jetzt eindeutig unterscheidbar, und var_export() macht nebenbei sichtbar, dass debug ein Boolean ist und port eine Zahl. Ein echo hätte aus false einen leeren String gemacht. Der Pfad muss dabei im Rumpf der Schleife entstehen. Ein Aufruf von getSubIterator() nach dem Durchlauf liefert kein brauchbares Ergebnis mehr.

iterator_to_array() und die Falle mit doppelten Schlüsseln

Aus dem eben Gesagten folgt der teuerste Fehler im Umgang mit PHP RecursiveArrayIterator. Wer das Ergebnis schnell in ein Array umwandeln will, greift zu iterator_to_array() und verliert dabei stillschweigend Daten. Es gibt keine Warnung, keine Exception, nur weniger Einträge als vorher.

<?php

$laender = [
'de' => ['name' => 'Deutschland', 'iso' => 'DE'],
'fr' => ['name' => 'Frankreich', 'iso' => 'FR'],
];

$lauf = new RecursiveIteratorIterator(
new RecursiveArrayIterator($laender)
);

/* Falle: der Iterator liefert nur den letzten Schlüsselteil,
gleichnamige Schlüssel überschreiben sich lautlos */
print_r(iterator_to_array($lauf));
/* ['name' => 'Frankreich', 'iso' => 'FR']
Vier Werte hinein, zwei heraus */

/* Richtig, wenn nur die Werte gebraucht werden */
print_r(iterator_to_array($lauf, false));
/* [0 => 'Deutschland', 1 => 'DE',
2 => 'Frankreich', 3 => 'FR'] */

Der zweite Parameter entscheidet alles. Mit false verwirft die Funktion die Schlüssel und nummeriert stattdessen durch, wodurch jeder Wert erhalten bleibt. Wer die Schlüssel wirklich braucht, baut sich den Pfad wie im vorigen Abschnitt zusammen und füllt damit ein eigenes Array. Dieselbe Vorsicht gilt bei jeder anderen Quelle, die Schlüssel mehrfach vergibt, etwa bei Generatoren mit yield.

Objekte, Tiefenbegrenzung und eigene Regeln für PHP RecursiveArrayIterator

Jetzt zu einem Punkt, der im Netz erstaunlich oft falsch herum steht. Objekte im Array gelten für den Iterator standardmäßig als Kinder. Er steigt also hinein und liefert deren öffentliche Eigenschaften als einzelne Blätter. Wer Wertobjekte in der Struktur hat, bekommt sie in ihre Bestandteile zerlegt, ohne das bestellt zu haben.

<?php

class Adresse
{
public function __construct(
public string $ort = 'Kiel',
public string $plz = '24103'
) {
}
}

$daten = ['kunde' => 'Meier', 'adresse' => new Adresse()];

/* Standard: der Iterator steigt auch in Objekte hinein */
$standard = new RecursiveIteratorIterator(
new RecursiveArrayIterator($daten)
);
foreach ($standard as $schluessel => $wert) {
echo $schluessel . PHP_EOL;
}
/* kunde, ort, plz */

/* Mit Flag: das Objekt bleibt am Stück */
$nurArrays = new RecursiveIteratorIterator(
new RecursiveArrayIterator(
$daten,
RecursiveArrayIterator::CHILD_ARRAYS_ONLY
)
);
foreach ($nurArrays as $schluessel => $wert) {
echo $schluessel . ' (' . get_debug_type($wert) . ')' . PHP_EOL;
}
/* kunde (string), adresse (Adresse) */

Das Flag RecursiveArrayIterator::CHILD_ARRAYS_ONLY mit dem Wert 4 beschränkt den Abstieg auf echte Arrays. Objekte kommen dann als Ganzes im Blatt an. Dass die Klasse ihre Wurzeln in der Standardbibliothek hat, zeigt sich an dieser Stelle deutlich, denn sie erbt dieselbe Flag-Mechanik wie PHP ArrayObject. Die eine Klasse baut eine Struktur auf, die andere durchläuft eine bestehende.

Für die zweite Art von Begrenzung ist der Treiber zuständig. setMaxDepth() legt fest, wie weit der Abstieg reicht, und setMaxDepth(-1) hebt die Grenze wieder auf. Das rettet bei sehr tiefen Strukturen und bei Daten, die sich selbst enthalten.

<?php

$tief = [
'app' => ['cache' => ['treiber' => 'redis']],
'name' => 'Shop',
];

$lauf = new RecursiveIteratorIterator(
new RecursiveArrayIterator($tief),
RecursiveIteratorIterator::SELF_FIRST
);
$lauf->setMaxDepth(1);

foreach ($lauf as $schluessel => $wert) {
echo $lauf->getDepth() . ' ' . $schluessel . ' => '
. (is_array($wert) ? 'Array' : $wert) . PHP_EOL;
}

/* Ausgabe:
0 app => Array
1 cache => Array
0 name => Shop 'treiber' liegt zu tief */

Reicht eine Zahl als Grenze nicht aus, weil die Entscheidung fachlich ist, wird geerbt. Eine eigene Klasse leitet von PHP RecursiveArrayIterator ab und überschreibt hasChildren(). Ein Koordinatenpaar oder ein Adressblock bleibt damit am Stück, während echte Listen weiterhin aufgelöst werden.

<?php

class NurListen extends RecursiveArrayIterator
{
public function hasChildren(): bool
{
$aktuell = $this->current();

return is_array($aktuell) && array_is_list($aktuell);
}
}

$daten = [
'meta' => ['titel' => 'Artikel'], /* bleibt am Stück */
'tags' => ['php', 'spl'], /* wird aufgelöst */
];

$lauf = new RecursiveIteratorIterator(new NurListen($daten));

foreach ($lauf as $schluessel => $wert) {
echo $schluessel . ' => '
. (is_array($wert) ? 'Array' : $wert) . PHP_EOL;
}

/* meta => Array
0 => php
1 => spl */

Drei Zeilen Logik in einer eigenen Methode reichen aus. Die fachliche Regel steht damit an einer Stelle und gilt für jeden Durchlauf, der diese Quelle benutzt.

Stolperfallen, die immer wieder Zeit kosten

Quelle und Treiber sind zwei Objekte mit zwei Aufgaben. Wer diese Trennung im Kopf zusammenschiebt, landet bei einer der vier Fragen, die in Foren zu PHP RecursiveArrayIterator seit Jahren in fast unveränderter Reihenfolge auftauchen.

Die Schleife liefert nur die oberste Ebene

Dann fehlt der Treiber. Die Quelle allein verhält sich wie ein normales Array. Das Objekt muss in ein RecursiveIteratorIterator eingepackt werden, sonst passiert nichts Rekursives.

Die Zwischenebenen fehlen in der Ausgabe

Der Standardmodus LEAVES_ONLY liefert nur Blätter. Für einen Baum mit Oberkategorien wird SELF_FIRST als zweites Argument gebraucht.

Nach iterator_to_array() fehlen Werte

Gleichnamige Schlüssel auf verschiedenen Ästen überschreiben sich beim Umwandeln. Der zweite Parameter false verwirft die Schlüssel und behält alle Werte.

Der Pfad ist immer um eine Ebene zu kurz

Die Zählung von getDepth() beginnt bei null. Die Schleife zum Pfadaufbau muss bis einschließlich getDepth() laufen, ein < statt <= verschluckt den letzten Teil.

Ob array_walk_recursive() genügt oder das Gespann aus Quelle und Treiber nötig wird, hängt allein am Ziel. Der Modus und die Frage nach dem Pfad kommen erst danach.

flowchart TD
    A[Array verschachtelt] --> B{Ziel?}
    B -->|Werte aendern| C[array_walk_recursive]
    B -->|Lesen| D[RecursiveArrayIterator]
    D --> E[RecursiveIteratorIterator]
    E --> F{Modus?}
    F -->|Nur Blaetter| G[LEAVES_ONLY]
    F -->|Knoten zuerst| H[SELF_FIRST]
    F -->|Kinder zuerst| I[CHILD_FIRST]
    G --> J{Pfad noetig?}
    J -->|Ja| K[getDepth plus getSubIterator]
    J -->|Nein| L[iterator_to_array Flag false]

PHP RecursiveArrayIterator, array_walk_recursive() und eigene Rekursion

Die Frage nach der Alternative kommt spätestens im Code-Review. Ein array_walk_recursive() schickt einen Callback durch die Struktur und ändert Werte an Ort und Stelle, kennt aber weder die Tiefe noch den Pfad des aktuellen Elements. PHP RecursiveArrayIterator kehrt das um: ein normales foreach mit vollem Kontext, dafür ohne Schreibzugriff auf das Original.

<?php

$preise = ['a' => 1, 'b' => ['c' => 2]];

/* array_walk_recursive: ändert an Ort und Stelle,
kennt aber weder Tiefe noch Pfad */
array_walk_recursive($preise, function (&$wert, $schluessel): void {
$wert = $wert * 10;
});
/* ['a' => 10, 'b' => ['c' => 20]] */

/* Iterator: normales foreach mit vollem Kontext,
dafür ohne Schreibzugriff auf das Original */
$lauf = new RecursiveIteratorIterator(
new RecursiveArrayIterator($preise)
);

foreach ($lauf as $wert) {
echo 'Ebene ' . $lauf->getDepth() . ': ' . $wert . PHP_EOL;

if ($wert > 15) {
break; /* mit dem Callback nicht möglich */
}
}

Damit steht die Regel fest. Sollen Werte im Original geändert werden, bleibt array_walk_recursive() aus der array_walk-Familie die einfachere Wahl. Sobald Tiefe, Pfad, Zwischenknoten oder ein vorzeitiger Abbruch gebraucht werden, gewinnt der Iterator. Ein break im foreach funktioniert sofort, im Callback gibt es dafür keine Entsprechung.

Bleibt die selbstgeschriebene rekursive Funktion. Sie funktioniert, keine Frage, aber sie ist bei jedem Projekt neu zu schreiben und neu zu testen, und die Modi sowie die Tiefenbegrenzung fehlen ihr. Eine Generatorfunktion mit yield from löst dieselbe Aufgabe speichersparend, liefert den Pfad aber ebenfalls nur, wenn er von Hand mitgeführt wird. Wer ehrlich rechnet, kommt bei PHP RecursiveArrayIterator auf zwei Zeilen Aufbau und bekommt Reihenfolge, Tiefe und Pfad dazu.

Fazit

PHP RecursiveArrayIterator ist die Quelle, RecursiveIteratorIterator der Treiber. Wer diese Rollenverteilung einmal verstanden hat, durchläuft ein beliebig tief verschachteltes Array in einer einzigen Schleife, und die Tiefe der Daten taucht im Code nicht mehr auf. Der Standardmodus LEAVES_ONLY liefert nur Blätter, SELF_FIRST nimmt die Zwischenknoten vor ihren Kindern mit und CHILD_FIRST danach.

Den meisten Nutzen bringt die Kombination aus getDepth() und getSubIterator(), mit der sich der vollständige Schlüsselpfad zusammensetzen lässt. Zwei Dinge sind dabei im Blick zu behalten: iterator_to_array() ohne zweiten Parameter verliert gleichnamige Schlüssel, und Objekte werden ohne CHILD_ARRAYS_ONLY in ihre Eigenschaften zerlegt. Zum Ändern von Werten bleibt array_walk_recursive() das passendere Werkzeug, zum Lesen mit Kontext ist PHP RecursiveArrayIterator die bessere Wahl.

 

Tags:

 

 

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Melde dich an, um einen Kommentar zu schreiben.
Bücherregal mit drei Büchern: 'PHP 4 - Grundlagen und Profiwissen' von Hanser Verlag, 'Webdesign in a Nutshell' von O'Reilly Verlag, und 'Webgestaltung' von Galileo Computing.