SPL – Die Standard PHP Library

Vorwort

Mit PHP 5, das mittlerweile bereits 3 Jahre alt ist, haben auch viele neue Funktionen Einzug in den Programmierer Alltag gehalten. Einige der neuen Funktionen haben auf Anhieb den Weg in’s Programmierer Herz gefunden, während andere Neuerungen nach wie vor unbeachtet bleiben.
Eine dieser genialen, aber wenig beachteten Neuerungen ist die Standard PHP Library kurz SPL. Mit wenigen Worten zu beschreiben was die SPL ist, ist nicht einfach. Prinzipiell versucht die SPL, wie es der Name schon vermuten läßt, für gewisse wiederkehrende Probleme einen einheitlichen Lösungs-Standard zu schaffen.
Der Entwickler der SPL beschreibt es auf seiner Seite so:

SPL – Standard PHP Library SPL is a collection of interfaces and classes that are meant to solve standard problems and implements some efficient data access interfaces and classes.

In dieser SPL-Einführung wollen wir als Anschauungsobjekt eine relativ primitive Kernfunktion eines Datei-Backup Scripts erstellen das Verzeichnisse rekursive ausliest, bestimmte Datei-Typen herausfiltert und dann, unter Beibehaltung der Verzeichnisstruktur, ein Zip-Archiv der Dateien erstellt. Bei dieser Aufgabe lassen wir uns von den Iterator-Klassen der SPL helfen. Das hört sich im ersten Moment vielleicht schwierig und kompliziert an, aber wie wir sehen werden ist das, dank der SPL, (fast) so einfach wie eine Email per PHP zu versenden!

Doch bevor wir anfangen sollte sich jeder der noch keine Ahnung hat was die SPL ist einen ersten Überblick verschaffen: Standard PHP Library Homepage

Sollte man nach diesem Artikel auf den Geschmack gekommen sein, so sollte dieser Link in die Bookmarks aufgenommen werden. Leider ist die SPL (noch) sehr schlecht dokumentiert auf der PHP-Webseite und vieles findet man nur durch trial&error; heraus. Die Übersicht auf der Entwicklerseite bietet noch den besten Überblick was mit der SPL möglich ist.

Voraussetzungen

Dieses Einführung richtet sich an fortgeschrittene PHP Programmierer. Dementsprechend sollten folgende Voraussetzungen erfüllt sein um diesem Artikel folgen zu können:

  • fundierte PHP Kenntnisse
  • ein gutes Verständnis der OOP
  • Webserver oder Xampp mit PHP 5 – optimalerweise das aktuelle PHP 5.2.3
  • aktivierte SPL und ZipArchive Klasse (sollte standardmäßig aktiv sein)

Um zu prüfen ob die benötigten Klassen zur Verfügung stehen einfach eine PHP Datei mit dem Inhalt

echo "<pre>" .print_r( get_declared_classes(), true ). "</pre>";

auf dem Server aufrufen und schauen ob folgende Klassen-Namen aufgeführt sind.

  1. ArrayObject
  2. ArrayIterator
  3. RecursiveDirectoryIterator
  4. RecursiveIteratorIterator
  5. FilterIterator
  6. ZipArchive

Sind alle Voraussetzungen erfüllt kann es los gehen. Die Erklärungen in dieser Einleitung fallen etwas knapper aus, als es in meinen anderen Tutorials der Fall ist. Grund dafür ist, daß der Artikel auch ohne detaillierte Erläuterungen recht lang ist und darüber hinaus ist der Stoff nicht schwer zu verstehen, wenn man die oben erwähnten Voraussetzungen mitbringt.

Was ist ein Iterator?

Der Ausdruck Iterator ist jetzt schon häufiger gefallen, deswegen sollten wir vielleicht erst einmal klären was ein Iterator überhaupt ist. Am besten trifft wohl die Bezeichnung Zeiger als Übersetzung zu. Wenn wir uns durch ein Objekt bewegen (iterate), verschieben wir dabei einen Zeiger (Iterator) innerhalb des Objekts um auf bestimmte Inhalte zuzugreifen. Dabei ist es uns egal wie die Struktur der Daten aussieht, alles was uns interessiert ist deren Inhalt. In allen untenstehenden Beispielen werde ich den Ausdruck Zeiger verwenden, damit man die Funktionsweise des Iterator besser nachvollziehen kann.

Unser erstes Iterator-Beispiel

Das klassische Anwendungsgebiet für Iteratoren sind Arrays. Also erstellen wir ein kleines Array, leiten ein Objekt davon ab und schauen uns an was wir damit anstellen können.

$webtechnologien = array( "PHP", "HTML", "CSS", "MySQL", "Ruby", "JavaScript", "Flash" );
$objekt = new ArrayObject( $webtechnologien );
$zeiger = $objekt->getIterator();

Unser Array habe ich jetzt mal webtechnologien genannt, unabhängig davon ob jemand mit dem Inhalt des Array nicht einverstanden ist.
In Zeile 2 sprechen wir bereits die Klasse ArrayObject an und übergeben unser Array an diese Klasse. Das zurück gelieferte Objekt legen wir in $objekt ab. Die Klasse ArrayObject stellt zahlreiche Methoden zur Verfügung, die uns den Umgang mit einem Array erleichtert. So kann man z.B. auf zahlreiche klassische Sortier-Funktionen zugreifen, wie etwa

  • $objekt->asort()
  • $objekt->ksort()
  • $objekt->uasort()
  • $objekt->uksort()
  • $objekt->natsort()
  • $objekt->natcasesort()

Eine weitere Methode die man häufig braucht ist $objekt->count(), die einem die Anzahl der im Array befindlichen Elemente liefert. Benötigt wird das u.a. um zu wissen wie viele Schleifen-Durchläufe wir machen müssen, wie wir weiter unten noch sehen werden.
Zusammen mit der Klasse ArrayObject werden automatisch weitere Klassen geladen die ihrerseits Methoden zur Verfügung stellt. Eine dieser Klassen ist die Iterator Klasse und bildet mit ihren Methoden den Kern des Ganzen. Wer sich mal „kurz“ einen kleinen Überblick verschaffen möchte in welchem Zusammenhang die Iterator Klasse zu den anderen Klassen steht, dem möchte ich dieses niedliche Diagramm an’s Herz legen.

Aber schauen wir uns mal die Methoden der Iterator Klasse an:

  • current() – Beinhaltet den Wert des aktuellen Elements auf dem sich der Zeiger befindet
  • key() – Beinhaltet den Schlüssel des aktuellen Elements auf dem sich der Zeiger befindet
  • next() – Bewegt den Zeiger zum nächsten Element
  • rewind() – Bewegt den Zeiger an den Anfang des Objekts/Arrays
  • valid() – Prüft ob ein Element existiert, nachdem mit next() oder rewind() die Zeigerposition verändert wurde.

In unserem Beispiel oben ermitteln wir also mit $zeiger = $objekt->getIterator(); die Position des aktuellen Elements und übergeben diese an unseren Zeiger. Ab sofort stellt unser Zeiger ($zeiger) ein Objekt dar und wir können mit den Methoden der Klassen ArrayObject und Iterator arbeiten. Machen wir das doch einfach mal, um das etwas zu verdeutlichen:

$webtechnologien = array( "PHP", "HTML", "CSS", "MySQL", "Ruby", "JavaScript", "Flash" );
$objekt = new ArrayObject( $webtechnologien );
$zeiger = $objekt->getIterator();
 
$zeiger->rewind(); // setzt den Zeiger an die erste Position im Array
echo $zeiger->count(). "<br />"; // gibt 7 aus, da 7 Elemente in unserem Array sind
echo $zeiger->current(). "<br />"; // gibt PHP aus, da sich der Zeiger auf dem ersten Element befindet
$zeiger->next(); // bewegt den Zeiger zur nächsten Position
if ($zeiger->valid()) // prüft ob sich der Zeiger auf einem gueltigen Element befindet.
{
    echo $zeiger->key(). "<br />"; // gibt 1 aus, da sich der Zeiger auf dem 2. Element befindet 
}
 

Soweit mal die absoluten Basics. Jeder mit etwas PHP Erfahrung wird hier nicht wirklich etwas neues gesehen haben, abgesehen davon das wir ein Array objektorientiert angesprochen haben. Einzig rewind() ist vielleicht etwas neu, aber die Funktionsweise entspricht der von reset(), die hinlänglich bekannt sein sollte.

Array Iteration in der Praxis
Ist ja alles schön und gut, nur wühlt sich niemand manuell durch ein Array, deswegen sollten wir uns einmal anschauen welche Möglichkeiten wir haben ein Array mit einer Schleife auszugeben. Zu dem Zweck basteln wir uns erst mal ein neues, assoziatives Array mit einigen fiktiven Angaben und erzeugen das benötigte Objekt.

$person = array( "Vorname" => "Max",
                 "Nachname" => "Mustermann",
                 "Alter" => 39,
                 "Haarfarbe" => "Braun",
                 "Beruf" => "Rauhhaardackelzüchter" );
 
$PersonObjekt = new ArrayObject( $person );
$zeiger = $PersonObjekt->getIterator();

Soweit gibt’s hier nichts neues zu sehen. Schauen wir uns jetzt an, wie wir an unsere Daten kommen.

Als foreach()-Schleife

$zeiger->rewind();
foreach ($zeiger as $schluessel => $wert)
{
    echo $schluessel. ": " .$wert. "<br />";
}

Als for()-Schleife

for ($zeiger->rewind(); $zeiger->valid(); $zeiger->next())
{
    echo $zeiger->key(). ": " .$zeiger->current(). "<br />";
}

Als while()-Schleife

$zeiger->rewind();
while ($zeiger->valid())
{
    echo $zeiger->key(). ": " .$zeiger->current(). "<br />";
    $zeiger->next();
}

Wie wir sehen bleibt sich bei foreach() alles beim alten. Kein Wunder, ist doch foreach() eigentlich die Ausgabemethode für Arrays.

Bei der for()-Schleife setzen wir den Zeiger auf den Array-Anfang ($zeiger->rewind()) zurück und übernehmen diese Position als Startwert für die Schleife. Als Bedingung benutzen wir hier $zeiger->valid() um zu prüfen ob die aktuelle Zeigerposition auf ein valides Array-Element zeigt. Mit $zeiger->next() legen wir den Intervall fest. Die Ausgabe ist dann Element-Schlüssel ($zeiger->key()) und der Element-Wert ($zeiger->current()).

Bei der while()-Schleife müssen wir etwas aufpassen, damit wir nicht durch Nachlässigkeit eine Endlosschleife produzieren. Hier wird nämlich als Bedingung geprüft: solange $zeiger->valid() True ist, führe die Schleife aus. Deswegen ist es zwingend notwendig, daß wir innerhalb der Schleifen den Zeiger immer um eine Position nach vorn schieben ($zeiger->next()), wenn die Ausgabe erfolgt ist. Nur so können wie sicherstellen das die Bedingung irgendwann False ist – nämlich dann, wenn wir an der letzten Position im Array angekommen sind und den Zeiger „über das Ende hinaus“ schieben.

Alles bis hierhin war (hoffentlich) einfach nachvollziehbar. Zugegeben, einen wirklichen Durchbruch gegenüber der prozeduralen Herangehensweise ist noch nicht so ganz zu erkennen. Allerdings ist es für komplexere Aufgaben zwingend erforderlich das man den Umgang mit den Basisfunktionen beherrscht. Denn ganz gleich welche Iterator Klassen aus der SPL wir benutzen, in allen wird auf eine sehr ähnliche, bzw. identische Weise navigiert. Schauen wir uns nun ein mehr praxisbezogenes Beispiel an.

Mit Verzeichnissen arbeiten – DirectoryIterator

Was für Arrays die ArrayIterator Klasse ist, ist für Verzeichnisse die DirectoryIterator Klasse. Diese Klasse stellt Methoden zu Verfügung mit der wir ohne großen Aufwand gängige Verzeichnis-Aktionen ausführen können. Schauen wir uns im Schnelldurchlauf mal die wichtigsten Methoden an, die weitestgehend selbsterklärend sind. Neben den üblichen 5 Verdächtigen der Iterator Klasse (siehe oben) stehen weiterhin zur Verfügung:

  • getFilename() – Der Dateiname ohne Pfad
  • getMTime() – Datum der letzten Modifikation
  • getPath() – Der Pfad zu einer Datei
  • getRealPath() – Der absolute Pfad zu einer Datei
  • getSize() – Dateigröße
  • isDir() – Gibt True zurück, wenn der Name ein Verzeichnis ist
  • isDot() – Gibt True zurück, wenn „.“ oder „..“ gefunden wird
  • isFile() – Ist True, wenn es sich um eine Datei handelt
  • isReadable() – Ist True, wenn eine Datei lesbar ist
  • isWritable() – Ist True, wenn eine Datei schreibbar ist

Die DirectoryIterator Klasse umfasst etwa dreimal soviele Methoden wie die hier gezeigten, aber das sind meiner Meinung nach die wichtigsten.

Das Auslesen eines Verzeichnis ist sehr einfach und mit wenigen Zeilen Code erledigt.

$verzeichnis = new DirectoryIterator( "testdateien/" );
 
while ($verzeichnis->valid())
{
    echo $verzeichnis->current(). " (" .$verzeichnis->getSize(). ")<br />";
    $verzeichnis->next();
}

Wir erzeugen ein Objekt von der DirectoyIterator Klasse und übergeben dabei den Verzeichnisname dessen Inhalt wir ausgeben lassen möchten. Die Struktur der while()-Schleife sollte nun bekannt sein. Zusätzlich lassen wir uns noch zu jeder Datei die Größe ausgeben, was für uns die Methode getSize() der DirectoyIterator Klasse übernimmt. Das Ergebnis sieht allerdings nicht sonderlich ansprechend aus, wie man im folgenden Listing sieht:

. (0)
.. (0)
bilder (0)
controller.php (4901)
includes (0)
m3_saz.jpg (72517)
mixed (0)
whois.php (21563)
whois.zip (3718)

Da sind Dateien mit Ordnern vermischt und auch „.“ „..“ sehen nicht sonderlich gut aus. Wir bekommen ausserdem nur die Dateien aus dem angegebenen Ordner, was meistens wenig nützlich ist. Um herauszufinden was in den anderen Ordnern steckt müssen wir anfangen eine umständliche Funktion zu schreiben, die uns rekursive durch die Verzeichnisse navigieren läßt. Für diesen Zweck gibt es ja auch die nötigen Boardmittel wie isDot() oder auch isDir().
Aber muß es wirklich so kompliziert werden oder kommt uns da vielleicht wieder die SPL zu Hilfe? In der Tat wurde auch daran gedacht und die SPL stellt für das rekursive arbeiten die entsprechenden Klassen zur Verfügung. Um es genau zu sagen bietet die SPL zu jedem Iterator-Typ auch eine Recursive-Version der Klasse an. Wie praktisch das ist zeigt das nächste Beispiel…

Verzeichnisse rekursive auslesen mit der RecursiveDirectoryIterator Klasse

Die RecursiveDirectoryIterator Klasse bringt im Vergleich zur DirectoryIterator Klasse nur 2 neue Methoden mit, aber die sind überaus nützlich. Diese beiden Methoden sind:

  • hasChildren() – Liefert True, wenn ein „Kind“ (Unterverzeichnis) vorhanden ist
  • getChildren() – Liefert den Namen vom „Kind“ (Unterverzeichnis)

Mit diesen beiden zusätzlichen Methoden können wir jetzt wirklich ganz bequem eine sehr übersichtliche Funktion erstellen, um rekursive durch Verzeichnisse zu navigieren. Here we go…

function VerzeichnisBaum( RecursiveDirectoryIterator $zeiger )
{
    echo '<ul>';
    for ($zeiger->rewind(); $zeiger->valid(); $zeiger->next())
    {
        if ($zeiger->isDir() && !$zeiger->isDot())
        {
            echo '<li><span class="hinweis">' .$zeiger->getFilename(). '</span></li>';
            if ($zeiger->hasChildren())
            {
                $unterverzeichnis = $zeiger->getChildren();
                echo '<ul>' . VerzeichnisBaum( $unterverzeichnis ) . '</ul>';
            }
        } elseif ($zeiger->isFile())
        {
            echo '<li><em>'. $zeiger->getFilename() . '</em></li>';
        }
    }
    echo '</ul>'; 
}
 
VerzeichnisBaum( new RecursiveDirectoryIterator( 'testdateien/' ) );

Die Basisversion dieser Funktion habe ich aus dem Buch „PHP 5 – Grundlagen und Profiwissen von Jörg Krause“. Allerdings habe ich die Funktion etwas abgewandelt um sie verständlicher zu machen.
Die Funktionsweise ist einfach und weitestgehend selbsterklärend. Beim Funktionsaufruf (ganz unten) wird das Objekt der RecursiveDirectoryIterator Klasse übergeben. Der Klasse selbst geben wir nur den Pfad und Verzeichnisname mit den wir durchlaufen möchten.
Wie in der Funktion zu sehen ist prüfen wir ob der Zeiger auf einem Verzeichnis steht ($zeiger->isDir()) und geben den Element-Name ($zeiger->getFilename()) fett aus. Anschließend wird geprüft (mit $zeiger->hasChildren()) ob das Element (Verzeichnis) ein Kind (Unterverzeichnis) hat – trifft das zu, lesen wir den Namen des Kindes aus ($zeiger->getChildren()) und übergeben diesen Namen an die Funktion, die sich selbst mit dem Unterverzeichnisname aufruft. Am besten zwei oder drei mal diese Funktion durchlesen und nachvollziehen um das Prinzip zu verinnerlichen. Viel einfacher kann man Verzeichnisse nicht rekursive auslesen, oder?! Hmm, oder vielleicht doch!?!

Zip-Dateien on-the-fly erstellen

Bevor wir mit den Iterator-Klassen weiter machen, wollen wir einen kurzen Abstecher zu einer anderen, sehr nützlichen, neuen Klasse in PHP 5 machen: ZipArchive

Mit der ZipArchive Klasse kann man auf eine wirklich sehr einfache Art und Weise Zip Dateien erstellen, verändern und auch wieder entpacken. Das erforderte früher mehr oder weniger komplizierte externe Klassen mit sehr viel Code, in denen nur die wenigsten verstanden was da eigentlich vor sich geht. Das ändert sich grundlegend mit PHP 5. Die Methoden dieser Klasse sind sehr intuitiv und einfach zu verstehen; hier ein Überblick der wichtigsten Methoden:

  • addEmptyDir() – Fügt einen leeren Ordner hinzu
  • addFile() – Fügt eine Datei hinzu
  • addFromString() – Fügt einen Datei aus Text hinzu
  • close() – Schließt eine offene Zip-Datei
  • extractTo() – Entpackt ein Archiv an eine bestimmte Position
  • open() – Öffnet eine Zip-Datei, bzw legt eine neue Zip-Datei an

Das ist nur ein kleiner Teil der Methoden der ZipArchive Klasse. Desweiteren stehen Methoden zur Verfügung um Kommentare anzufügen/auszulesen, Index und Namen zu verändern, usw. Schauen wir uns ein sehr einfaches Beispiel an, wie man mit sehr wenigen Zeilen Code eine Zip-Datei erstellen und auch wieder entpacken kann. Das kann sehr nützlich sein um Backups zu erstellen und wieder herzustellen.

Das Listing um eine neue Zip-Datei zu erstellen sieht wie folgt aus:

$zip = new ZipArchive;
$resource = $zip->open( 'testarchiv.zip', ZipArchive::CREATE );
if ($resource === TRUE)
{
    $zip->addFromString( 'test.txt', 'Hier steht ein beliebiger Text.' );
    $zip->addFile( 'testdateien/controller.php', 'config/controller.php' );
    $zip->addFile( 'testdateien/m3_saz.jpg', 'bilder/blindschleiche.jpg' );
    $zip->addFile( 'testdateien/whois.php', 'whois.php' );
    $zip->addFile( 'testdateien/whois.zip', 'whois.zip' );
    $zip->close();
}

In Zeile 1 erzeugen wir eine neue Instanz der ZipArchive Klasse. Anschliessend erstellen wir mit der Zeile

$resource = $zip->open( 'testarchiv.zip', ZipArchive::CREATE );

eine neue Zip-Datei und merken uns den Status (True oder False) von dem Vorgang. Wichtig hier ist der 2. Parameter in $zip->open(). Durch den Zusatz ZipArchive::CREATE teilen wir der Klasse mit, daß wir eine neue Zip-Datei erstellen möchten. Ohne diesen Zusatz würde die Klasse versuchen die Datei testarchiv.zip zu öffnen, was unweigerlich in einem Fehler endet, da diese Datei nicht existiert. Möchten wir hingegen eine bereits existierende Zip-Datei öffnen, weil wir z.B. Datei hinzufügen wollen, muß der Zusatz weggelassen werden, da sonst die Zip-Datei überschrieben wird!

Nachdem geprüft wurde ob die Datei erfolgreich angelegt wurde, fügen wird Dateien zu unserem Zip-Archiv hinzu. Als ersten benutzen wir $zip->addFromString() um eine Datei aus einer Variablen anzufügen. Wie wir sehen bestimmen wir als ersten Parameter den Dateinamen in der Zip-Datei und als 2. Parameter übergeben wir den String, bzw die Variable die den String beinhaltet.
Mit der Methode $zip->addFile() erweitern wir unser Zip-Archiv um weitere Dateien. Dabei stellt der erste Parameter die Quelldatei (ggfs mit Pfad) auf dem Server dar, während der 2. Parameter der Pfad und Dateiname in dem Archiv darstellt. Wie wir sehen können wir Pfad (Ordner-Struktur im Archiv) und Dateiname frei vergeben und müssen uns nicht an die Struktur auf dem Server halten. Sind alle Dateien hinzugefügt, wird das Archiv mit $zip->close() wieder geschlossen und unsere Zip-Datei ist fertig. Um Problemen mit Safe_Mode aus dem Weg zu gehen empfiehlt es sich, unmittelbar nach dem close() die Dateirechte mit CHMOD anzupassen.

Das entpacken einer Zip-Datei ist genauso einfach:

$zip = new ZipArchive;
if ($zip->open( 'testarchiv.zip' ) === TRUE)
{
    $zip->extractTo( 'testdateien/' );
    $zip->close();
}

Als erstes wieder ein neues Objekt von der ZipArchive Klasse erzeugen. In der if() wird geprüft ob das öffnen ($zip->open()) der Zip-Datei erfolgreich war. (Kein 2. Parameter wie beim erstellen!) Dann entpackt man den Inhalt aus der testarchiv.zip mithilfe der Methode $zip->extractTo() in den Zielordner, was in unserem Fall testdateien ist. Die geöffnete Datei mit $zip->close() schliessen und das war’s! Kinderleicht, nicht wahr?!

Jetzt wo wir gesehen haben wie einfach es ist Zip-Dateien on-the-fly zu erstellen und wieder zu entpacken, kombinieren wir das mal mit den Fähigkeiten unseren DirectoryIterator Klassen.

Mal eine kleine Zwischenfrage: Was glaubst Du wie viele Zeilen Code wir brauchen um uns mithilfe der neuen ZipArchive Klasse und den Iterator Klassen der SPL ein Backup Script zu schreiben das uns ein Datei-Backup von unserer -sagen wir mal- 6-fach verschachtelte Verzeichnisstruktur erstellt? 25? 35? 50? mehr??

Fast, es sind ziemlich genau 7 Zeilen!

Die Kernfunktion unseres Datei-Backup Scripts

Ok zugegeben, das mit den 7 Zeilen war nicht leicht zu erraten, weil dazu eine weitere Iterator Klasse erforderlich ist die wir uns noch gar nicht angeschaut haben. Ebenso wurde auch noch nicht erwähnt das man Objekte einer Iterator Klasse beim Instanziieren einer anderen Iterator Klasse an diese übergeben kann, was den Umgang mit den Klassen wesentlich erleichtert und Schreibarbeit spart.

Das Listing des Backup Scripts zum erzeugen einer Zip Datei einer verschachtelten Verzeichnisstruktur:

$verzeichnis =  new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator( 'testdateien/' ), true );
$zip = new ZipArchive;
$resource = $zip->open( 'backup.zip', ZipArchive::CREATE );
if ($resource === TRUE)
{
    foreach ( $verzeichnis as $datei )
    {
        $zip->addFile( $datei, $datei );
    }
}
$zip->close();

Im Prinzip ist das ganze Listing weitestgehend identisch mit dem Zip-Archiv Beispiel von eben. Einzig die erste Zeile ist etwas anders im Vergleich zu den vorherigen Beispielen, aber hier geschieht die eigentliche Arbeit. Aufgrund der Länge der Zeile habe ich sie umgebrochen um sie übersichtlicher zu machen. Werfen wir mal einen genaueren Blick darauf…

$verzeichnis =  new RecursiveIteratorIterator( new RecursiveDirectoryIterator( 'testdateien/' ), true );

Die innere Instanziierung der RecursiveDirectoryIterator Klasse ist nicht neu, das hatten wir weiter oben schon unter die Lupe genommen. Was hier jetzt aber neu ist, ist die Klasse RecursiveIteratorIterator der, bei dessen Instanziierung, das Object der RecursiveDirectoryIterator Klasse übergeben wird.
Wie wir oben gelernt haben, hat die RecursiveDirectoryIterator Klasse die Methoden hasChildren() und getChildren(), womit sich ermitteln lässt ob ein Verzeichnis weitere Unterverzeichnisse hat. Hier setzt die RecursiveIteratorIterator Klasse an, denn ganz offensichtlich erkennt diese Klasse selbstständig ob es Kinder gibt und beginnt ggfs die rekursive Iteration rekursive zu iterieren. (Geiler Satz, gelle?! :D) Hier ist eine kurze Übersicht der Methoden die dafür verantwortlich sind:

  • beginChildren()
  • beginIteration()
  • callGetChildren()
  • callHasChildren()
  • endChildren()
  • endIteration()
  • getDepth()
  • getInnerIterator()
  • getSubIterator()
  • nextElement()

Das True beim Aufruf könnte man in diesem Fall auch weg lassen, da es für das erstellen der Zip-Datei nicht erforderlich ist. Wird beim Aufruf True angegeben, wird auch der Name des Kindes (Unterverzeichnis) ausgegeben. Das kann nützlich und sinnvoll sein wenn z.B. eine Ordnerstruktur auf dem Screen ausgeben werden soll.

Schon beeindruckend wie einfach man so ein kleines Backup Script schreiben kann. Allerdings ist die Arbeitsweise doch etwas unbefriedigend. Das Script würde nämlich gnadenlos alles in die Zip-Datei packen was ihr auf dem Weg bei der rekursiven Iteration über den Weg läuft. Wirklich sinnvolle Backups kann man aber eigentlich nur dann machen, wenn wir bestimmte Dateien ausklammern könnten (z.B. htaccess Dateien, Log-Files, usw.) oder nur bestimmte Dateien in’s Backup aufnehmen.
Wenn wir eins bis hierhin gelernt haben dann ist es, daß Verlaß auf die SPL ist. Wie nicht anders zu erwarten gibt es nämlich auch hier eine vorgefertigte Lösung und die heisst…

Inhalte filtern mit der FilterIterator Klasse

Die FilterIterator Klasse kommt verhältnismässig bescheiden daher, denn neben den 5 Standard Iterator Methoden verfügt diese Klasse nur über einen Konstruktor und diese Methoden:

  • getInnerIterator() – Ermittelt an welcher Position der Zeiger im inneren Iterator steht
  • accept() – Nimmt eine Bedingung auf um zu entscheiden ob das gegenwärtige Element (current()) gefiltert werden soll

Nichts desto Trotz ist der Einfluß dieser Klasse gewaltig, denn mit ihr können wir genau festlegen was gefiltert wird und was nicht. Um eine (Filter)Bedingung an die FilterIterator Klasse zu übergeben, müssen wir eine eigene Klasse schreiben und die FilterIterator Klasse erweitern.

Das Listing der FilterIterator Klasse Erweiterung:

class DateiFilter extends FilterIterator
{
    private $erweiterung;
    private $zeiger;
 
    public function __construct( $iterator, $erweiterung )
    {
        parent::__construct( $iterator );
        $this->zeiger = $iterator;
        $this->erweiterung = $erweiterung;
    }
 
    public function accept()
    {
        if (!$this->zeiger->isDir())
        {
            if (gettype( $this->erweiterung ) == "array")
            {
                $ArrayObjekt = new ArrayObject( $this->erweiterung );
                $zeiger = $ArrayObjekt->getIterator();
                $zeiger->rewind();
                foreach ($zeiger as $schluessel => $wert)
                {
                     $erweiterung = array_reverse( explode( '.', $this->current() ) );
                     if ($erweiterung[0] == $wert) $erweiterungen[] = $this->erweiterung;
                }
                return $erweiterungen;
            }
            else
            {
                $erweiterung = array_reverse( explode( '.', $this->current() ) );
                return $erweiterung[0] == $this->erweiterung;
            }
        }
        return true;
    }
}

Da für diesen Artikel ein gutes Verständnis der OOP vorausgesetzt wird sollten hier eigentlich keine großen Erklärungen nötig sein. Der Konstruktor erwartet zwei Parameter: 1) die zu iterierende Resource und 2) die erlaubte Datei-Endung(en).
Die Methode accept() in unserer Klasse wurde nicht willkürlich so genannt, sondern muß so heissen, damit wir die Methode accept() der FilterIterator Klasse überladen können.

Um den neuen Filter in Aktion zu sehen rufen wir unsere Klasse mit folgendem Code auf:

$verzeichnis =  new DateiFilter(
                new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator( 'testdateien/' ) ), 'jpg' );
 
foreach ($verzeichnis as $datei)
{
    echo $datei. "<br />\n";
}

Um mehrere Dateiendungen aufzulisten übergeben wir beim Aufruf ein Array:

$verzeichnis =  new DateiFilter(
                new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator( 'testdateien/' ) ), array( 'jpg', 'html' ));
 
foreach ($verzeichnis as $datei)
{
    echo $datei. "<br />\n";
}

Die inneren Instanziierungen kennen wir vom letzten Beispiel. Das so erzeugte Objekt übergeben wir, zusammen mit der erlaubten Datei-Endung (jpg), an unsere DateiFilter Klasse. Anschliessend geben wir in einer einfachen foreach() sämtliche gefundene Dateien aus die auf jpg enden.

Statt dem echo() in der foreach() könnten wir auch genauso gut, nachdem wir eine Zip-Datei geöffnet/angelegt haben, das $zip->addFile() dort stehen haben oder jede beliebige andere Aktion hier ausführen. Selbstverständlich könnten wir die Methode accept() auch erweitern, so daß z.B. mit $zeiger->getMTime() geprüft wird ob $zeiger->current() neuer oder älter als eine Datei im Zip-Archiv ist und auf diese Weise so etwas wie ein Inkrementelles Backup Script erzeugen.

Fazit und Schlußwort

Als erstes möchte ich im Schlußwort noch auf eine „Kleinigkeit“ hinweisen. Während des ganzen Artikels sprach ich aus Gründen der Einfachheit, um den Lernenden nicht zu verwirren, von der Iterator „Klasse“. In Wirklichkeit ist es aber ein Interface das in die anderen Iterator Klassen (wie z.B. DirectoryIterator) implementiert wird. Das ist dahingehend wichtig, als das man sich eigene Iterator Klassen erstellen kann und durch das implementieren des Iterator Interface auf die Iterations-Kernfunktionen zugreifen kann. Es ist also mehr als eine einfache Vererbung.

Jetzt aber wieder zurück zum Thema…
Die Möglichkeiten mit den Iterator Klassen sind schier unbegrenzt und jeder sollte sich etwas Zeit nehmen um ein wenig damit zu experimentieren. Die hier gezeigten Beispiele sind nur an der Oberfläche gekratzt, um einen generellen Einstieg etwas zu erleichtern. Viele werden die Notwendigkeit jetzt noch nicht so deutlich erkennen, aber Anfang nächsten Jahres läuft der Support für PHP 4 aus und immer mehr Provider werden nur noch PHP 5 anbieten, welches wesentlich objektorientierter arbeitet als seine Vorgänger. Das wird sich über kurz oder lang auch auf MySQL auswirken, wenn sich das objektorientierte MySQLi durchsetzen wird.
Ausserdem wird ein Code mithilfe der SPL übersichtlicher und einheitlicher, was es einem erleichtert sich in fremden Code zurecht zu finden. Jeder der schon mal fremde Scripts anpassen wollte wird das sicherlich kennen. Da kämpft man mehr damit sich durch die Logik und Struktur des fremden Code zu hangeln, statt sich auf das wesentliche zu konzentrieren. Die SPL versucht hier einen Standard in’s Spiel zu bringen, von dem wir alle profitieren könnten.

Dieser Artikel war sehr lang, deswegen möchte ich auf weitere Worte verzichten. Im Anschluß gibt es noch einige Tipps um der SPL wichtige Informationen zu entlocken.

Tipps zur SPL

Die erste Anlaufstelle um an Informationen zu kommen und um die Zusammenhänge der einzelnen Klassen zueinander zu verstehen ist die Webseite des Entwicklers.

Um festzustellen welche Klassen mit PHP installiert wurden einfach eine PHP Datei mit folgenden Inhalt auf dem Server ausführen.

echo "<pre>" .print_r( get_declared_classes(), true ). "</pre>";

Alle Methoden einer Klasse ermitteln:

echo "<pre>" .print_r( get_class_methods( "DirectoryIterator" ), true ). "</pre>";

Ich hoffe dieser Artikel konnte den ein oder anderen dazu ermutigen sich etwas mit dieser tollen Erweiterung von PHP 5 zu beschäftigen und wünsche Euch allen ein fröhliches iterieren 😉

Christof Servit Administrator
Inhaber und Entwickler bei servit.biz

Ich bin spezialisiert auf die Bereiche Webentwicklung, WordPress und .net Applikationen.
Dabei spielt es keine Rolle ob Frontend oder Backend (Full-Stack). Ich unterstütze meine Kunden bei der Enwicklung, Beratung und Einrichtung in allen Bereichen.

follow me
Entwickler gesucht? Ich kann Ihnen helfen.
Ich bin spezialisiert auf die Bereiche Webentwicklung, WordPress und .net Applikationen.
Dabei spielt es keine Rolle ob Frontend oder Backend (Full-Stack).
Jetzt kontaktieren

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Scroll to Top