Dienstag, 7. Februar 2012

entwickler.com Magazine Konferenzen Entwickler Akademie Entwickler-Forum Jobbörse Bücher
Software & Support Media





April 2006
aus PHP Magazin Ausgabe: 04.2004
Ein Colt für alle Fälle
Applikationskonfiguration in PHP
Von Stephan Schmidt

Sicher standen auch Sie schon einmal vor dem Problem, dass Sie eine Anwendung, die Sie in der Vergangenheit entwickelt hatten, für ein weiteres Projekt noch einmal einsetzen sollten. Anfangs dachten Sie, dass dies kein Problem darstellt, schließlich muss lediglich der Code von einem Projektverzeichnis ins andere kopiert werden. Doch während der Umsetzung ist Ihnen sicher aufgefallen, dass sich die beiden Projekte in einigen, wenn auch noch so kleinen, Parametern unterscheiden, und dass Sie z.B. die eMail-Adresse, an die Benachrichtigungen verschickt werden, per Suchen-und-Ersetzen-Funktion Ihres Editors in mehreren Dateien ändern müssen. Dieser Artikel wird Ihnen zeigen, wie Sie solche Arbeiten vermeiden können und stellt Ihnen einige Hilfsmittel zum Lesen und Bearbeiten von Konfigurationen vor.


Wiederverwendbarkeit von Code
Computer wurden erfunden, um die Arbeit zu erleichtern. Ihre Erfindung führte in vielen Fällen dazu, dass Menschen von Computern abgelöst wurden. Nun ist es in Ihrem Fall als Programmierer wohl eher so, dass Sie ohne Computer arbeitslos wären, aber trotzdem versuchen Sie ja, Ihre tägliche Arbeit durch den Computer zu vereinfachen, indem Sie z.B. einen Editor verwenden, der Code-Completion anbietet.

Das heißt auch, dass Sie versuchen sollten, den von Ihnen erstellten Code so zu gestalten, dass er Ihnen Arbeit abnimmt. Am einfachsten gelingt das, wenn Sie einen Code entwickeln, der häufig wiederkehrende Arbeiten, wie z.B. das Anzeigen und Validieren eines Formulars oder das Verschicken einer eMail automatisiert Aber auch wenn diese Aufgaben in fast jedem Ihrer Projekte auf Sie warten, so sind sie doch nie zu 100 Prozent identisch. Kein Formular gleicht dem anderen und auch die eMails gehen sicherlich an verschiedene Ansprechpartner. Die Logik hinter den Applikationen ist zwar dieselbe, jedoch unterscheiden sie sich in einigen Parametern. Es sollte also Ihr erklärtes Ziel sein, einen Code zu entwickeln, der von außen parametrisierbar oder konfigurierbar ist.

Modularer Aufbau
Um dies zu erreichen, sollten Sie schon beim Design Ihrer Applikation darauf achten, diese modular aufzubauen. Das bedeutet z.B., dass Sie Funktionen oder Klassen, die Sie häufig verwenden, in einzelne Dateien auslagern und diese dann bei Bedarf mittels require_once einbinden, anstatt den Code redundant in mehreren Dateien zu pflegen. Schreiben Sie z.B. an mehreren Stellen in Ihrer Applikation eine Log-Datei, so empfiehlt es sich, diese Logik in einer Klasse oder einer Funktion zu kapseln. Einfacher wird dies natürlich, wenn Sie direkt eine Klasse aus einer Code-Bibliothek wie z.B. PEAR [1] verwenden.

Konfiguration prozeduralen Codes
Nachdem Sie wiederkehrenden Code analysiert und extrahiert haben, müssen Sie diesen natürlich noch parametrisierbar machen. Wenn es sich um prozeduralen Code handelt, so ist der einfachste Weg, auf globale Variablen zurückzugreifen und die Deklaration dieser Variablen in einer weiteren Datei vorzunehmen, die dann einfach ausgetauscht werden kann. Listing 1 zeigt eine Funktion, die eine eMail verschickt. Dabei wird eigentlich nur die mail() -Funktion gekapselt, sodass der Empfänger der eMail nicht jedes Mal übergeben werden muss. Weiterhin wurde noch eine Variable definiert, deren Wert dem Betreff der eMail vorangestellt wird, wodurch Sie z.B. sehr einfach entscheiden können, ob die eMail vom Test-System oder Live-System verschickt wurde. Die Konfigurationsdatei, die mit require_once eingebunden wird, könnte z.B. folgendermaßen aussehen:

<?PHP
$empfaenger = 'webmaster@localhost';
$prefix = '[Testinstallation] ';
?>

Listing 1

<?PHP
function sendMail( $betreff, $body )
{
$empfaenger = $_GLOBALS['mail_empfaenger'];
$prefix = $_GLOBALS['mail_prefix'];

$betreff = $prefix . $betreff;

return mail( $empfaenger, $betreff, $body );
}

require_once 'config1.php';
sendMail( 'Test', 'Dies ist nur eine Test-Mail.' );
?>

Es geht auch besser
Auch wenn dieser Weg funktioniert, so ist es sicherlich keine sehr gute Lösung. Wenn Ihre Applikation wächst, steigt auch die Anzahl der Parameter, die Sie benötigen, und es können Probleme auftreten:
  • Da globale Variablen verwendet werden, kann es zu Namenskonflikten kommen.
  • Wenn die Konfiguration nicht von einem Programmierer geändert wird, kann es sehr schnell passieren, dass ein Laie einen Parse-Error einbaut (z.B. durch falsch eingesetzte Quotes), worauf Ihre Applikation nicht reagieren kann.
  • Auf die Variablen muss immer über $_GLOBALS zugegriffen werden.

Statt PHP-Dateien sollte also auf ein Format zurückgegriffen werden, das von Laien einfach interpretiert und geändert und das auch von PHP leicht gelesen werden kann. Hier bieten sich vor allem zwei Formate an: das bereits von Windows bekannte INI-Format und XML.

PHP bietet bereits die Funktion parse_ini_file() [2], mit der ganz einfach INI-Dateien eingelesen werden können. Eine INI-Datei hat einen sehr simplen Aufbau. In ihr kann einer Option immer genau ein Wert zugeordnet werden, und für die Zuordnung steht zwischen Option und Wert ein Gleichheitszeichen. Die Konfiguration aus dem obigen Beispiel sähe im INI-Format folgendermaßen aus:

empfaenger = "webmaster@localhost"
prefix = "[Testinstallation] "

Liest man diese Datei nun ein, indem man den Dateinamen als Parameter an parse_ini_file() übergibt, so bekommt man folgendes assoziatives Array zurück:

Array
(
[empfaenger] => webmaster@localhost
[prefix] => [Testinstallation]
)

Listing 2 zeigt, wie die Funktion zum Versenden einer eMail, basierend auf einer INI-Datei, funktioniert.


Listing 2:

<?PHP
function sendMail( $betreff, $body )
{
$config = parse_ini_file( 'config1.ini' );

$empfaenger = $config['mail_empfaenger'];
$prefix = $config['mail_prefix'];

$betreff = $prefix . $betreff;

return mail( $empfaenger, $betreff, $body );
}

sendMail( 'Test', 'Dies ist nur eine Test-Mail.' );
?>


Falls Sie jetzt schon die Dokumentation von parse_ini_file() studiert haben, ist Ihnen sicher aufgefallen, dass die Funktion noch einen zweiten Parameter unterstützt. Dieser wird benötigt, wenn Sie verschiedene Abschnitte (so genannte Sections) in Ihrer Konfigurationsdatei verwenden möchten. Nehmen Sie an, Sie möchten verschiedene Konfigurationen zum Versenden einer eMail anlegen. Dies könnte im INI-Format folgendermaßen aussehen:

[fehler]
empfaenger = "webmaster@localhost"
prefix = "[Testinstallation] "

[kontakt]
empfaenger = "kunde@test.de"
prefix = "[Kontakt] "

Wenn Sie nun beim Aufruf den zweiten Parameter auf true setzen, so sucht PHP in der Datei nach Sections und liefert Ihnen ein Array zurück, das für jede der Sections (fehler und kontakt) ein Array mit den Werten zurück liefert. Die Rückgabe sieht also folgendermaßen aus:

Array
(
[fehler] => Array
(
[empfaenger] => webmaster@localhost
[prefix] => [Testinstallation]
)
[kontakt] => Array
(
[empfaenger] => kunde@test.de
[prefix] => [Testinstallation]
)
)

Besondere Werte in INI-Dateien
Es ist möglich, in einer INI-Datei auch andere Werte als Strings zu definieren. Setzen Sie einen Wert z.B. auf true oder yes (ohne Anführungszeichen), so wird dieser automatisch in den Wert 1 konvertiert, false und no werden automatisch zu Leerstrings. Leider resultiert dies in einem Bug, da diese Werte nicht als Optionsnamen verwendet werden können. Sie sollten also nicht versuchen, no als Landeskürzel für Norwegen zu verwenden.


Listing 3:

<?PHP
$config = parse_ini_file( 'config2.ini', true );
echo '<pre>';
print_r( $config );
echo '</pre>';
?>

Sicherheitsaspekte
Da anzunehmen ist, dass Sie in Ihrer Konfigurationsdatei sensitive Daten wie Zugangspasswörter speichern, müssen Sie sicher sein, dass man die Dateien nicht über den Webbrowser abrufen kann. Der einfachste Weg ist natürlich, wenn Sie die Dateien außerhalb des Webroots ablegen, z.B. in /etc/myApp/config, sodass gar nicht erst die Möglichkeit zum Anfordern der Datei besteht.

Sollte Ihnen das nicht möglich sein, so hilft es auch, die Datei-Endung anzupassen. Für eine PHP-Konfigurationsdatei sollte immer die Endung .php gewählt werden, da der Webserver diese dann parst und der Benutzer eine leere Seite sieht. Für INI-Dateien ist dies leider nicht möglich, jedoch bietet die Apache-Konfiguration hier eine Möglichkeit, die Datei zu schützen. Legen Sie einfach in dem Verzeichnis, in das Sie Ihre Konfigurationsdateien gespeichert haben, eine Datei mit dem Namen .htaccess an und kopieren Sie folgenden Inhalt in diese Datei:

<Files *.ini>
Order deny,allow
Deny from all
</Files>

Der Webserver wird dann keine Dateien mit der Endung .ini mehr ausliefern und Ihre Konfigurationsoptionen sind sicher.

Hilfsmittel
Natürlich sind Sie nicht der einzige Entwickler, der das Problem hat, seine Applikation konfigurierbar zu machen. Aus diesem Grund haben andere bereits Klassen entwickelt, die eine Abstraktionsschicht zur Konfiguration darstellen und das Lesen und Schreiben verschiedener Konfigurationsformate vereinfachen.

PEAR::Config
Eine Klasse, die beim Einlesen und Erstellen von Konfigurationsdateien hilft, ist PEAR::Config [3]. Wie alle Klassen aus PEAR lässt sie sich einfach mit dem PEAR-Installer durch

pear install Config

installieren. Haben Sie bisher noch keine Erfahrungen mit dem PEAR-Installer gesammelt, finden Sie Informationen in früheren Ausgaben des PHP Magazins [4].

Die Klasse ist treiberbasiert und ermöglicht das Arbeiten mit den Formaten XML, INI, Apache-Style (Mischung aus XML und INI) sowie PHP Arrays. Der Vorteil dieser Klasse ist, dass die API für alle Treiber die selbe ist. Das bedeutet, dass XML-Konfigurationen über die selben Befehle erzeugt und eingelesen werden können wie INI-Dateien. Dadurch ist es auch nötig, dass die Formate alle der selben Struktur folgen; ähnlich wie INI-Dateien bestehen die Konfigurationen, mit denen PEAR::Config arbeitet, aus Sections und Direktiven.

Möchte man nun die Konfigurationsdatei aus dem vorherigen Beispiel einbinden, so muss zunächst ein neues Config-Objekt erzeugt werden und dann die Methode parseConfig() aufgerufen werden. Da die Methode verschiedene Formate einlesen kann, muss hierbei auch definiert werden, in welchem Format die Konfigurationsdatei vorliegt. Der Parameter für INI-Konfigurationsdateien ist IniFile. Nach dem Einlesen bekommt man nicht direkt die Konfiguration als Array zurück, sondern stattdessen ein Container-Objekt, durch das man Zugriff auf die Konfiguration hat. In den meisten Fällen ist es gewünscht, die komplette Konfiguration als Array zurück zu liefern. Dazu kann die Methode toArray() des Container-Objektes verwendet werden. Listing 4 zeigt den Code, der nötig ist, um die INI-Datei einzulesen.


Listing 4:

<?PHP
require_once 'Config.php';

$config = new Config();
$root =& $config->parseConfig('config2.ini', 'IniFile');

$settings = $root->toArray();

echo "<pre>";
print_r( $settings );
echo "</pre>";
?>


Auf den ersten Blick mag dies etwas umständlich erscheinen. Es ermöglicht einem jedoch, jedes Format, das von PEAR::Config unterstützt wird, mit den selben Methoden einzulesen. Lediglich der Parameter IniFile muss durch das Format ersetzt werden. Weiterhin lässt sich die Konfiguration in jedem beliebigen Format wieder abspeichern:

$root->writeDatasrc( 'config2.conf', 'Apache' );

Listing 5 zeigt, wie man eine Konfiguration aus einem PHP-Array erzeugen kann und diese danach in XML abspeichert. Wenn Sie mehr über PEAR::Config wissen möchten, so finden Sie alle Informationen in der PEAR-Dokumentation [5] oder im DevShed-Tutorial [6].


Listing 5:

<?PHP
require_once 'Config.php';

$settings = array(
'fehler' => array(
'email' => 'webmaster@localhost',
'prefix' => '[FEHLER]',
)
);

$config = new Config();
$root =& $config->parseConfig($settings, 'PHPArray');
$root->writeDatasrc( 'config2.xml', 'XML' );
?>

patConfiguration
Eine weitere Klasse, die Konfigurationsdateien einlesen kann, ist patConfiguration [7], die jedoch auf das Lesen und Schreiben von XML-Dateien spezialisiert ist. Nach dem Download des Archivs muss dieses nur extrahiert werden. Die Klasse selbst befindet sich im include-Verzeichnis. patConfiguration bietet ein vordefiniertes Tag-Set, das jedoch über Konfigurationsdateien selbst erweitert werden kann. Weiterhin ist es bei patConfiguration möglich, einen Typ für die Konfigurationseinstellung anzugeben, um z.B. Fließkommazahlen oder boolesche Werte zu speichern.

Eine typische Konfiguration in patConfiguration hat folgenden Aufbau:

<configuration>
<path name="fehler">
<configValue name="email" type="string">webmaster@localhost</configValue>
<configValue name="priority" type="float">100</configValue>
</path>
</configuration>

Nach dem Instanziieren des Objekts kann mit der Methode parseConfigFile() eine Datei eingelesen werden. Der Zugriff auf die Optionen erfolgt danach über getConfigValue(). Dieser Methode kann ein Pfad zur Direktive übergeben werden; möchte man im obigen Beispiel die eMail-Adresse erhalten, die im Fehlerfall verwendet werden soll, so ist der Pfad fehler.email zu verwenden. Wird kein Pfad angegeben, so erhält man die komplette Konfiguration als Array. Listing 6 zeigt den PHP-Code, der verwendet werden muss, um die Datei einzulesen.


Listing 6:

<?PHP
require_once 'include/patConfiguration.php';

$config = new patConfiguration(
array(
"configDir" =>"./",
"errorHandling" =>"nice_die"
)
);
$config->parseConfigFile( 'config6.xml' );

echo $config->getConfigValue( 'fehler.email' );
?>

patConfiguration 2.0.0
Im Moment wird an einer komplett überarbeiteten Version von patConfiguration gearbeitet, die ähnlich wie PEAR::Config treiberbasiert ist. Damit ist es dann auch möglich, verschiedene Formate zu bearbeiten. Eventuell ist diese Version bei Veröffentlichung dieses Artikels bereits verfügbar, ansonsten können Sie eine aktuelle Entwicklerversion auf snaps.php-tools.net/ downloaden.


In diesem Beispiel haben Sie bereits gesehen, dass bei einem <configValue/>-Tag auch ein Typ angegeben werden kann. Diese sind identisch mit den Typangaben, die der PHP-Funktion settype() übergeben werden können. Wird kein Typ angegeben, so wird string angenommen.

Soll eine Direktive öfter verwendet werden, so kann für diese auch ein Tag definiert werden:

<configuration>
<define tag="priority" type="float"/>
<define tag="email" type="string"/>
<path name="fehler">
<email>webmaster@localhost</email>
<priority>100</priority>
</path>
</configuration>

Analog zu getConfigValue() gibt es auch noch setConfigValue(), mit dem ein Wert verändert werden kann. Nach erfolgter Änderung kann die Datei mit writeConfigFile() wieder zurück geschrieben werden, wie Listing 7 zeigt. patConfiguration bietet noch weitere Features. Neben Tags können auch Namespaces und Attribute definiert werden, über den vordefinierten Tag kann eine externe Datei eingebunden werden und somit die Konfiguration auf mehrere Dateien verteilt werden. Außerdem bietet patConfiguration ein Caching-System, damit die Konfiguration nur nach Änderung neu eingelesen werden muss. Weitere Informationen finden Sie auf der PHP Application Tools-Homepage und im patConfiguration-Tutorial auf DevShed [8].


Listing 7:

<?PHP
require_once 'include/patConfiguration.php';

$config = new patConfiguration(
array(
"configDir" =>"./",
"errorHandling" =>"nice_die"
)
);
$config->parseConfigFile( 'config6.xml' );

$config->setConfigValue( 'fehler.email', 'errors@localhost' );

$config->writeConfigFile( 'config6_new.xml', 'xml', array( 'mode' => 'pretty' ) );
?>

Fazit
Konfigurierbare Applikationen können Entwicklungszeit einsparen, wenn Komponenten in verschiedenen Projekten eingesetzt werden sollen. Noch mehr Zeit lässt sich sparen, wenn schon zum Arbeiten mit Konfigurationsdateien eine bestehende Klasse eingesetzt wird. Ob dies nun patConfiguration oder PEAR::Config ist, hängt von Ihren Anforderungen ab. Während PEAR::Config vor allem darauf Wert legt, beliebige Formate zu verarbeiten, liegt der Schwerpunkt bei patConfiguration auf XML und zusätzlichen Funktionen. Mit dem Erscheinen der Version 2.0.0 wird patConfiguration jedoch mit der gleichen API auch INI- und WDDX-Dateien einlesen können. PHP-Arrays werden in der aktuellen Version schon unterstützt.

Links und Literatur


    Hat Ihnen dieser Artikel gefallen? Dann abonnieren Sie das PHP Magazin direkt über unser

zur vorherigen Seite
zurück
an den Anfang der Seite
nach oben
Diesen Artikel drucken
drucken
Diesen Artikel weiterempfehlen
empfehlen

Software & Support Media GmbH