Die Standard PHP Library (SPL) ist fester Bestandteil von PHP 5 und baut auf den Iterator-Schnittstellen auf. Sie tritt an, ein PHP-Pendant zu der von C++ bekannten Standard Template Library (STL) zu werden.
In PHP 5.0 umfasst die Standard PHP Library die folgenden Klassen und Schnittstellen:
FilterIterator, LimitIterator und
SeekableIterator bieten Funktionen für das Filtern,
Einschränken und Suchen von Elementen einer Menge. Sie bilden den
Kern der Standard PHP Library.
ArrayObject und ArrayIterator bieten eine
alternative Möglichkeit für die Arbeit mit Arrays an.
RecursiveIterator, RecursiveIteratorIterator
und ParentIterator stellen die Funktionalität zur Verfügung,
um rekursiv mit verschachtelten Iteratoren arbeiten zu können.
RecursiveIterator ist hierbei eine Schnittstelle, die
von einer Iterator-Klasse implementiert werden muss, wenn ihre Objekte
von einem RecursiveIteratorIterator rekursiv verarbeitet
werden sollen. Ein Beispiel für eine Klasse, die
RecursiveIterator implementiert, ist
SimpleXMLIterator (siehe Kapitel 8).
DirectoryIterator und RecursiveDirectoryIterator
bieten eine einfache und effiziente Möglichkeit, über die Dateien und
Unterverzeichnisse eines Verzeichnisses im Dateisystem zu iterieren.
CachingIterator und CachingRecursiveIterator
[8]
erweitern das Standardverhalten von Iterator-Implementierungen,
indem sie immer ein Element im Voraus lesen.
Betrachten wir einmal die Aufgabe, die Elemente einer Menge gefiltert zu verarbeiten. Mit Hilfe eines Iterators sind wir bereits in der Lage, die Elemente einer beliebigen Menge zu durchlaufen. Eine einfache Möglichkeit, aus einer Menge von Teilstrings nur diejenigen auszugeben, die mit "Bar" beginnen, sehen wir in Beispiel 3.9.
Beispiel 3.9: Filtern einer Menge von Teilstrings
<?php
$string = new String('Foo Bar Barbara');
foreach ($string as $key => $value) {
if (strpos($value, 'Bar') === 0) {
print $value . "\n";
}
}
?>Bar Barbara
In Beispiel 3.9 wenden wir die Filterregel direkt in der Schleife an, mit der wir die Elemente der Menge durchlaufen. Dies wird jedoch zum einen bei komplexeren Filteroperationen schnell unübersichtlich, zum anderen stößt diese Methode an Grenzen, wenn es darum geht, Filterregeln dynamisch auszutauschen oder zu kombinieren.
Die Standard PHP Library bietet für dieses Problem die Möglichkeit an, Iterator-Objekte zu kombinieren. Hierbei kontrolliert ein äußerer Iterator einen inneren Iterator. Der innere Iterator arbeitet auf der eigentlichen Menge von Elementen, die verarbeitet werden soll. Der äußere Iterator entscheidet jedoch für jedes dieser Elemente, ob und in welcher Weise es an den Verwender zurückgegeben werden soll. Hinter diesem Konzept steht mit dem Dekorierer (siehe Kapitel 6) ein weiteres Entwurfsmuster, das wir später noch genauer behandeln werden.
Die abstrakte Klasse FilterIterator erlaubt das
Schreiben einer Klasse für die Verwendung als äußeren Iterator. Dieser
filtert die Elemente des inneren Iterators, der als Objekt dem
Konstruktor zu übergeben ist. Die Filterkriterien sind hierbei in der
Methode accept() zu implementieren. In dieser Methode
kann über $this->getInnerIterator()->current() auf das
aktuelle Element des inneren Iterators zugegriffen werden. Soll
dieses akzeptiert (also an den Verwender des äußeren Iterators
weitergereicht) werden, so muss die Methode TRUE als
Ergebnis liefern.
Beispiel 3.10
zeigt eine von FilterIterator abgeleitete Klasse, die
den aus Beispiel 3.9
bekannten Filter implementiert.
Beispiel 3.10: Eine FilterIterator-Implementierung
<?php
require_once 'StringIterator.php';
class StringFilterIterator extends FilterIterator {
private $prefix;
public function
__construct(StringIterator $stringIterator, $prefix) {
parent::__construct($stringIterator);
$this->prefix = $prefix;
}
public function accept() {
$current = $this->getInnerIterator()->current();
if (strpos($current, $this->prefix) === 0) {
return TRUE;
}
return FALSE;
}
}
$stringIterator = new StringIterator('Foo Bar Barbara');
$filterIterator = new StringFilterIterator(
$stringIterator,
'Bar'
);
foreach ($filterIterator as $key => $value) {
print "$value\n";
}
?>Bar Barbara
Ein Objekt der Klasse LimitIterator erlaubt das Limitieren der
Elemente des inneren Iterators, für den es als äußerer Iterator in Aktion
tritt. Der Konstruktor erwartet neben dem inneren Iterator zwei optionale
Parameter $offset und $count. Der Erste gibt
die Nummer des ersten Elements an (beginnend bei 0), das akzeptiert werden
soll. Der Zweite die maximale Anzahl an Elementen. In
Beispiel 3.11
werden so alle Elemente ab einschließlich des Dritten ausgegeben.
Beispiel 3.11: Verwendung der Klasse LimitIterator
<?php
require_once 'StringIterator.php';
$stringIterator = new StringIterator('Foo Bar Barbara');
$limitIterator = new LimitIterator($stringIterator, 2);
foreach ($limitIterator as $key => $value) {
print "$value\n";
}
?>Barbara
Der äußere Iterator eines inneren Iterators kann seinerseits als
innerer Iterator für einen weiteren Iterator dienen. In
Beispiel 3.12
ist der StringFilterIterator einerseits äußerer Iterator
für den StringIterator, andererseits aber auch innerer
Iterator für den LimitIterator.
Beispiel 3.12: Kombination von FilterIterator und LimitIterator
<?php
require_once 'StringFilterIterator.php';
$stringIterator = new StringIterator('Foo Bar Barbara');
$filterIterator = new StringFilterIterator($stringIterator, 'Bar');
$limitIterator = new LimitIterator($filterIterator, 1, 1);
foreach ($limitIterator as $key => $value) {
print $value . "\n";
}
?>Barbara
Im Standardfall nutzt ein LimitIterator-Objekt wiederholte
Aufrufe der Methode next(), die von der Schnittstelle
Iterator vereinbart wird, um den durch die Parameter
$offset und $count angegebenen Elementebereich
zu erreichen. Bei der Verarbeitung der Ergebniszeilen einer
Datenbankabfrage hat dieses Vorgehen jedoch einen Nachteil: Die
vor dem gewünschten Bereich auftretenden Ergebniszeilen werden
beispielsweise mit mysql_fetch_assoc() über die Verbindung
zum Datenbankserver in den Speicher des PHP-Interpreters geladen,
nur um gleich darauf unverarbeitet wieder überschrieben zu werden.
Sinnvoller wäre an dieser Stelle die Verwendung der Funktion
mysql_data_seek(), um direkt an die richtige Stelle
der Ergebnismenge zu springen.
Für Mengen, bei denen die Position auf ein bestimmtes Element direkt
gesetzt werden kann, bietet sich die Erweiterung der Klasse
LimitIterator durch eine Kindklasse an, die zusätzlich die
Schnittstelle SeekableIterator implementiert. Als einzige
Methode dieser Schnittstelle ist seek($position) zu
implementieren. Ein Objekt einer solchen Klasse kann nun effizient
an die gewünschte Position springen.
[8]
Passendere Namen wären LookAheadIterator und
LookAheadRecursiveIterator, da kein Caching im
eigentlichen Sinne durchgeführt wird.