Quakenet/#php Tutorial

Hinweis: Wenn sie diese Seite von einer externen URL aufgerufen haben achten sie darauf das alle Kapitel aufeinander aufbauen. Stellen sie daher sicher dass sie alle vorherigen Kapitel gelesen haben, da sie sonst relevante Informationen übersehen.

Templatesystem

  1. Aufbau eines Templatesystems
  2. Templatesysteme in PHP
  3. Eigenes Templatesystem schreiben
  4. Vor- und Nachteile dieses Templatesystems

1. Aufbau eines Templatesystems

Bei einem Templatesystem versucht man den Bereich im PHP-Skript der für die Ausgabe zuständig ist von dem Teil des Skripts zu trennen der für den Rest zuständig ist. Dies entspricht in etwa dem einem Model View Controller. Die Bereiche für die Ausgabe werden dabei in extra Dateien ausgelagert. Solche Dateien werden dann Templates genannt. Mit so einer Trennung ist es viel einfacher die Ausgabe eines Skriptes zu verändern. Es wird nur die entsprechende Template-Datei bearbeitet, der Skript-Teil der für das Holen der Daten für das Template zuständig ist bleibt dabei unberührt. So kann z.B. ein Webdesigner die Template-Dateien nach seinen HTML- und CSS-Wissen bearbeiten ohne zu Wissen wie die Daten aus einer MySQL-Datenbank ausgelesen werden.

2. Templatesysteme in PHP

Im Internet gibt es genügend Templatesystem für PHP. Alle haben ihre Vor- Und Nachteile, bieten Features an die andere nicht haben, haben Sicherheitsaspekte die andere nicht implementiert haben und verwenden eine Syntax die die anderen nicht benutzen. In den meisten Fällen wird jedoch vergessen das PHP selbst bereits eine Templateengine ist. PHP wurde so entwickelt dass es zwischen HTML-Code verwendet werden kann, daher auch die PHP Start- und Endtags <?php und ?>. Deswegen verwenden wir hier PHP als Templatesystem indem wir unseren Programmcode entsprechend strukturieren und include-Anweisungen verwenden.

3. Eigenes Templatesystem schreiben

Mit include-Anweisungen können wir andere PHP-Dateien in das aktuelle Skript laden und ausführen. Diese Technik verwenden wir für unsere Templates. Die Template-Dateien laden wir dann mit so einer include-Anweisung. Daher können wir in den Template-Dateien PHP-Code verwenden um diverse Variablen ausgeben. Dies heißt aber auch dass wir keine User-Templates laden können bzw. sollten. Wenn wir nun ein System schreiben für ein großes Projekt und für die User eigene Templates anbieten so können sie zwar ganz normal die Variablen ausgeben, aber eben auch beliebigen PHP-Code wie z.B. shell_exec oder mail ausführen. Daher sollten nur die Webautoren Zugriff auf die Templates haben.

Unser Templatesystem wird von der Startdatei index.php verarbeitet. Wie bei allen Projekten wird das Error-Reporting hochgeschraubt und diverse Funktionen, Klassen und Variablen geladen.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'functions.php';
include 
'classes.php';
include 
'variables.php';
?>

Nun benutzen wir die GET-Variable section für ein Array-Include. Dabei speichern wir in dem Array $dateien alle mögliche Dateinamen die durch ein include geladen werden können. Der Index eines Arrayelements gibt an bei welchem Wert von $_GET['section'] die Datei geladen wird.

<?php
// zum Beispiel in der variables.php angelegt
$dateien = array();
$dateien['news'] = 'news.php';
$dateien['forum'] = 'forum.php';
$dateien['downloads'] = 'dl.php';
// ...
?>

Mit isset und file_exists wird nun überprüft ob die entsprechenden Variablen, Arrayelemente und include-Dateien existieren, die dann auch mit include geladen wird.

<?php
$ret 
1// speichert den rückgabewert von include, standardwert 1
if (isset($_GET['section'], $dateien[$_GET['section']])) {
    if (
file_exists('includes/'.$dateien[$_GET['section']])) {
        
$ret = include 'includes/'.$dateien[$_GET['section']]; 
    } else {
        
$ret "Include-Datei konnte nicht geladen werden: 'includes/".$dateien[$_GET['section']]."'";
    }
} else {
    
// default bereich laden, news
    
$ret = include 'includes/'.$dateien['news'];
}
?>

Einmal sehen wir das hier eine Variable $ret verwendet wird. Sie enthält den Rückgabewert der include-Anweisung. Des Weiteren enthält es einige Abfragen ob bestimmte Arrayindizes oder Dateien existieren. Wenn dies nicht der Fall ist so wird der Standardbereich geladen, in diesem Fall ein Newsskript.

Für unsere Include-Dateien definieren wir nun wie der Rückgabewert aussehen muss. Im Normalfall soll die Include-Datei ein assoziatives Array zurückliefern. Das Element mit dem Index filename gibt dabei den Dateinamen vom Template an, was dieses Skript laden möchte. Das Element mit dem Index data ist ein Array und enthält die Daten die im Template verwendet werden können. Dort können dann z.B. die Newsbeiträge gespeichert werden (nur die Daten, keine HTML-Codes). Im Falle eines Fehlers kann die Include-Datei einen String zurückliefern. Dies wird dann als Fehlermeldung interpretiert und soll vom Templatesystem entsprechend ausgegeben werden. Und zum Schluss gibt es noch ein Fall dass die Include-Datei den Wert 1 zurückliefert. Bei so einem Wert wurde einfach vergessen ein Array oder ein String zurückzuliefern da im Skript keine return-Anweisung verwendet wurde. All diese Informationen schreiben wir am Besten mit einem Kommentar in das Skript.

<?php
$ret 
1// speichert den rückgabewert von include, standardwert 1
/*
 * Die Include-Datei muss eine return Anweisung enthalten mit folgenden
 * Werten:
 * - Bei normaler Ausführung
 *   Array('filename' => string, -- Dateiname vom Template
 *         'data' => Array())    -- Array mit Daten für das Template
 * - Bei einem Fehler
 *   string  -- Die Fehlermeldung die angezeigt werden soll.
 */
if (isset($_GET['section'], $dateien[$_GET['section']])) {
    
// ...
?>

Somit weiß man auch was eigentlich programmiert wurde und andere Entwickler können das System verstehen.

Nehmen wir an wir hätten nun solche Include-Dateien und müssen nun das richtige Template ausgeben. Dafür verwenden wir diverse If-Abfragen um den Inhalt der $ret Variable zu prüfen und die entsprechende Datei zu laden.

<?php
if (is_array($ret) and isset($ret['filename'], $ret['data']) and
        
is_string($ret['filename']) and
        
is_array($ret['data'])) {
    
// Gültiger return-Wert
    
if (file_exists($file 'templates/'.$ret['filename'])) {
        
$data $ret['data']; // speicher die Arraydaten in eine Variable $data
                              // die dann im Template verwendet werden kann.
        
include $file;
    } else {
        
// Datei existiert nicht, eine Fehlermeldung anzeigen.
        
$data = array();
        
$data['msg'] = 'Templatedatei "'.$file.'" ist nicht vorhanden.';
        include 
'templates/error.tpl';
    }
} else if (
is_string($ret)) {
    
// include-Datei lieferte eine String zurück, welches eine Fehlermeldung sein soll
    
$data = array();
    
$data['msg'] = $ret;
    include 
'templates/error.tpl';
} else if (
=== $ret) {
    
// return-Anweisung wurde vergessen
    
$data = array();
    
$data['msg'] = 'In der Include-Datei wurde die return Anweisung vergessen.';
    include 
'templates/error.tpl';
} else {
    
// überhaupt ein ungültiger return-Wert
    
$data = array();
    
$data['msg'] = 'Die Include-Datei hat einen ungültigen Wert zurückgeliefert.';
    include 
'templates/error.tpl';
}
?>

Je nach Rückgabewert wird eine entsprechende Templatedatei geladen. Im Normalfall wird die Templatedatei geladen die im Returnwert vom der Include-Datei angegeben wurde und im Fehlerfall wird, wie hier angegeben, die templates/error.tpl-Datei geladen. Da in einem Fehlerfall auch so eine Datei geladen werden kann wird diese nun mit dem folgendem Inhalt erstellt.

<?php
/* Daten:
 *  'msg' - Die Fehlermeldung
 */
?><p class="error">
    <?php echo htmlspecialchars($data['msg']); ?>
</p>

Die Datei enthält einen PHP-Kommentar der beschreibt welche Daten an die Templatedatei übergeben werden (mittels der Variable $data). In diesem Fall nur ein Feld msg die die Fehlermeldung enthält.

Nun schreiben wir unsere Beispiel Include-Datei. Da das Newsskript der Bereich ist der geladen wird wenn ein ungültige Bereich gewählt wurde schreiben wir nun diesen Bereich. Dieses Kapitel behandelt jedoch nicht wie ein Newsskript erstellt wird, daher benutzen wir Pseudo-Daten.

<?php
$a 
= array();
$a['filename'] = 'news.tpl';
$a['data'] = array();

// Newsdaten irgendwie aus einer Datenbank holen
if (false) {
    
// Falls ein Datenbankfehler auftrat, kann hier nur simuliert werden, deswegen if (false)
    
return "Es trat ein Fehler in der Datenbank auf: ...";
}

$news = array();

// Array mit Newsbeiträgen füllen (normalerweise aus der Datenbank)
$newsbeitrag = array();
$newsbeitrag['Titel'] = 'Neue Homepage';
$newsbeitrag['Datum'] = '2008-01-01 00:00:00';
$newsbeitrag['Inhalt'] = 'Pünktlich zum Neujahr starten wir mit einer neuen Homepage.';
$news[] = $newsbeitrag;

$a['data']['news'] = $news;

return 
$a// nicht Vergessen, sonst enthält $ret nur den Wert int(1)
?>

Zu beachten ist das hier je nach Situation ein String oder ein Array zurückgeliefert wird. Dies ist auch die Idee mit den unterschiedlichen Rückgabetypen. Bei einem Fehler kann einfach der Ausdruck return 'Fehlertext'; verwendet werden. Im anderen Fall wird das vorher definierte Array zurückgeliefert. Da dieses Skript eine Template-Datei news.tpl laden möchten erstellen wir nun eine solche Datei, die dann die Newsbeiträge anzeigt.

<?php
/*
 * Daten:
 * 'news' -- Array mit Newsbeiträgen mit folgendem Aufbau
 *           Array(
 *               'Titel', -- Der Titel der News
 *               'Datum', -- Erstelldatum
 *               'Inhalt' -- Inhalt der News
 *           )
 */ 
?>
<?php 
if (count($data['news'])) { ?>
    <?php foreach ($data['news'] as $beitrag) { ?>
    <h2><?php echo htmlspecialchars($beitrag['Titel']); ?></h2>
    <p class="beitrag">
        <span>Geschrieben am <?php echo htmlspecialchars($beitrag['Datum']); ?></span>:
        <?php echo htmlspecialchars($beitrag['Inhalt']); ?>
    </p>
    <?php ?>
<?php 
} else { ?>
<p class="info">
    Es sind keine News vorhanden
</p>
<?php ?>

Am Anfang sieht man wieder einen informellen Kommentar wie das $data-Array aufgebaut ist. Dann folgt das eigentliche Template. Wie man sieht wechselt man hier immer zwischen PHP- und HTML-Modus. Der PHP-Teil dient entsprechend nur dem Programmfluss und den Ausgeben der Variablen. Der HTML-Code bestimmt dann entsprechend die HTML-Struktur. Ein Template-Entwickler muss also nicht komplett PHP lernen, er muss nur Wissen dass er für die Ausgabe immer <?php echo ... ?> schreiben muss. Er sieht diesen Code dann als Platzhalter und so ersetzt auch PHP diese Platzhalter mit den Werten, in diesem Fall aus dem Array.

Neben den Bereich der angezeigt werden soll fehlt noch die komplette HTML-Struktur. Die wird genauso vor und nach dem Laden des Templates mit include geladen. Die komplette index.php sieht dann wie folgt aus.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'functions.php';
include 
'classes.php';
include 
'variables.php';

$ret 1// speichert den rückgabewert von include, standardwert 1
// Laden der Include-Datei
/*
 * Die Include-Datei muss eine return Anweisung enthalten mit folgenden
 * Werten:
 * - Bei normaler Ausführung
 *   Array('filename' => string, -- Dateiname vom Template
 *         'data' => Array())    -- Array mit Daten für das Template
 * - Bei einem Fehler
 *   string  -- Die Fehlermeldung die angezeigt werden soll.
 */
if (isset($_GET['section'], $dateien[$_GET['section']])) {
    if (
file_exists('includes/'.$dateien[$_GET['section']])) {
        
$ret = include 'includes/'.$dateien[$_GET['section']]; 
    } else {
        
$ret "Include-Datei konnte nicht geladen werden: 'includes/".$dateien[$_GET['section']]."'";
    }
} else {
    
// default bereich laden, news
    
$ret = include 'includes/'.$dateien['news'];
}

// Laden des HTML-Kopfs
include 'templates/html_header.tpl';   // Doctype, <html>, <head>, <meta> kram
include 'templates/html_body_tag.tpl'// Analog auch einfach echo "<body>";
include 'templates/menu.tpl';          // falls man z.B. ein Menu haben möchte

// Laden der Template-Datei
if (is_array($ret) and isset($ret['filename'], $ret['data']) and
        
is_string($ret['filename']) and
        
is_array($ret['data'])) {
    
// Gültige Include-Datei
    
if (file_exists($file 'templates/'.$ret['filename'])) {
        
$data $ret['data']; // speicher die Arraydaten in eine Variable $data
                              // die dann im Template verwendet werden kann.
        
include $file;
    } else {
        
$data['msg'] = 'Templatedatei "'.$file.'" ist nicht vorhanden.';
        include 
'templates/error.tpl';
    }
} else if (
is_string($ret)) {
    
// Fehlermeldung
    
$data['msg'] = $ret;
    include 
'templates/error.tpl';
} else if (
=== $ret) {
    
// return wurde vergessen
    
$data['msg'] = 'In der Include-Datei wurde die return Anweisung vergessen.';
    include 
'templates/error.tpl';
} else {
    
// ein Ungültiger Return wert
    
$data['msg'] = 'Die Include-Datei hat einen ungültigen Wert zurückgeliefert.';
    include 
'templates/error.tpl';
}

// HTML footer laden
include 'templates/html_footer.tpl';  // Zeug wie </body> und </html>
?>

Nun fehlen die diversen HTML-Dateien, aber die können nach den eigenen Wünschen geschrieben werden. Wenn nun ein neuer Bereich hinzugefügt werden soll geht man wie folgt vor.

  1. Es wird eine Template-Datei angelegt. Sie enthält ganz oben einen Kommentar damit jeder weiß welche Variablen bzw. Werte in dieses Template übergeben wird. Anhand dieser Information kann dann das eigentliche Template geschrieben werden.

  2. Es wird eine Include-Datei angelegt. Diese erstellt dann ein Array für den Dateinamen und die Daten, füllt den Datenbereich und liefert dann entweder ein String für eine Fehlermeldung zurück (z.B. "Geben sie eine Emailadresse ein") oder das Array mit der die Template-Datei geladen werden soll.

  3. Das $section Array muss entsprechend erweitert werden dass nun auch die Include-Datei geladen werden kann.

4. Vor- und Nachteile dieses Templatesystems

Wie beschrieben kann dieses Templatesystem nicht für ungeprüfte Templatedaten wie Usertemplates verwendet werden. Die Sicherheit ist nicht gewährleistet dass die Benutzer auch nur das echo-Sprachkonstrukt verwenden. Vorteilhaft ist jedoch dass die Ausgabe etwas verzögert ist. Dies mag schlecht klingen jedoch ist es somit möglich ohne Probleme noch die Header-Angaben zu verändern, was mit den Funktionen session_start, header und setcookie entsprechend möglich ist.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Zurück zu Weiter zu
Copyright © bei den OPs von #php.de/QuakeNet Valid XHTML 1.0 Strict Valid CSS!