Kristall Tutorial – Teil 1: Struktureller Aufbau

Nachdem wir jetzt einen mehr oder weniger tollen Projektnamen haben müssen wir uns ein paar Gedanken darüber machen, wie wir die verschiedenen Dateien und Verzeichnisse organisieren.

Grundsätzlich ist erst mal zu berücksichtigen, dass ein Framework in Prinzip eine Klassenbibliothek ist, die in einer Anwendung eingesetzt wird und keine Anwendung an sich.

Also brauchen wir ein Verzeichnis für die Applikation, der Einfachheit halber “app” genannt und eines für Bibliotheken welches wir “lib” nennen.

Außerdem ist es immer Ratsam, dass sich die Bibliotheken und die Anwendung selbst außerhalb des webroot befinden, damit der Zugriff auf Dateien, die tatsächliche Programm- und Geschäftslogik beinhalten über eine URL gar nicht erst möglich ist. Daher benötigen wir noch ein Verzeichnis für öffentlich zugängliche Dateien wie CSS, Bilder und JavaScript. Dies kommt auf die gleiche Verzeichnisebene wie app und lib und wird “web” heißen.

Damit stehen wir folgendermaßen da:

app/
lib/
    Kristall/
web/
    img/
    js/
    css/
    index.php

Das bedeutet, dass der Webserver so konfiguriert werden muss, dass das DocumentRoot auf das web/ Verzeichnis zeigt.

Im web Verzeichnis legen wir zusätzlich noch eine index.php an, die als zentraler Einstiegspunkt und Verteiler dient, der die Anfragen an die Applikation übergibt welche dann wiederum die dazu gehörende Aktion ausführt. Mehr PHP Dateien müssen im DocumentRoot gar nicht auftauchten. Der Rest spielt sich praktisch “unter Ausschluss der Öffentlichkeit” ab.

Bootstrap

Die besagte index.php verwenden wir als boostrap um ein paar Konstanten zu definieren, die wir später noch häufiger gebrauchen werden.

web/index.php
ob_start();                                                     

define('DS'      , DIRECTORY_SEPARATOR);
define('BASE_DIR', realpath(dirname(__FILE__).DS.'..'.DS));
define('WEB_DIR' , dirname(__FILE__));
define('APP_DIR' , realpath(BASE_DIR.DS.'app'));
define('LIB_DIR' , realpath(BASE_DIR.DS.'lib'));
define('KRISTALL', realpath(LIB_DIR.DS.'Kristall'));

ob_end_flush();

Wieder nichts aufregendes. Zu beginn aktivieren wir das output buffering, damit nicht versehentlich irgendwelche ungewollten Ausgaben während des Scriptverlaufs an den Browser gesendet werden.
Anschließend definieren wir uns noch ein paar Konstanten, die unsere Verzeichnisstruktur abbilden. Und zu guter letzt schicken wir den Inhalt des buffers an den Browser.

Damit hätten wir schon mal die grundlegende Struktur festgelegt.

Basisklassen und Interfaces

Für eine saubere Struktur benötigen wir zunächst einige allgemeine Basisklassen und Interfaces auf denen die konkreten Klassen der Anwendung aufbauen können. Fangen wir mit dem Essentiellen an. Für ein MVC Framework wäre das jeweils eine allgemeine Klasse für ein Model, einen Controller und – genau – einen View. Da sich dieses Tutorial auf php 5.2 bezieht und wir somit keine Namespaces zur Verfügung haben, werden die Framework Klassen und Interfaces mit einem Präfix versehen, so dass bei der Entwicklung der Applikation Namenskonflikte vermieden werden. Klassen bekommen das Präfix ‘kc’ und Interfaces ‘ki’.

Zusätzlich zu den MVC Basisklassen brauchen wir noch eine Klasse, die in der Lage ist, eine eingegebene URL einer bestimmten Methode eines bestimmten Controllers zuzuordnen und diese aufzurufen – eine dispatcher Klasse.

Zusammengefasst wäre das also:

lib/Kristall/kcModel.php
class kcModel
{

}
lib/Kristall/kcView.php
class kcView
{

}
lib/Kristall/kcController.php
class kcController
{

}
lib/Kristall/kcDispatcher.php
class kcDispatcher {

    public function dispatch()
    {

    }
}

Application Singleton

Als nächstes kümmern wir uns um eine Klasse, die die Applikation an sich repräsentiert. Diese Klasse wird zunächst einmal die Konfiguration der Anwendung zur Laufzeit verfügbar machen. Da sie im Verlauf noch weitere Aufgaben übernehmen muss, nämlich das autoloading sowie das setup von abhängigen Objekten, die innerhalb der Applikation nur einmal existieren dürfen, machen wir daraus ein singleton.

lib/Kristall/Kristall.php
class Kristall
{

    /**
     * Stores the single instance
     * @var Kristall
     */
    protected static $instance = null;

    /**
     * Stores the configuration array
     * @var array
     */
    protected $configuration = array();

    /**
     * Returns the application instance or creates it if it does not exist.
     * The configuration argument is only neccessary on the fist call.
     *
     * @param array $configuration
     * @return Kristall
     */
    public static function application($configuration = null)
    {
        if(is_null(self::$instance)){
            self::$instance = new self($configuration);
        }
        return self::$instance;
    }

    /**
     * Prevents cloning of the singleton
     */
    public function __clone()
    {
        throw new Exception(sprintf('Cloning of %s is not allowed.', __CLASS__));
    }

    /**
     * Creates and configures a new instance of Kristall and registers the autoloader
     *
     * @param array $configuration
     */
    protected function  __construct($configuration)
    {
        $this->configuration = $configuration;
    }

    /**
     * Searches the configuration for a certain setting with the given path. Use the dot sign as path separator.
     * If the given path does not exist, the optional default value will be returned.
     *
     * @param string $path
     * @param mixed $default
     * @return mixed
     */
    public function getSetting($path, $default=null)
    {
        $config  = $this->configuration;
        $nodes   = explode('.', trim($path, '.'));
        $current = & $config;
        while (null !== ($index = array_shift($nodes))){
            if (is_null($value)){
                if (!isset($current[$index])) return $default;
            }
            $current = & $current[$index];
        }
        return $current;
    }
}

Prinzipiell haben wir es hier nicht mit „rocket science“ zu tun sondern einfach nur das Singleton Entwursmuster angewendet. Das einzig halbwegs interessante ist die getSetting() Methode, die es uns ermöglicht auf unkomplizierte weise einen bestimmten Eintrag aus der Konfiguration zu lesen, oder falls dieser nicht vorhanden ist, einen Standard-Wert zurück zu geben.

Damit wären wir am Ende des ersten Teils der Serie angelangt. Eigentlich hatte ich ein deutlich größeres Pensum vorgesehen, aber ich möchte auch niemanden mit einer Textwand erschlagen. Daher habe ich das ganze in kleinere Häppchen unterteilt. Falls Ihr mehr Input pro Abschnitt haben wollt, fragen und/oder Anregungen habt, hinterlasst mir einen Kommentar und dann schauen wir mal was sich da machen lässt.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.