Dieser Artikel veranschaulicht verschiedene Möglichkeiten zum Erstellen konfigurierbarer PHP-Anwendungen. Der Artikel untersucht auch die idealen Konfigurationspunkte in einer Anwendung und sucht nach einem Gleichgewicht zwischen einer zu konfigurierbaren und einer zu geschlossenen Anwendung.
Wenn Sie planen, Ihre PHP-Anwendung anderen Personen oder Unternehmen zur Verfügung zu stellen, müssen Sie sicherstellen, dass die Anwendung konfigurierbar ist. Erlauben Sie Benutzern zumindest, Datenbank-Anmeldungen und Passwörter auf sichere Weise festzulegen, damit das darin enthaltene Material nicht veröffentlicht wird.
In diesem Artikel werden verschiedene Techniken zum Speichern von Konfigurationseinstellungen und zum Bearbeiten dieser Einstellungen demonstriert. Darüber hinaus gibt der Artikel auch Hinweise dazu, welche Elemente konfigurierbar gemacht werden müssen und wie man nicht in das Dilemma der Über- oder Unterkonfiguration gerät.
Konfiguration mit INI-Dateien
PHP verfügt über eine integrierte Unterstützung für Konfigurationsdateien. Dies wird durch einen Initialisierungsdateimechanismus (INI) wie die Datei php.ini erreicht, in der Konstanten wie Zeitüberschreitungen bei Datenbankverbindungen oder die Art und Weise, wie Sitzungen gespeichert werden, definiert werden. Wenn Sie möchten, können Sie in dieser php.ini-Datei die Konfiguration für Ihre Anwendung anpassen. Zur Veranschaulichung habe ich der Datei php.ini die folgenden Codezeilen hinzugefügt.
myapptempdir=foo
Dann habe ich ein kleines PHP-Skript geschrieben, um dieses Konfigurationselement zu lesen, wie in Listing 1 gezeigt.
Listing 1. ini1.php
<?php
Funktion get_template_directory()
{
$v = get_cfg_var( "myapptempdir" );
return ( $v == null ) ? "tempdir" : $v;
}
echo( get_template_directory()."n" );
?>
Wenn Sie diesen Code in der Befehlszeile ausführen, erhalten Sie die folgenden Ergebnisse:
% php ini1.php
foo
%
wunderbar. Aber warum können wir nicht die Standard-INI-Funktion verwenden, um den Wert des Konfigurationselements myapptempdir abzurufen? Ich habe einige Nachforschungen angestellt und festgestellt, dass benutzerdefinierte Konfigurationselemente mit diesen Methoden in den meisten Fällen nicht abgerufen werden können. Der Zugriff darauf ist jedoch über die Funktion get_cfg_var möglich.
Um diesen Ansatz zu vereinfachen, kapseln Sie den Zugriff auf die Variable in einer zweiten Funktion, die den Namen des Konfigurationsschlüssels und einen Standardwert als Parameter verwendet, wie unten gezeigt.
Listing 2. ini2.php
-Funktion get_ini_value( $n, $dv )
{
$c = get_cfg_var( $n );
return ( $c == null ) ? $dv : $c;
}
Funktion get_template_directory()
{
return get_ini_value( "myapptempdir", "tempdir" );
}
Dies ist eine gute Übersicht über den Zugriff auf die INI-Datei. Wenn Sie also einen anderen Mechanismus verwenden oder die INI-Datei woanders speichern möchten, müssen Sie sich nicht die Mühe machen, viele Funktionen zu ändern.
Ich empfehle aus zwei Gründen nicht, INI-Dateien für die Anwendungskonfiguration zu verwenden. Erstens erleichtert dies zwar das Lesen der INI-Datei, macht es jedoch nahezu unmöglich, die INI-Datei sicher zu schreiben. Dies ist also nur für schreibgeschützte Konfigurationselemente geeignet. Zweitens wird die Datei php.ini von allen Anwendungen auf dem Server gemeinsam genutzt, daher denke ich nicht, dass anwendungsspezifische Konfigurationselemente in diese Datei geschrieben werden sollten.
Was müssen Sie über INI-Dateien wissen? Das Wichtigste ist, wie Sie den Include-Pfad zurücksetzen, um Konfigurationselemente hinzuzufügen, wie unten gezeigt.
Listing 3. ini3.php
<?php
echo( ini_get("include_path")."n" );
ini_set("include_path",
ini_get("include_path").":./mylib" );
echo( ini_get("include_path")."n" );
?>
In diesem Beispiel habe ich mein lokales mylib-Verzeichnis zum Include-Pfad hinzugefügt, sodass ich PHP-Dateien aus diesem Verzeichnis anfordern kann, ohne den Pfad zur require-Anweisung hinzuzufügen.
Konfiguration in PHP
Eine gängige Alternative zum Speichern von Konfigurationseinträgen in einer INI-Datei ist die Verwendung eines einfachen PHP-Skripts zum Beibehalten der Daten. Unten finden Sie ein Beispiel.
Listing 4. config.php
<?php
# Geben Sie den Speicherort des temporären Verzeichnisses an
#
$TEMPLATE_DIRECTORY = "tempdir";
?>
Der Code, der diese Konstante verwendet, lautet wie folgt.
Listing 5. php.php
<?php
require_once 'config.php';
function get_template_directory()
{
global $TEMPLATE_DIRECTORY;
return $TEMPLATE_DIRECTORY;
}
echo( get_template_directory()."n" );
?>
Der Code enthält zunächst die Konfigurationsdatei (config.php), und dann können Sie diese Konstanten direkt verwenden.
Der Einsatz dieser Technologie bietet viele Vorteile. Erstens: Wenn jemand nur die Datei config.php durchsucht, ist die Seite leer. Sie können config.php also in derselben Datei ablegen wie das Stammverzeichnis Ihrer Webanwendung. Zweitens kann es in jedem Editor bearbeitet werden, und einige Editoren verfügen sogar über Funktionen zur Syntaxfärbung und Syntaxprüfung.
Der Nachteil dieser Technologie besteht darin, dass es sich wie bei INI-Dateien um eine schreibgeschützte Technologie handelt. Das Extrahieren der Daten aus dieser Datei ist ein Kinderspiel, aber das Anpassen der Daten in der PHP-Datei ist schwierig und in manchen Fällen sogar unmöglich.
Die folgende Alternative zeigt, wie man ein Konfigurationssystem schreibt, das sowohl les- als auch beschreibbar ist.
Die beiden vorherigen Beispiele fürTextdateien
eignen sich gut für schreibgeschützte Konfigurationseinträge, aber was ist mit Konfigurationsparametern, die sowohl lesbar als auch schreibbar sind? Schauen Sie sich zunächst die Textkonfigurationsdatei in Listing 6 an.
Listing 6. config.txt
# Die Konfigurationsdatei meiner Anwendung
Titel=Meine App
TemplateDirectory=tempdir
Dies ist das gleiche Dateiformat wie die INI-Datei, aber ich habe mein eigenes Hilfstool geschrieben. Dazu habe ich meine eigene Konfigurationsklasse erstellt, wie unten gezeigt.
Listing 7. text1.php
<?php
Klassenkonfiguration
{
private $configFile = 'config.txt';
private $items = array();
function __construct() { $this->parse(}
function __get($id) { return $this->items[ $id ];
Funktion parse()
{
$fh = fopen( $this->configFile, 'r' );
while( $l = fgets( $fh ) )
{
if ( preg_match( '/^#/', $l ) == false )
{
preg_match( '/^(.*?)=(.*?)$/', $l, $found );
$this->items[ $found[1] ] = $found[2];
}
}
fclose( $fh );
}
}
$c = new Configuration();
echo( $c->TemplateDirectory."n" );
?>
Dieser Code erstellt zunächst ein Konfigurationsobjekt. Als nächstes liest der Konstruktor config.txt und setzt die lokale Variable $items mit den analysierten Dateiinhalten.
Das Skript sucht dann nach TemplateDirectory, das nicht direkt im Objekt definiert ist. Daher wird die magische __get-Methode aufgerufen, wobei $id auf „TemplateDirectory“ gesetzt ist, was den Wert im $items-Array für diesen Schlüssel zurückgibt.
Diese __get-Methode ist spezifisch für die PHP V5-Umgebung, daher muss dieses Skript unter PHP V5 ausgeführt werden. Tatsächlich müssen alle Skripte in diesem Artikel unter PHP V5 ausgeführt werden.
Wenn Sie dieses Skript über die Befehlszeile ausführen, werden die folgenden Ergebnisse angezeigt:
% php text1.php
tempdir
%
Alles wird erwartet, das Objekt liest die Datei config.txt und erhält den richtigen Wert für das TemplateDirectory-Konfigurationselement.
Aber was sollten Sie tun, um einen Konfigurationswert festzulegen? Durch das Erstellen einer neuen Methode und eines neuen Testcodes in dieser Klasse können Sie diese Funktionalität erhalten, wie unten gezeigt.
Listing 8. text2.php
<?php
Klassenkonfiguration
{
...
function __get($id) { return $this->items[ $id ];
function __set($id,$v) { $this->items[ $id ] = $v;
Funktion parse() { ... }
}
$c = neue Konfiguration();
echo( $c->TemplateDirectory."n" );
$c->TemplateDirectory = 'foobar';
echo( $c->TemplateDirectory."n" );
?>
Nun gibt es eine __set-Funktion, die der „Cousin“ der __get-Funktion ist. Diese Funktion ruft den Wert für eine Mitgliedsvariable nicht ab. Diese Funktion wird aufgerufen, wenn eine Mitgliedsvariable festgelegt werden soll. Der Testcode unten legt den Wert fest und druckt den neuen Wert aus.
Folgendes passiert, wenn Sie diesen Code über die Befehlszeile ausführen:
% php text2.php
tempdir
foobar
%
Sehr gut! Aber wie kann ich es in einer Datei speichern, damit die Änderung behoben wird? Dazu müssen Sie die Datei schreiben und lesen. Neue Funktion zum Schreiben von Dateien wie unten gezeigt.
Listing 9. text3.php
<?php
Klassenkonfiguration
{
...
Funktion save()
{
$nf = '';
$fh = fopen( $this->configFile, 'r' );
while( $l = fgets( $fh ) )
{
if ( preg_match( '/^#/', $l ) == false )
{
preg_match( '/^(.*?)=(.*?)$/', $l, $found );
$nf .= $found[1]."=".$this->items[$found[1]]."n";
}
anders
{
$nf .= $l;
}
}
fclose( $fh );
copy( $this->configFile, $this->configFile.'.bak' );
$fh = fopen( $this->configFile, 'w' );
fwrite( $fh, $nf );
fclose( $fh );
}
}
$c = neue Konfiguration();
echo( $c->TemplateDirectory."n" );
$c->TemplateDirectory = 'foobar';
echo( $c->TemplateDirectory."n" );
$c->save();
?>
Neue Speicherfunktion manipuliert geschickt config.txt. Anstatt die Datei einfach mit den aktualisierten Konfigurationselementen neu zu schreiben (wodurch die Kommentare entfernt würden), habe ich die Datei gelesen und den Inhalt des $items-Arrays flexibel neu geschrieben. Auf diese Weise bleiben die Kommentare in der Datei erhalten.
Führen Sie das Skript in der Befehlszeile aus und geben Sie den Inhalt der Textkonfigurationsdatei aus. Sie können die folgende Ausgabe sehen.
Listing 10. Funktionsausgabe
%php text3.php
speichern
tempdir
foobar
% cat config.txt
#Die Konfigurationsdatei meiner Anwendung
Titel=Meine App
TemplateDirectory=foobar
%
Die ursprüngliche Datei config.txt wird jetzt mit den neuen Werten aktualisiert.
XML-Konfigurationsdateien
Obwohl Textdateien leicht zu lesen und zu bearbeiten sind, erfreuen sie sich nicht so großer Beliebtheit wie XML-Dateien. Darüber hinaus stehen zahlreiche Editoren für XML zur Verfügung, die Markup, spezielle Symbol-Escapes und mehr verstehen. Wie würde also die XML-Version der Konfigurationsdatei aussehen? Listing 11 zeigt die Konfigurationsdatei im XML-Format.
Listing 11. config.xml
<?xml version="1.0"?>
<config>
<Titel>Meine App</Titel>
<TemplateDirectory>tempdir</TemplateDirectory>
</config>
Listing 12 zeigt eine aktualisierte Version der Configuration-Klasse, die XML zum Laden von Konfigurationseinstellungen verwendet.
Listing 12. xml1.php
<?php
Klassenkonfiguration
{
private $configFile = 'config.xml';
private $items = array();
function __construct() { $this->parse(}
function __get($id) { return $this->items[ $id ];
Funktion parse()
{
$doc = new DOMDocument();
$doc->load( $this->configFile );
$cn = $doc->getElementsByTagName( "config" );
$nodes = $cn->item(0)->getElementsByTagName( "*" );
foreach( $nodes als $node )
$this->items[ $node->nodeName ] = $node->nodeValue;
}
}
$c = neue Konfiguration();
echo( $c->TemplateDirectory."n" );
?>
Es scheint, dass XML noch einen weiteren Vorteil hat: Der Code ist einfacher und einfacher als die Textversion. Um dieses XML zu speichern, ist eine andere Version der Speicherfunktion erforderlich, die das Ergebnis im XML-Format statt im Textformat speichert.
Listing 13. xml2.php
...
Funktion save()
{
$doc = new DOMDocument();
$doc->formatOutput = true;
$r = $doc->createElement( "config" );
$doc->appendChild( $r );
foreach( $this->items as $k => $v )
{
$kn = $doc->createElement( $k );
$kn->appendChild( $doc->createTextNode( $v ) );
$r->appendChild( $kn );
}
copy( $this->configFile, $this->configFile.'.bak' );
$doc->save( $this->configFile );
}
...
Dieser Code erstellt ein neues XML Document Object Model (DOM) und speichert dann alle Daten im $items-Array in diesem Modell. Verwenden Sie anschließend die Save-Methode, um das XML in einer Datei zu speichern.
Eine letzte Alternative
zur Verwendung einer Datenbank
besteht darin, eine Datenbank zum Speichern der Werte von Konfigurationselementen zu verwenden.Der erste Schritt besteht darin, ein einfaches Schema zum Speichern von Konfigurationsdaten zu verwenden. Unten finden Sie ein einfaches Muster.
Listing 14. schema.sql
DROP TABLE IF EXISTS-Einstellungen;
CREATE TABLE-Einstellungen (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
Namenstext,
Wert TEXT,
PRIMÄRSCHLÜSSEL(id)
);
Dies erfordert einige Anpassungen basierend auf den Anwendungsanforderungen. Wenn Sie beispielsweise möchten, dass das Konfigurationselement pro Benutzer gespeichert wird, müssen Sie die Benutzer-ID als zusätzliche Spalte hinzufügen.
Zum Lesen und Schreiben von Daten habe ich die aktualisierte Konfigurationsklasse geschrieben, die in Abbildung 15 dargestellt ist.
Listing 15. db1.php
<?php
require_once( 'DB.php' );
$dsn = 'mysql://root:password@localhost/config';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage() }
class Configuration
{
private $configFile = 'config.xml';
private $items = array();
function __construct() { $this->parse(}
function __get($id) { return $this->items[ $id ];
Funktion __set($id,$v)
{
global $db;
$this->items[ $id ] = $v;
$sth1 = $db->prepare( 'DELETE FROM Settings WHERE name=?' );
$db->execute( $sth1, $id );
if (PEAR::isError($db)) { die($db->getMessage() }
$sth2 = $db->prepare('INSERT INTO Settings ( id, name, value ) VALUES ( 0, ?, ? )' );
$db->execute( $sth2, array( $id, $v ) );
if (PEAR::isError($db)) { die($db->getMessage() }
}
Funktion parse()
{
global $db;
$doc = new DOMDocument();
$doc->load( $this->configFile );
$cn = $doc->getElementsByTagName( "config" );
$nodes = $cn->item(0)->getElementsByTagName( "*" );
foreach( $nodes als $node )
$this->items[ $node->nodeName ] = $node->nodeValue;
$res = $db->query( 'SELECT name,value FROM Settings' );
if (PEAR::isError($db)) { die($db->getMessage() }
while( $res->fetchInto( $row ) ) {
$this->items[ $row[0] ] = $row[1];
}
}
}
$c = neue Konfiguration();
echo( $c->TemplateDirectory."n" );
$c->TemplateDirectory = 'new foo';
echo( $c->TemplateDirectory."n" );
?>
Dies ist eigentlich eine hybride Text-/Datenbanklösung. Bitte schauen Sie sich die Parse-Methode genauer an. Diese Klasse liest zuerst die Textdatei, um den Anfangswert zu erhalten, und liest dann die Datenbank, um den Schlüssel auf den neuesten Wert zu aktualisieren. Nach dem Festlegen eines Werts wird der Schlüssel aus der Datenbank entfernt und ein neuer Datensatz mit dem aktualisierten Wert hinzugefügt.
Es ist interessant zu sehen, wie die Configuration-Klasse in mehreren Versionen dieses Artikels funktioniert. Sie kann Daten aus Textdateien, XML und Datenbanken lesen und dabei dieselbe Schnittstelle beibehalten. Ich empfehle Ihnen, auch in Ihrer Entwicklung Schnittstellen mit der gleichen Stabilität zu verwenden. Wie das genau funktioniert, ist für den Auftraggeber des Objekts unklar. Der Schlüssel ist der Vertrag zwischen dem Objekt und dem Kunden.
Was ist Konfiguration und wie wird sie konfiguriert?
Es kann schwierig sein, den richtigen Mittelweg zwischen zu vielen Konfigurationsoptionen und nicht genügend Konfiguration zu finden. Natürlich sollte jede Datenbankkonfiguration (zum Beispiel Datenbankname, Datenbankbenutzer und Passwort) konfigurierbar sein. Darüber hinaus habe ich einige grundlegende empfohlene Konfigurationselemente.
In den erweiterten Einstellungen sollte jede Funktion über eine separate Aktivierungs-/Deaktivierungsoption verfügen. Erlauben oder deaktivieren Sie diese Optionen entsprechend ihrer Bedeutung für die Anwendung. Beispielsweise ist in einer Webforum-Anwendung die Verzögerungsfunktion standardmäßig aktiviert. E-Mail-Benachrichtigungen sind jedoch standardmäßig deaktiviert, da hierfür offenbar eine Anpassung erforderlich ist.
Die Optionen der Benutzeroberfläche (UI) sollten alle an einem Ort festgelegt werden. Die Struktur der Benutzeroberfläche (z. B. Menüpositionen, zusätzliche Menüelemente, URLs, die auf bestimmte Elemente der Benutzeroberfläche verweisen, verwendete Logos usw.) sollten alle auf einen einzigen Ort festgelegt sein. Ich rate dringend davon ab, Schriftart-, Farb- oder Stileinträge als Konfigurationselemente anzugeben. Diese sollten über Cascading Style Sheets (CSS) eingestellt werden und das Konfigurationssystem sollte angeben, welche CSS-Datei verwendet werden soll. CSS ist eine effiziente und flexible Möglichkeit, Schriftarten, Stile, Farben und mehr festzulegen. Es gibt viele großartige CSS-Tools, und Ihre Anwendung sollte CSS sinnvoll nutzen, anstatt zu versuchen, selbst den Standard festzulegen.
Innerhalb jeder Funktion empfehle ich die Einstellung von 3 bis 10 Konfigurationsoptionen. Diese Konfigurationsmöglichkeiten sollten sinnvoll benannt werden. Wenn Konfigurationsoptionen über die Benutzeroberfläche festgelegt werden können, sollten Optionsnamen in Textdateien, XML-Dateien und Datenbanken in direktem Zusammenhang mit dem Titel des Schnittstellenelements stehen. Darüber hinaus sollten diese Optionen alle eindeutige Standardwerte haben.
Im Allgemeinen sollten die folgenden Optionen konfigurierbar sein: E-Mail-Adressen, welches CSS verwendet werden soll, der Speicherort der von Dateien referenzierten Systemressourcen und die Dateinamen von Grafikelementen.
Für grafische Elemente möchten Sie möglicherweise einen separaten Profiltyp namens „Skin“ erstellen, der Einstellungen für das Profil enthält, einschließlich der Platzierung von CSS-Dateien, der Platzierung von Grafiken und dergleichen. Lassen Sie den Benutzer dann aus mehreren Skin-Dateien auswählen. Dies vereinfacht umfangreiche Änderungen am Erscheinungsbild Ihrer Anwendung. Dies bietet Benutzern auch die Möglichkeit, die Anwendung zwischen verschiedenen Produktinstallationen zu verteilen. Dieser Artikel behandelt diese Skin-Dateien nicht, aber die Grundlagen, die Sie hier lernen, werden die Unterstützung von Skin-Dateien viel einfacher machen.
Fazit:
Konfigurierbarkeit ist ein wesentlicher Bestandteil jeder PHP-Anwendung und sollte von Anfang an ein zentraler Bestandteil des Designs sein. Ich hoffe, dieser Artikel hilft Ihnen bei der Implementierung Ihrer Konfigurationsarchitektur und gibt Hinweise darauf, welche Konfigurationsoptionen Sie zulassen sollten.