Die Standard PHP Library (SPL)

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:

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.

Abbildung 3.2. FilterIterator, LimitIterator und SeekableIterator

FilterIterator, LimitIterator und SeekableIterator


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.