PHP

PHP & OOP - Konfigurationsmanagement (PHP Tutorial)

Tutorial erstellt von Andreas W. in PHP 5.x, letzte Änderung am 15.12.2010

Das folgende Tutorial soll die Grundlagen für eine Implementation einer Bibliothek zur Verwaltung und Verarbeitung von Konfigurationsdateien legen.

Dabei soll darauf geachtet werden, die Implementation so abstrakt zu halten, dass sie zum einen leicht erweiterbar und zum anderen vom Kontext des einsetzenden Projekts gelöst eingesetzt werden kann. Zu diesem Zweck wird eine abstrakte Klasse entworfen werden, die alle Methoden zur Verfügung stellt, die nur mit den internen Daten arbeitet. Eine konkrete Klasse, die von dieser abstrakten Implementation abgeleitet wird, muss dann nur noch die Operationen, das Schreiben und Lesen, zur Verfügung stellen.

Am Ende dieses Exkurses soll ein Beispiel aus der Praxis zeigen, wie eine solche Bibliothek auch in kleineren Projekten dazu eingesetzt werden kann, um ein Projekt schlanker und leichter wartbar zu gestalten. Die gezeigten Anwendungen und Methoden werden dabei ausführlich erläutert.

Motivation


Die Gründe, die für die Implementierung einer solchen Bibliothek sprechen, sind eigentlich recht ersichtlich.

Es gibt Daten innerhalb einer Applikation, die an mehreren Stellen innerhalb des Projektes gebraucht werden, eventuell veränderlich sind und nicht in einer Datenbank abgelegt werden können oder sollen.

Die Zugangsdaten, die benötigt werden, um eine Verbindung zur Datenbank aufbauen zu können, sind ein Beispiel dafür. In einer Datenbank können sie nicht gespeichert werden, da man sie ohne sich selbst gar nicht abfragen könnte und verschiedene Teilprogramme greifen auf diese Daten zurück.

Ähnlich verhält es sich mit allgemeinen Daten, wie dem Namen der Website, der Kurzbeschreibung, den standardmäßig verwendeten Schlüsselwörtern und dem Projekt-Root Pfad. Diese könnten zwar in einer Datenbank abgelegt werden, dies macht jedoch wenig Sinn, da es sich im Regelfall um zu wenig Datensätze und zu simple Operationen handelt.

Es macht folglich Sinn, einige wenige Daten in Konfigurationsdateien, welcher Art auch immer, abzulegen.

Anforderungen an eine solche Bibliothek


Um die Verwaltung mit Hilfe einer in PHP entwickelten Bibliothek für einen Entwickler interessant zu machen, muss diese für ihn schnell und einfach einsetzbar sein.

Zu diesem Zweck sollte der Entwickler die Möglichkeit besitzen die Bibliothek zu erweitern, ohne an bestehendem Code zu arbeiten oder viel Zeit aufzuwenden. Außerdem soll sie einer objektorientierten Syntax folgen. Um dies zu gewährleisten sollte man feste Richtlinien an diese Objekte anlegen. Folglich muss eine abstrakte Klasse implementiert werden.

Eine Unterteilung der verschiedenen Einträge in verschiedene Bereiche erleichtern dem Entwickler zusätzlich die Arbeit, da es so möglich wird, Daten für verschiedene Bereiche in einer Datei abzulegen und einzeln zu laden.

Die Implementierung


Zu Beginn implementiert man eine abstrakte Klasse, wie wir sie oben beschrieben haben. Diese ermöglicht das Arbeiten auf den Konfigurationsdaten, die in einem Array abgelegt sind. Es werden also CRUD-Methoden zum Lesen, Schreiben, Umbenennen, Hinzufügen und Löschen von Bereichen, die im folgenden Sektionen genannt werden, und Einträgen (Items) benötigt.

Da teilweise eine komplette Konfiguration neu gesetzt oder geladen werden soll, müssen auch für diese Aufgaben Methoden bereitgestellt werden

Weil bei der Verarbeitung der Daten innerhalb dieser CRUD-Methoden verschiedene Fehler auftreten können, die man behandeln muss, ist es außerdem sinnvoll eine eigene Exception-Klasse zu definieren. Diese heißt in diesem Fall ConfigException.

Da die Implementation dieser Methoden sehr einfach ist, sofern man sich mit der Arbeit auf Arrays auskennt, findet man nachfolgend direkt die Implementation.

Configuration.php
Code:

<?php

/**
* @package core::config
* @class ConfigException
*
* The ConfigException class implements a
* Exception type for the concrete Configuration
* classes.
*
* @author Andreas Wilhelm
* @version
* Version 0.1, 26.11.2010
*/
class ConfigException extends Exception {}

/**
* @package core::config
* @class Configuration
*
* The abstract Configuration class allows reading a configuration-file.
* Furthermore you are able to add new entries to the configuration.
* Sure it is also possible to add, remove, edit, query and to save
* the new configuration. Finally it is important to
* mention that this class supports the structuring of the
* configuration in different sections, which sure can also
* be handled. Warning! This class can just handle existing
* configuration files and it doesn't support array items.
*
* @author Andreas Wilhelm
* @version
* Version 0.1, 26.11.2010
*/
abstract class Configuration {
    /**
     * @protected
     * Holds the configuration data.
     */
    protected $config = array();
    
    /**
     * @protected
     * Holds the name of the configuration file.
     */
    protected $file;
    
    /**
     * @public
     *
     * Adds a new item into a section of
     * the configuration, by given section name,
     * key and value.
     *
     * @param string The section name.
     * @param string The key.
     * @param string The value.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function addItem($section, $key, $value) {
        // Check if entry already exists, ...
        if( !isset($this->config[$section][$key]) ) {
            // ... add it if not ...
            $this->config[$section][$key] = $value;
        }
        
        // ... and otherwsie throw an error.
        else {
            throw new ConfigException('Item ' . $section . ' - ' . $key . ' already exists.');
        }
    }
    
    /**
     * @public
     *
     * Removes an item from a section of
     * the configuration, by given section name and key.
     *
     * @param string The section name.
     * @param string The key.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function delItem($section, $key) {
        unset( $this->config[$section][$key] );
    }
    
    /**
     * @public
     *
     * Assigns a new value to an item of
     * the configuration, by given section name,
     * key and value.
     *
     * @param string The section name.
     * @param string The key.
     * @param string The value.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function setItem($section, $key, $value) {
        // Check if item exists ...
        if( isset($this->config[$section][$key]) ) {
            // ... and assign value if it does.
            $this->config[$section][$key] = $value;
        }
        
        // Otherwise throw an exception.
        else {
            throw new ConfigException('Item ' . $section . ' - ' . $key . ' does not exist.');
        }
    }

    /**
     * @public
     *
     * Returns the value of an item by given
     * section name and key.
     *
     * @param string The section name.
     * @param string The key.
     *
     * @return The item's value.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function getItem($section, $key) {
        // Check if item exists ...
        if( !isset($this->config[$section][$key]) ) {
            // ... and throw an exception if not.
            throw new ConfigException('Cannot get item ' . $section . ' - ' . $key);
        }
        
        // Otherwise return it.
        return $this->config[$section][$key];
    }
    
    /**
     * @public
     *
     * Renames an item by given section name,
     * old and new key.
     *
     * @param string The section name.
     * @param string The old key.
     * @param string The new key.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function rnItem($section, $from, $to) {
        // Check if item exists ...
        if( isset($this->config[$section][$from]) && !isset($this->config[$section][$to])) {
            // ... and move data to new item.
            $this->config[$section][$to] = $this->config[$section][$from];
            
            // Remove old item.
            unset( $this->config[$section][$from] );
        }
        
        // Otherwise throw an exception.
        else {
            throw new ConfigException('Cannot rename item' . $section . ' - ' . $key . '.');
        }
    }
    
    /**
     * @public
     *
     * Adds a new section.
     *
     * @param string The section name.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function addSection($name)    {
        // Check if section already exists ...
        if( !isset($this->config[$name]) ) {
            // ... and add it if not.
            $this->config[$name] = array();
        }
        
        // Otherwise throw an exception.
        else {
            throw new ConfigException('Section ' . $name . ' already exists');
        }
    }
    
    /**
     * @public
     *
     * Removes a section.
     *
     * @param string The section name.
     * @param boolean Flag that determents weather a section should be empty or not.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function delSection($name, $ifEmpty = true) {
        // Check if entry exists, ...
        if( isset($this->config[$name]) ) {
            // ... if section should be empty ...
            if( ($ifEmpty == true) && (count($this->config[$name]) > 0) ) {
                // ... and it is not, throw an exception.
                throw new ConfigException('Section ' . $name . ' is not empty.');
            }
            
            // Remove the section.
            unset($this->config[$name]);
        }
        
        // Otherwise throw an exception.
        else {
            throw new ConfigException('Cannot found section ' . $name . '.');
        }
    }
    
    /**
     * @public
     *
     * Returns all items of a section.
     *
     * @param string The section name.
     *
     * @return Item array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function getSection($name) {
        // Check if entry exists.
        if( !isset($this->config[$name]) ) {
            // Otherwise throw an exception.
            throw new Exception('Cannot get section ' . $name . '.');
        }
        
        return $this->config[$name];
    }
    
    /**
     * @public
     *
     * Returns the names of all sections.
     *
     * @return Section array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function getSections() {
        // Initialize section array, ...
        $sections = array();
        
        // ... loop over the data array ...
        foreach( $this->config as $key => $dummy ) {
            // ... append the section name.
            $sections[] = $key;
        }
        
        return $sections;
    }
    
    /**
     * @public
     *
     * Renames a section by given old
     * and new section name.
     *
     * @param string The old name.
     * @param string The new name.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function rnSection($from, $to) {
        // Check if old section exists and new does not.
        if( isset($this->config[$from]) && !isset($this->config[$to])) {
            // Move data to new section ...
            $this->config[$to] = $this->config[$from];
            
            // ... and then remove it.
            unset( $this->config[$from] );
        }
        
        // Otherwise throw an exception.
        else {
            throw new ConfigException('Cannot rename section ' . $from . '.');
        }
    }
    
    /**
     * @public
     *
     * Returns the whole configuration in an array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function getConfig() {
        return $this->config;
    }
    
    /**
     * @public
     *
     * Creates a new Configuration from array.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function setConfig($new) {
        $this->config = $new;
    }
}

?>


Interessanter gestaltet sich nun die konkrete Implementierung einer Klasse zur Verwaltung und Verarbeitung von Konfigurationsdaten. Zu diesem Zweck wird eine Klasse von Configuration abgeleitet, die auf *.ini-Dateien arbeitet. PHP stellt zum Auslesen von Ini-Dateien die Funktion parse_ini_file(string $filename[, bool $process_sections = false]). Sie liest eine komplette Datei ein, die über die Variable $filename angegeben werden kann. Falls das Flag $process_sections gesetzt ist wird anstatt eines eindimensionalen Arrays ein zweidimensionales Array zurückgegeben, welches die gewählte Unterteilung der Einträge in der Konfigurationsdatei in Sektionen beibehält.

Die konkrete Klasse IniConfiguration, die von Configuration abgeleitet wird, muss folglich nur einen Konstruktor implementieren, der einen Dateinamen übergeben bekommt und dann folgende Zuweisung vornimmt.

Code:

// Save configuration to the data array.
$this->config = parse_ini_file($file, true);


Natürlich empfiehlt es sich vorher die Existenz und Lesbarkeit der übergebenen Datei zu überprüfen.

Da man nun eine Datei laden kann, muss man nur noch die Änderungen an der Konfigurationsdatei speichern. Zu diesem Zweck muss eine Methode save implementiert werden. Dabei sollte man darauf achten, dass die Datei, da sie sensible Daten enthalten kann, nicht lesbar ist. Dies kann man relativ einfach durch einen Kommenar am Anfang der Ini-Datei erreichen, der via PHP einen Abbruch erzeugt. Anschließend kann man dann das Array einfach in einer Schleife durchlaufen und die einzelnen Sektionen und deren Einträge ausgeben.

Code:

// Add message that avoids reading the file.
$config = "; <?php die('File access denied.'); ?>\n";

// Loop over all sections.
foreach( $this->config as $section => $items) {
   // Check if there are items.
   if( is_array($items) ) {
      // Save section name.
      $config .= "\n[" . $section . "]\n";

      // Save section items.
      foreach( $items as $key => $value) {
         $config .= $k . " = \"" . $v . "\"\n";
      }
   }

   else {
      // Save entry as key-value pair.
      $config .= $section . " = \"" . $items . "\"\n";
   }
}


Der auf diese Weise erzeugte String muss anschließend nur noch in die Datei zurückgeschrieben werden.

Die fertige IniConfiguration-Klasse sieht dann wie folgt aus.

IniConfiguration.php
Code:

<?php

require_once("Configuration.php");

/**
* @package core::config
* @class IniConfiguration
*
* The IniConfiguration class allows reading an *.ini configuration-file.
* Furthermore you are able to add new entries to the configuration.
* Sure it is also possible to add, remove, edit, query and to save
* the new configuration. Finally it is important to
* mention that this class supports the structuring of the
* configuration in different sections, which sure can also
* be handled. Warning! This class can just handle existing
* configuration files and it doesn't support array items.
*
* @author Andreas Wilhelm
* @version
* Version 0.1, 26.11.2010
*/
class IniConfiguration extends Configuration {
    /**
     * @public
     *
     * The default constructor loads
     * the configuration data from the
     * specified ini file.
     *
     * @param string The file name.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function __construct($file) {
        // Check if file exists ...
        if( file_exists($file) ) {
            // ... set path to config-file.
            $this->file = $file;
        
            // Save configuration to the data array.
            $this->config = parse_ini_file($file, true);
        }
        
        // Otherwise throw an exception.
        else {
            throw new ConfigException('Failed to open ' . $file . '.');
        }
    }
    
    /**
     * @public
     *
     * Saves the configuration data into
     * the *.ini configuration file.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 26.11.2010
     */
    public function save() {
        // Add message that avoids reading the file.
        $config = "; <?php die('File access denied.'); ?>\n";

        // Loop over all sections.
        foreach( $this->config as $section => $items) {
            // Check if there are items.
            if( is_array($items) ) {
                // Save section name.
                $config .= "\n[" . $section . "]\n";
                
                // Save section items.
                foreach( $items as $key => $value) {
                    $config .= $k . " = \"" . $v . "\"\n";
                }
            }
            
            else {
                // Save entry as key-value pair.
                $config .= $section . "= \"" . $items . "\"\n";
            }
        }

        // Try to write contents to config file.
        if( !file_put_contents($this->file, $config) ) {
            // Throw an exception if writing failed.
            throw new ConfigException('Cannot save configuration.');
        }
    }
}

?>


Ein Beispiel aus der Praxis


Zum Abschluss soll nun ein Skript eine Verbindung zu einer Datenbank aufbauen. Dazu werden die Verbindungsdaten aus einer Konfigurationsdatei geladen. Dies ist ein typisches Beispiel aus der Praxis, das gerade bei kleineren Projekten oft anzutreffen ist.

Es ist wichtig die Konfigurationsdateien mit der Endung *.php abzuspeichern, da sonst die erste Zeile beim Aufruf nicht ausgeführt wird und so die Datei für jeden, der sie im Browser aufruft, lesbar wäre. Die diesem Beispiel zu Grunde liegende Konfigurationsdatei sieht nun wie folgt aus.

config.ini.php
Code:

; <?php die('File access denied.'); ?>

[database]
host = "localhost"
name = "user_database"
user = "user_name"
pwd = "user_pass"

[ftp]
host = "localhost"
user = "user_name"
pwd = "user_pass"
port = "ftp_port"

[errors]
error400 = "Error 400 - Bad request."
error404 = "Error 404 - Page could not be found."
error500 = "Error 500 - Internal Server Error."


Die nachfolgende Test-Klasse testet zwar nicht alle Funktionalitäten, das wäre für dieses Tutorial auch zu umfangreich, aber sie sollte die Verwendung dieser Klasse ausreichend demonstrieren.

ConfigTest.php
Code:

<?php

require_once("IniConfiguration.php");

class ConfigTest {
    /**
     * @public
     *
     * Default constructor.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 30.11.2010
     */
    public function __construct() {
        echo "Starting simulation ...\n";
    }
    
    /**
     * @public
     *
     * Default destructor.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 30.11.2010
     */
    public function __destruct() {
        echo "Simulation finsihed ...\n";
    }
    
    /**
     * @public
     *
     * Starts the simulation.
     *
     * @author Andreas Wilhelm
     * @version
     * Version 0.1, 30.11.2010
     */
    public function run() {
        // Load the configuration.
        $config = new IniConfiguration("config.ini.php");
    
        // Load the database connection parameters from the configuration ...
        $db = $config->getSection('database');
        
        // ... and initialize the database connection object.
        $mysqli = new mysqli($db['host'], $db['user'], $db['pwd'], $db['name']);
        
        // Now check if connection was established, ...
        if (mysqli_connect_errno()) {
            throw new Exception("Connect failed: %s\n", mysqli_connect_error());
        }

        // ... create table ...
        if ( $mysqli->query("CREATE TABLE IF NOT EXISTS myCity (id INT, name VARCHAR(35))") === true ) {            
        print "Table myCity successfully created.\n";
        }
        
        // ... and handle errors.
        else {
            throw new Exception("Failed to create table myCity.\n");
        }

        // Select number of entries in the table.
        if ($result = $mysqli->query("SELECT name FROM myCity LIMIT 10")) {
        printf("Select returned %d rows.\n", $result->num_rows);
            $result->close();
        }
        
        // ... and handle errors.
        else {
            throw new Exception("Failed to select number of entries.\n");
        }
    
        $mysqli->close();
    }
}

try {
    // Initialize simulation ...
    $simulation = new ConfigTest();
    
    // ... and run it.
    $simulation->run();
}

catch(Exception $e) {
    print $e->getMessage();
}

?>


Die fertige Bibliothek


Eine bereits implementierte Bibliothek gibt es natürlich auch. Diese findet ihr als *.zip-Archiv im Bereich Skripte des Download-Archivs. Ich möchte darum bitten, an dieser Bibliothek nur unter Rücksprache etwas zu ändern und die eingefügten Kommentare und Versions-Hinweise nicht zu entfernen.

Hinweise zur Implementation, Erweiterungsvorschläge oder BugFixes sind natürlich gern gesehen und können, wie unter Fragen & Antworten beschrieben, an mich weitergegeben werden.

Fragen und Antworten


Fragen und Antworten findet ihr selbstverständlich bei uns im Forum. Ich möchte euch auch darum bitten, keine Fragen an mich persönlich zu senden aus dem einfachen Grund, dass die anderen Leser in diesem Fall nichts von den gegebenen Antworten haben. Zudem können euch im Forum auch viele andere Benutzer, die viel Ahnung haben, helfen. Natürlich freue ich mich über ein kurzes Feedback zu diesem Tutorial. Auch ein Link oder Blog-Post kann helfen. Ich bedanke mich für eure Aufmerksamkeit und hoffe, dass euch mein kurzer Exkurs gefallen hat.

Liebe Grüße,

Andreas

http://www.avedo.net

Dieses Tutorial zeigt eine mögliche Implementation einer Bibliothek zur Verwaltung und Verarbeitung von Konfigurationsdateien.


>> Allgemeine Fragen oder Probleme mit dem Tutorial? Hier gehts zum Forum!

Impressum / Datenschutzerklärung          © der-Webdesigner.net 2002 - 2011           top ▲