14 Configuration
In der Kategorie »Configuration« ist zurzeit nur das Paket »Config« zu finden. Hierbei handelt es sich um ein sehr flexibles Werkzeug zum Erstellen, Einlesen oder Manipulieren von Konfigurationsdateien in unterschiedlichsten Formaten.
14.1 Config 

Besprochene Version: 1.10.4 | Lizenz: PHP-Lizenz |
Klassendatei(en): Config.php |
Das Paket Config kann mit verschiedenartigsten Formaten von Konfigurationsdateien umgehen. Als Formate können Sie XML-Dateien, Dateien, die aus PHP-Arrays bestehen, oder auch Dateien im Apache-Format nutzen.
Konfigurationsdateien bestehen aus Sektionen und Direktiven. Eine Sektion ist ein Abschnitt, der bestimmte Konfigurationsanweisungen zusammenfasst. So könnte eine Sektion sich beispielsweise auf ein Unterverzeichnis beziehen. Die Konfigurationsanweisungen, die in der Sektion vorhanden sind, beziehen sich dann alle auf das Verzeichnis. Diese Konfigurationsanweisungen bzw. -informationen werden auch als Direktiven bezeichnet. Eine Sektion kann auch noch weitere Sektionen beinhalten.
Um eine solche Struktur darzustellen, können nun verschiedene Möglichkeiten genutzt werden. Im Apache-Stil könnte eine solche Datei so aussehen:
Eine andere Variante, die Daten darzustellen, wäre der Typ »PHPArray«, bei dem die gleichen Daten so ausgegeben würden:
$data['conf']['db']['type'] = 'mysql'; $data['conf']['db']['host'] = 'localhost'; $data['conf']['auth']['user'] = 'root'; $data['conf']['auth']['pass'] = 'geheim';
Wie Sie erkennen können, sind die reinen Nutzdaten in beiden Fällen identisch, nur die Darstellung ist unterschiedlich. In der Sektion conf sind die beiden Sektionen db und auth vorhanden. Innerhalb von db sind dann zwei Direktiven enthalten, und auch auth besteht aus zwei Direktiven.
Möchten Sie eine neue Konfigurationsdatei erstellen, nutzen Sie den Konstruktor der Klasse Config_Container. Ein Objekt dieser Klasse kann sowohl eine Sektion ('section') als auch eine Direktive ('directive'), ein Kommentar ('comment') oder eine Leerzeile ('blank') sein. Die Strings in den Klammern sind hierbei jeweils der erste Parameter. Der zweite Parameter ist der Name, der für die Direktive oder den Abschnitt genutzt werden soll. Zusätzlich können Sie noch einen Inhalt und ein Array mit Attributen angeben. Hierbei muss es sich um ein assoziatives Array handeln, bei dem die Schlüssel als Attribut dienen und die Werte direkt übernommen werden. Allerdings machen die letzten beiden Parameter nicht bei allen Dateitypen Sinn.
Um eine neue Konfiguration zu erstellen, wird üblicherweise eine neue Sektion erstellt, unter der dann neue Elemente eingefügt werden können. Das Objekt, das der Konstruktor zurückgibt, kann zum Hinzufügen neuer Elemente genutzt werden.
Zum Hinzufügen von Einträgen sind verschiedene Methoden vorgesehen. Jede dieser Methoden gibt ein Objekt zurück, über das Sie den Eintrag später ansprechen können.
Um eine weitere Sektion einzufügen, können Sie die Methode createSection() nutzen. Sie bekommt als ersten Parameter den Namen der neu zu erstellenden Sektion übergeben. Danach können Sie optional Attribute in Form eines Arrays übergeben. Mit dem nächsten Parameter können Sie beeinflussen, wo die neue Sektion eingefügt wird. Üblicherweise ist die Standardeinstellung hier 'bottom', so dass die neue Sektion immer unter allen anderen Einträgen in dieser Sektion eingefügt wird. Übergeben Sie hier 'top', wird die Sektion vor allen anderen Einträgen eingefügt. Sie können allerdings auch spezifizieren, vor oder nach welchem Eintrag die neue Sektion eingefügt wird. Geben Sie hierzu 'before' oder 'after' an. In diesem Fall benötigt die Methode danach aber noch die Information, um welches Objekt es sich handelt. Geben Sie dazu als nächsten Parameter das Objekt an, das Sie beim Einfügen des entsprechenden Eintrags erhalten haben.
Eine Direktive können Sie mit der Methode createDirective() einfügen. Sie bekommt als ersten Parameter den Namen der Direktive übergeben, worauf der Wert in Form eines Strings folgt. An dritter Stelle können Sie auch hier wieder ein Array mit Attributen übergeben. Mit den nächsten beiden Parametern haben Sie wiederum die Möglichkeit, die Position der Direktive festzulegen. Die zulässigen Parameter sind hierbei identisch mit denen von createSection().
Zusätzlich können Sie noch mit den Methoden createComment() einen Kommentar und mit createBlank() eine Leerzeile einfügen. createComment() akzeptiert als ersten Wert den Kommentar, der eingefügt werden soll. Danach können Sie wiederum mit zwei Parametern die Einfügeposition bestimmen. Diese beiden Parameter sind auch die einzigen, die createBlank() akzeptiert.
require_once('Config.php'); // Neue Section anlegen $conf = new Config_Container('section', 'conf'); // Section fuer Datenbank-Konfiguration hinzufuegen $conf_db = $conf->createSection('db'); $conf_db->createDirective('host', 'localhost'); // Kommentar am Anfang der Section einfuegen $conf_db->createComment ('Datenbank-Konfiguration','top'); // Section fuer Auhthentifizierungsdaten $conf_auth = $conf->createSection('auth'); $conf_auth->createDirective('user', 'root'); $conf->createBlank('before',$conf_auth); // Neues Config-Objekt zum Speichern der Daten $config = new Config(); // Root-Element festlegen $config->setRoot($conf); // Konfiguration in Datei schreiben $res=$config->writeConfig('db.cfg', 'phparray'); if (true===PEAR::isError($res)) { die ($res->getMessage()); }
Listing 14.1 Schreiben einer Konfiguration
Das Script aus Listing 14.1 erzeugt eine Datei mit folgendem Inhalt:
<?php // Datenbank-Konfiguration $conf['conf']['db']['host'] = 'localhost'; $conf['conf']['auth']['user'] = 'root'; ?>
Wie Sie in Listing 14.1 sehen können, wird für die Ausgabe der Konfiguration ein Objekt der Klasse Config benötigt. Mit der Methode setRoot() wird definiert, welches die Sektion sein soll, die die anderen Daten beinhaltet. Um die Datei zu schreiben, ist die Methode writeConfig() vorgesehen, die als ersten Parameter den Dateinamen übergeben bekommt. Der zweite Parameter ist der Typ der Konfigurationsdatei, der genutzt werden soll, also in diesem Fall 'phparray'.
Des Weiteren stehen die folgenden Typen zur Verfügung:
Sie sehen, dass die Typen IniFile und PHPCostants ein wenig aus der Reihe tanzen, da hier die Verschachtelung aufgebrochen wird. In beiden Fällen wird keine Verschachtelung unterstützt. Bei IniFile ist die Sektion [conf] somit leer, und bei den beiden anderen Sektionen handelt es sich um Top-Level-Sektionen. Des Weiteren ist der Typ zurzeit noch nicht korrekt im Paket angemeldet. Somit muss vor Aufruf der Methode writeConfig() die folgende Zeile ausgeführt werden:
Damit wird der entsprechende Typ beim System angemeldet.
Auch wenn die Daten in den meisten Fällen als Datei ausgegeben werden, ist es möglich, die kompletten Daten als String oder als Array auszulesen. Die Methoden toString() bzw. toArray() bekommen hierzu den gewünschten Ausgabetyp als Parameter übergeben. toString() gibt die komplette Datei als String zurück, der exakt so formatiert ist, wie das der Fall wäre, wenn die Daten als Datei geschrieben würden. toArray() liefert ein verschachteltes, assoziatives Array zurück, das die Struktur der Daten abbildet. Die Namen der Sektionen bzw. Direktiven fungieren dabei als Schlüssel und verweisen dann jeweils auf ein neues Array oder auf einen Datensatz.
Das Paket kann aber nicht nur Daten schreiben, sondern auch bestehende Konfigurationsdateien einlesen und verarbeiten. Hierbei ist es möglich, die oben erläuterten Typen zu nutzen. Zusätzlich ist noch ein weiterer Typ namens 'IniCommented' vorgesehen. Dieser kann Dateien im ini-Format einlesen, ohne dass Kommentare verloren gehen, was bei 'IniFile' der Fall wäre.
Um eine bestehende Datei einzulesen, benötigen Sie ein Config-Objekt, aus dem heraus die Methode parseConfig() aufgerufen wird. Diese bekommt als ersten Parameter den Dateinamen und als zweiten den Typ übergeben, der verarbeitet werden soll. Sie gibt ein Config_Container-Objekt zurück, das auf das Root-Objekt verweist.
Der einfachste Fall in diesem Zusammenhang wäre, dass Sie die Datei nur von einem Format in ein anderes konvertieren wollen:
require_once('Config.php'); $config = new Config(); // Einlesen der Daten im Format IniFile $root = $config->parseConfig('db.cfg','IniFile'); if (true===PEAR::isError($root)) { die ($root->getMessage); } // Ausgabe als PHPArray $res=$config->writeConfig('db2.cfg', 'PHPArray'); if (true===PEAR::isError($res)) { die ($res->getMessage()); }
Listing 14.2 Konvertierung einer Datei mit Config
Sie können eine Datei allerdings auch komplett analysieren und verändern. Wie schon erwähnt wurde, liefert die Methode nach dem Einlesen das Root-Objekt zurück. Da eine Konfigurationsdatei nicht immer ein eindeutiges Wurzel-Element haben muss, hat man hier einen kleinen Trick angewandt. Und zwar wird ein virtuelles Wurzel-Objekt genutzt, das nicht Bestandteil der Datei ist. Unter ihm werden dann die einzelnen Inhalte der Datei in einem Baum verwaltet.
Dieser Baum ist je nach Dateityp unterschiedlich aufgebaut. Lesen Sie eine XML-Datei ein, ist ein eindeutiges Root-Element in der Datei enthalten, und das virtuelle Root-Element verweist auf exakt einen Nachfolger. Wäre die gleiche Datei in Form einer ini-Datei abgespeichert worden, würde jede Direktive und jede Sektion als Kind des virtuellen Root-Elements behandelt. Somit ist es ein wenig aufwändiger, ein allgemein gültiges Script zu erstellen, das alle Arten von Dateien analysieren kann.
Um herauszufinden, wie viele Elemente unterhalb des Root-Elements oder eines anderen Elements vorhanden sind, können Sie die Methode countChildren() aufrufen. Haben Sie die Anzahl der Elemente ermittelt, können Sie die untergeordneten Elemente abarbeiten. Um ein untergeordnetes Element, also ein Kind, zu erhalten, nutzen Sie die Methode getChild(). Übergeben Sie Ihr keine Zahl, liefert sie Ihnen das erste Kind-Element zurück. Übergeben Sie Ihr die Zahl 1 als Parameter, erhalten Sie das zweite Kind und so weiter.
$root = $config->parseConfig('db.cfg','IniFile'); $anzahl_kinder= $root->countChildren(); for {$cnt = 0; $cnt < $anzahl_kinder; $cnt+=1) { $kind[$cnt]= $root->getChild($cnt); }
Haben Sie ein Kind-Element ausgelesen, stellt sich die Frage, um was für eine Art Element es sich handelt. Das teilt die Methode getType() Ihnen mit. Wenn es sich um eine Sektion oder eine Direktive handelt, können Sie den Namen des Objekts mit getName() auslesen. Sollte es sich um eine Direktive handeln, so können Sie den Wert mithilfe von getContent() in Erfahrung bringen. Verfügt das Element über ein Attribut, können Sie auch dieses auslesen. Um den Wert eines bestimmten Attributs auszulesen, können Sie aus dem fraglichen Objekt heraus die Methode getAttribute() aufrufen, die den Namen des gewünschten Attributs übergeben bekommt. Um alle Attribute auf einmal auszulesen, rufen Sie getAttributes() auf.
Sie können die Werte und Attribute der gefundenen Elemente auch ändern. Der eigentliche Wert einer Direktive kann mit setContent() überschrieben werden. Um Attribute neu zu setzen, ist die Methode setAttributes() deklariert.
Natürlich können Sie auch Elemente entfernen, was die Methode removeItem() erledigt.
Das folgende Beispiel verarbeitet diese Datei:
<?xml version="1.0" encoding="ISO-8859–1"?> <conf> <!-- Datenbank-Konfiguration --> <host>localhost</host> <auth> <user>root</user> <pass>geheim</pass> </auth> </conf>
Listing 14.3 XML-Datei für Listing 14.4
In dieser Datei sollen die Werte der Direktiven host und user durch XXX ersetzt werden. Die Direktive pass soll komplett entfernt werden. Da es sich hierbei um eine XML-Datei handelt, sind die Sektionen ineinander verschachtelt. Um korrekt in den Baum hinabsteigen zu können, arbeitet das Script den Baum mithilfe einer rekursiven Funktion ab. Diese wird mit dem Wurzel-Element aufgerufen und bekommt zwei Arrays übergeben. Im ersten Arrray sind die Namen der Direktiven enthalten, bei denen die Werte überschrieben werden sollen. Das zweite Array enthält die Namen der Elemente, die komplett gelöscht werden sollen. Findet die Funktion eine weitere Sektion, ruft sie sich selbst wieder auf und übergibt mit dem neuen Aufruf die gefundene Sektion als Wurzel-Element.
require_once('Config.php'); function rec_tree ($root, $remove, $delete) { // Wie viele Kinder hat die uebergebene Wurzel? $anz_kinder=$root->countChildren(); // Jedes Kind einzeln abarbeiten for ($cnt = 0; $cnt < $anz_kinder; $cnt+=1) { $child = $root->getChild($cnt); // Ist das Kind eine neue Sektion? if ('section' == $child->getType()) { // Funktion ruft sich rekursiv auf rec_tree($child,$remove,$delete); } elseif (true == in_array($child->getName(),$remove)) { // Hier wird der Content ersetzt $child->setContent('XXX'); } elseif (true == in_array($child->getName(),$delete)) { // Hier wird das Element ganz entfernt $child->removeItem(); } } } $config = new Config(); // Konfigurationsdatei einlesen $root = $config->parseConfig('db_.cfg','xml'); if (true === PEAR::isError($root)) { die ($root->getMessage()); } // "Echte" Wurzel ermitteln $conf_root=$root->getChild(0); // Funktion aufrufen rec_tree($conf_root,array('host','user'),array('pass')); // Wurzel wieder zum Schreiben an Config-Objekt uebergeben $config->setRoot($conf_root); $res=$config->writeConfig('db_neu.cfg', 'xml'); if (true===PEAR::isError($res)) { die ($res->getMessage()); }
Listing 14.4 Manipulation einer Konfigurationsdatei mit Config
Das Ergebnis von Listing 14.4 sieht so aus:
<?xml version="1.0" encoding="ISO-8859–1"?> <conf> <host>XXX</host> <auth> <user>XXX</user> </auth> </conf>
Die Nutzung einer for-Schleife wäre nicht unbedingt nötig gewesen. Sie können es auch ausnutzen, dass getChild() als Ergebnis false liefert, wenn ein Kind-Element nicht gefunden werden kann. Somit könnte die for-Schleife beispielsweise durch eine while-Schleife ersetzt werden:
Bei ini-Dateien, bei denen die Elemente nicht mehrfach verschachtelt sind, wäre es recht aufwändig, die einzelnen Elemente alle auszulesen, um sie zu verändern. In so einem Fall sind directiveContent() und setDirective() hilfreich. directiveContent() kann den Wert mehrerer Direktiven auf einmal auslesen und bekommt ein Array mit den Namen der fraglichen Direktiven übergeben. Die Werte werden dann als Array zurückgegeben, oder es wird false zurückgegeben, wenn die Direktiven nicht vorhanden sind.
Um eine Direktive vom Root-Element aus mit einem neuen Wert zu belegen, können Sie setDirective() nutzen. Sie bekommt den Namen der Direktive und den dazugehörigen Wert als Parameter übergeben. Existiert die Direktive nicht, so wird sie neu angelegt und die Methode gibt das entsprechende Objekt zurück. Existieren mehrere gleichnamige Direktiven, können Sie als dritten Parameter noch eine Zahl übergeben, die definiert, auf welche Direktive Sie sich beziehen. 0 steht für das erste Auftreten, 1 für das zweite und so weiter.