phpBuddy

Code-Schnipsel

Jeder Programmierer sammelt im Laufe der Jahre Dutzende nützliche Funktionen, Klassen und sonstige Snipets. Hier gibt es einige nützliche Goodies, die jeder frei in seine eigenen Projekte einbauen kann.
Reinschauen lohnt sich!

Sie sind hier: Startseite Mehrdimensionales Array hierarchisch ausgeben
Mehrdimensionales Array hierarchisch ausgeben

Mehrdimensionales Array hierarchisch ausgeben

Zum Handwerkszeug eines PHP Programmierers gehört der perfekte Umgang mit Arrays. Bereits den meisten Anfängern bereitet die Ausgabe eines eindimensionalen Arrays keine wirklichen Probleme dank der foreach-Schleife. Komplizierter wird es dann schon, wenn man ein mehrdimensionales Array ausgeben muss. Regelrecht zum Haare raufen wird es, wenn das über 3, 4 oder mehr Ebenen in einer hierarchischen Struktur geschehen soll. Dann benötigt man etwas, dass häufig selbst Fortgeschrittenen kalte Schauer über den Rücken laufen lässt: Rekursion!

Den Begriff Rekursion kennen die meisten sicherlich bereits. Darunter versteht man in der Programmiererei, dass sich eine Funktion selbstständig erneut aufruft, bis die Abbruch-Bedingung eintritt. Hier liegt auch die Gefahr. Arbeitet die Rekursion nicht hunderprozentig, landet das Script in einer Endlosschleife, was in letzter Konsequenz den Absturz des gesamten Webserver zur Folge haben kann.

Mit PHP 5 wurde die SPL eingeführt, die einem die Arbeit mit Arrays enorm erleichtert. Anhand von zwei kleinen Beispielen möchte ich kurz zeigen, wie man sich viel Zeit und Stress sparen kann, wenn man ein mehrdimensionales Array hierarchisch ausgeben möchte.

Hier zunächst das Array mit dem wir arbeiten wollen:

$daten = array
(
    "produkte" => array
    (
        "elektronik" => array
        (
            "tv" => array
            (
                "LG Electronics",
                "Samsung",
                "Toshiba",
                "Sony",
            ),
            "video" => array
            (
                "DVD" => array
                (
                    "Sony",
                    "Panasonic",
                    "Philips",
                ),
                "Blu-ray" => array
                (
                    "Samsung",
                    "Panasonic",
                    "Philips",
                    "Toshiba",
                ),
            ),
        ),
        "baumarkt" => array
        (
            "renovieren" => array
            (
                "Tapeten",
                "Farben",
                "Bodenbeläge",
            ),
            "baustoffe" => array
            (
                "Beton",
                "Dämmstoffe",
            ),
            "arbeitskleidung" => array
            (
                "Gehörschutz",
                "Sicherheitsschuhe",
                "Schutzbrille",
            ),
        ),
    ),
    "service" => array
    (
        "Beratung",
        "Bestellung und Versand",
        "Telefon Hotline",
        "FAQ",
    ),
);

Wie unschwer zu erkennen ist, könnten das die extern gepflegten Menüpunkte einer Navigation für einen Online Shop sein. Manche Zweige sind flach, andere verzweigen sich bis in die 4. Ebene. Die einfache Ausgabe der einzelnen Einträge in einer Schleife, auch Iteration, bzw. iterieren genannt, ist mit klassischen Mitteln (rekursiv arbeitende Funktion) noch relativ einfach zu lösen. Erheblich komplizierter wird es allerdings, wenn wir wirklich eine HTML Liste für z.B. ein Menü daraus erstellen wollen.


Einfache Ausgabe mithilfe der SPL
Die SPL hält für uns verschiedene Klassen bereit, mit der wir Arrays bearbeiten können. Für eindimensionale Arrays findet die Klasse ArrayIterator Anwendung. Für mehrdimensionale Arrays, die rekursiv abgearbeitet werden müssen, gibt es die Klasse RecursiveArrayIterator. Dieser Klasse wird das zu bearbeitende Array übergeben und man erhält ein Array Objekt zurückgeliefert. Zur Bearbeitung und Abarbeitung des Array stehen zahlreiche Methoden zur Verfügung.
Da wir aber nicht jede einzelne Ebene manuell in einer Schleife abarbeiten wollen -wir müssten nämlich selbst abfragen ob es weitere Ebenen gibt, in diese Ebene wechseln, usw.- stellt uns PHP auch dafür einen automatisierten Mechanismus zur Verfügung. Diese Klasse hört auf den klangvollen Namen RecursiveIteratorIterator. Ja, zugegeben, man muss sich erst etwas an diese Namen gewöhnen. ;-)

Wir erzeugen aus unserem Array also ein RecursiveArrayIterator Objekt, das wir an die RecursiveIteratorIterator Klasse übergeben. Diese Klasse stellt uns erneut Methoden zur Verfügung, um auf jedes einzelne Element aus egal welcher Ebene zugreifen zu können. Erzeugen wir also erst mal unser Objekt:

// Iterator Objekt mithilfe der SPL Iterator-Klassen erzeugen
$datenarray = new RecursiveArrayIterator( $daten );
$iterator   = new RecursiveIteratorIterator( $datenarray, TRUE );

Soweit, sogut. Das zweite Argument TRUE, dass an den RecursiveIteratorIterator übergeben wird hat den Zweck, dass auch der Name das erste Element einer Ebene ausgegeben wird, sofern es sich dabei um ein Array handelt.
Das alles hat sich bis hierhin für die Meisten vermutlich entsetzlich kompliziert und umständlich angehört. Schauen wir uns aber einmal an, wie kompliziert die Anwendung tatsächlich ist, um an alle Elemente aus allen Ebenen zu kommen.

// Zeiger auf das erste Element setzen
$iterator->rewind();
 
// Einfache Ausgabe des Array
echo "<pre>";
foreach ($iterator as $schluessel => $wert)
{
    $ausgabe    = (is_array( $wert )) ? $schluessel : $wert;
    $einruecken = str_repeat( "    ", $iterator->getDepth() );
    echo $einruecken . $ausgabe . "\n";
}
echo "</pre>";

Tja, das war's schon. In der ersten Zeile setzen wir den internen Zeiger auf das erste Array Element. Anschließend durchlaufen wir das Array in einer einfachen foreach-Schleife. In $ausgabe halten wir den Name des Elements fest, auf dem sich der Zeiger gerade befindet. Würden wir immer nur $wert ausgeben, so hätten wir bei jedem Ebenenwechsel einfach nur Array da stehen, statt dem Name der Ebene.
$einrücken legt fest, wie viele Leerstellen eingerückt wird - also eine rein kosmetische Maßnahme. Um die entsprechende Ebene eines Elements zu ermitteln, bedienen wir uns der Methode getDepth(), die uns von der Iterator Klasse zur Verfügung gestellt wird. So sieht die Ausgabe unseres Array im Browser dann aus, wenn wir den Code ausführen:

produkte
    elektronik
        tv
            LG Electronics
            Samsung
            Toshiba
            Sony
        video
            DVD
                Sony
                Panasonic
                Philips
            Blu-ray
                Samsung
                Panasonic
                Philips
                Toshiba
    baumarkt
        renovieren
            Tapeten
            Farben
            Bodenbeläge
        baustoffe
            Beton
            Dämmstoffe
        arbeitskleidung
            Gehörschutz
            Sicherheitsschuhe
            Schutzbrille
service
    Beratung
    Bestellung und Versand
    Telefon Hotline
    FAQ

Wenn wir aber eine HTML Liste (ul) aus den Array Elementen generieren wollen, müssen wir den Code noch etwas erweitern. Dazu reicht es allerdings nicht ganz aus in der Ausgabe ein paar HTML-Tags zu ergänzen, sondern wir müssen die Basis-Klasse erweitern.


Erweiterte Ausgabe als HTML Liste
Hier zunächst das Listing der Erweiterung:

// Basis-Klasse erweitern
class ArrayAusgabe extends RecursiveIteratorIterator
{
    public $endChildren = '';
 
    // Konstruktor der Basis-Klasse aufrufen
    public function  __construct( Traversable $iterator, $modus )
    {
        parent::__construct( $iterator, $modus );
    }
 
    // Methode wird automatisch von der Basis-Klasse aufgerufen
    // wenn die Iteration über eine Ebene endet
    public function endChildren()
    {
         $this->endChildren .= "</ul>\n";
    }
}

Der einzige Grund, weshalb wir die Klasse erweitern ist der, dass wir unsere verschachtelten Listen schließen müssen, um semantisches HTML zu erhalten. Die Methode endChildren() wird immer dann automatisch von der RecursiveIteratorIterator Klasse aufgerufen, wenn keine weiteren Kind-Elemente mehr vorhanden sind. Oder anders ausgedrückt, wenn in einem Array keine Elemente mehr vorhanden sind und eine Ebene höher gewechselt wird. Das ist auch der Moment in dem wir unsere ul schließen müssen.
Der Rest des Codes ist leicht nachvollziehbar:

// Iterator Objekt erzeugen
$datenarray = new RecursiveArrayIterator( $daten );
$iterator   = new ArrayAusgabe( $datenarray, TRUE );
 
// Zeiger auf das erste Element setzen
$iterator->rewind();
 
// Unordered List (ul) öffnen
echo "<ul>\n";
// Iterator-Objekt rekursive in einer Schleife ausgeben
while ($iterator->valid())
{
    // Prüfen ob ein Kind (Sub-Array) existiert
    if ($iterator->callHasChildren())
    {
        // Schlüssel ausgeben und ul öffnen
        echo "<li>" . $iterator->key() . "<ul>\n";
    }
    // Einträge des Array ausgeben
    else
    {
        echo "<li>" . $iterator->current() . "</li>\n";
    }
 
    // Zum nächsten Array-Eintrag gehen
    $iterator->next();
 
    // Prüfen ob RecursiveIteratorIterator->endChildren() aufgerufen wurde
    if (isset( $iterator->endChildren ))
    {
        // ul schließen
        echo $iterator->endChildren;
        // Eigenschaft resetten
        unset( $iterator->endChildren );
    }
}
// ul schließen
echo "</ul>\n";

Die HTML Ausgabe, mit korrekter Verschachtelung der Listen, kann man auf der Demoseite bewundern.
Wie wir gesehen haben kann es total einfach sein sich rekursive durch multidimensionale Arrays zu hangeln. Wer mehr über die Möglichkeiten der SPL erfahren möchte, dem lege ich zunächst mein Tutorial SPL - Die Standard PHP Library an's Herz.


Bis zum nächsten Mal,
phpBuddy

Beispiel herunterladen (1 kB)