Die Trennung von Ausgabe und Logik ist ein Grundsatz den man beherzigen sollte. Viele CMS bieten Template-Engines an um dies zu erreichen. WordPress bildet in diesen Punkt eine Ausnahme. Es bringt keine eigene Template-Engine mit, sondern setzt auf PHP als Template-Engine. Grundsätzlich keine schlechte Idee, denn PHP ist ursprünglich eine Template-Sprache. Aber die Möglichkeiten die PHP bietet führen grundsätzlich dazu das Logik und Ausgabe munter miteinander vermischt werden, was wiederum zu arg unleserlichen und vor allem unflexiblen Code führt.
In diesen Artikel geht es nicht ausschließlich um WordPress. Das hier gezeigte lässt sich aber sehr leicht auf WordPress anpassen und viele WordPress-Themes haben es bitter nötig sich hinter der Fassade auch mal etwas aufzuhübschen.
Ich hatte in meinem letzten Artikel bereits kurz angesprochen das es eine gute Idee ist Logik und Ausgabe voneinander zu trennen. Eins der besten Argumente für die Trennung von Logik und Ausgabe ist wohl das, dass man einmal geschriebenen Code erneut verwenden kann, man spart sich so viel Arbeit. Aber auch die Übersichtlichkeit steigt, was z.B. die Fehlersuche vereinfacht. Und nicht zu Letzt ist es leichter im Team zu arbeiten. So kann sich einer um die Logik kümmern während der andere sich ausschließlich um das Design (Ausgabe) kümmert.
Das hier im Artikel beschriebene dürfte für PHP-Profis nichts neues sein. Für den einen oder anderen meiner Leser (sofern vorhanden ) kann es aber durchaus interessant sein. Fangen wir also mal ganz locker an.
Ran an den Speck
Ziel soll es sein das in der einen Datei ein Template (Vorlage) steht, während in einer anderen Datei dieses Template mit Daten gefüllt wird. Will man die Trennung noch weiter voran treiben, so legt man weitere Dateien an die sich z.B. um die Beschaffung der Daten kümmern oder um die Ausgabe der Daten (HTML, RSS, Druck, usw.).
Wir wollen uns aber mal darauf beschränken ein einfaches Template mit einem ebenso einfachem Array zu füllen. Bei dem Template soll es sich um eine Liste handeln. In WordPress zum Beispiel ist fast alles eine Liste. Ob nun die Artikel auf der Startseite, die Blogroll, eine Aufzählung der letzten Kommentare, Menüs, usw. Eigentlich besteht WordPress zu gefühlten 90% aus Listen. Listen dürften als Beispiel also ganz brauchbar sein.
Schauen wir uns mal kurz die Anatomie einer Liste in HTML an. Sie besteht im Prinzip aus zwei Teilen. Den äußeren Teil der die Liste definiert und den inneren Teil der die eigentliche Liste darstellt. In HTML gibt es drei native Listentypen: <ol> (Ordered List), <ul> (Unordered List) und <dl> (Defenition List). Da die <dl>-Liste etwas komplexer ist nehmen wir uns mal die <ol>- und <ul>-Listen vor. Als “Ersatz” für die <dl>-Liste möchte ich eine einfache <div>-Liste dazu nehmen. Die <div>-Liste besteht aus einem äußeren Div-Container und <p>-Tags für die Listenelemente.
Nachdem nun klar ist um welche Listen es geht, brauchen wir noch ein paar Daten für die Liste. Die meisten Daten werden uns als Array oder Objekt zur Verfügung gestellt. Wir können ja mal annehmen wir hätten gerade eine Datenbankabfrage gemacht und wollten nun das Ergebnis als Liste darstellen. Der Einfachheit halber begnügen wir uns mit einem eindimensionalen Array das lediglich die Zahlwörter von Eins bis Drei enthält.
Aus Daten wird eine Liste
Das Problem ein Array als Liste darzustellen ist recht einfach zu lösen. Äußeren Teil ausgeben, das Array durchlaufen und den inneren Teil erstellen, inneren Teil in den Äußern Teil einbetten und fertig ist die Liste.
<?php
require_once 'class-formatter.php';
class Lister extends Formatter
{
public function get_list( $data = array() ){
$inner = new stdClass();
$values = new stdClass();
foreach( $data as $key => $value ){
$values->key = $key;
$values->item = $value;
$inner->inner .= self::sprintf( '<li>%item%', $values );
}
return self::sprintf( '<ol>%inner%</ol>', $inner );
}
}
$data = array( 'Eins', 'Zwei', 'Drei' );
$list = new Lister();
echo $list->get_list( $data );
Um es gleich ordentlich zu machen verwende ich eine Klasse. Diese Klasse erweitert den
Formatter den ich bereits vorgestellt habe und erbt dadurch die Methoden
printf()
und
sprintf()
. Zu der Verarbeitung der Key-Value-Paare in der Foreach-Schleife komme ich später noch.
Wenn wir jetzt aber anstatt der Ordered List eine Unordered List erstellen wollen, schreiben wir uns eine neue Klasse!? Das wäre ja nicht Sinn der Aktion, wir wollen ja eine flexible Lösung haben.
Es fehlt die Möglichkeit einen Listen-Typ auszuwählen. Was dafür geändert werden muss, sind die Template-Strings mit dem HTML-Code. Die einfachste Methode um die Templates für verschiedene Listen-Typen zu speichern ist ein Array. Wir benötigen also ein Array das zu jeden Listen-Typ (ol, ul und div) zwei Template-Strings speichert (den inneren Teil und den äußeren Teil).
<?php
require_once 'class-formatter.php';
class Lister extends Formatter
{
public $templates = array();
public function __construct(){
$this->templates = $this->get_templates();
}
public function get_templates(){
$this->templates = array(
'ol' => array(
'outer' => "<ol>n%inner%</ol>n",
'inner' => "<li>%item%</li>n"
),
'ul' => array(
'outer' => "<ul>n%inner%</ul>n",
'inner' => "<li>%item%</li>n"
),
'div' => array(
'outer' => "<div>n%inner%</div>n",
'inner' => "<p>%item%</p>n"
),
);
}
public function get_list( $type = '', $data = array() ){
// get templates if not already set
if( empty( $this->templates ) )
$this->get_templates();
// check if the requested list-typ is defined
if( ! in_array( $type, array_keys( $this->templates ) ) )
return FALSE;
// create list
$inner = new stdClass();
$values = new stdClass();
foreach( $data as $key => $value ){
$values->key = $key;
$values->item = $value;
$inner->inner .= self::sprintf( $this->templates[$type]['inner'], $values );
}
return self::sprintf( $this->templates[$type]['outer'], $inner );
}
}
$data = array( 'Eins', 'Zwei', 'Drei' );
$list = new Lister();
echo $list->get_list( 'ol', $data );
echo $list->get_list( 'ul', $data );
echo $list->get_list( 'div', $data );
Wunderbar. Jetzt können wir ganz einfach durch Angabe eines Listen-Types und eines Arrays eine Liste erzeugen. Das spart uns schon mal viel Arbeit, denn anstatt für jede Liste eine eigene Foreach-Schleife zu schreiben, benutzen wir einfach die Instanz unserer Klasse.
Und wenn wir jetzt der Liste noch eine CSS-Klasse mitgeben wollen, dann schreiben wir uns eine andere Klasse die das kann!? Vielleicht erweitern wir einfach unsere Klasse dahingehend das sie nicht so starr ist.
Noch mehr Flexibilität
Die fest eingebauten Templates sind zu starr für unsere Zwecke, irgendwie müssen die raus aus der Klasse. Zu Anfang habe ich ja schon geschrieben das die Templates in die eine Datei, die Klasse die aus den Templates fertiges HTML macht in eine andere Datei soll. Das die Klasse oben einen Konstruktor hat, hat seinen Sinn. Denn das ist unser nächster Ansatzpunkt.
Wir schieben einfach die Methode get_templates() in eine Template-Klasse und übergeben der Lister-Klasse ein Objekt das aus der Template-Klasse erzeugt wurde. Damit können wir innerhalb der Lister-Klasse auf die Templates zugreifen. Alles klar soweit? Nicht? Einfach das Beispiel anschauen.
<?php
require_once 'class-formatter.php';
require_once 'class-templates.php';
interface Templates
{
public function get_templates();
}
class Lister extends Formatter
{
public $templates_object = NULL;
public $templates = array();
public function __construct( Templates $templates ){
$this->templates_object = &$templates;
$this->get_templates();
}
protected function get_templates(){
if( NULL === $this->templates_object )
throw new Exception( 'No templates defined' );
$this->templates = &$this->templates_object->get_templates();
}
public function get_list( $type = '', $data = array() ){
// get templates if not already set
if( empty( $this->templates ) )
$this->get_templates();
// check if the requested list-typ is defined
if( ! in_array( $type, array_keys( $this->templates ) ) )
return FALSE;
// create list
$inner = new stdClass();
$values = new stdClass();
foreach( $data as $key => $value ){
$values->key = $key;
$values->item = $value;
$inner->inner .= self::sprintf( $this->templates[$type]['inner'], $values );
}
return self::sprintf( $this->templates[$type]['outer'], $inner );
}
}
$data = array( 'Eins', 'Zwei', 'Drei' );
$list = new Lister( new Simple_List_Templates );
echo $list->get_list( 'ol', $data );
echo $list->get_list( 'ul', $data );
echo $list->get_list( 'div', $data );
<?php
class Simple_List_Templates implements Templates
{
public function get_templates(){
return array(
'ol' => array(
'outer' => "<ol>n%inner%</ol>n",
'inner' => "<li>%item%</li>n"
),
'ul' => array(
'outer' => "<ul>n%inner%</ul>n",
'inner' => "<li>%item%</li>n"
),
'div' => array(
'outer' => "<div>n%inner%</div>n",
'inner' => "<p>%item%</p>n"
),
);
}
}
Ich möchte zur Erklärung schnell mal über die Klasse Lister fliegen. Am Anfang sehen wir ein Interface, das hat in sofern seinen Sinn, als dass man damit bestimmte Bedingungen festlegen kann. So wird im Konstruktor der Lister-Klasse festgelegt das ein Objekt übergeben werden muss das aus einer Klasse erstellt wurde die das Interface Template
imlementiert. Damit gehen wir sicher, das dass übergebene Objekt über die Methode get_templates()
verfügt. Würden wir versuchen ein Objekt zu übergeben das nicht über diese Methode verfügt, sprich nicht das Interface Template implementiert, würden wir beim Aufruf der Methode get_templates()
eine Fehlermeldung bekommen. Damit gehen wir zwar nicht sicher das die Template-Klasse uns beim Aufruf der Methode get_templates()
auch ein Array mit Templates zurück gibt, es hilft aber unter Umständen sehr bei der Fehlersuche wenn mal wieder etwas nicht funktioniert.
Im Konstruktor sehen wir das bei der Parameterübergabe darauf gepocht wird das dass übergeben Objekt das Interface Templates implementieren muss. Das übergebene Objekt speichern wir uns in einer Klassen-Variablen damit wir in anderen Methoden ggf. darauf zugreifen können. Zum Schluss holen wir uns noch die heiß begehrten Templates von der Template-Klasse.
Als nächstes sehen wir dann eine get_templates() Methode. Die Methode sollte ja eigentlich ausgelagert werden. Hier steht sie quasi als Wrapper damit man z.B. noch weitere Kontrollen oder ähnliches einbauen kann. Natürlich hätte man auch z.B. in der Methode get_list() direkt auf die gespeicherte Instanz der Template-Klasse zugreifen können. Aber wir wollen uns ja mal angewöhnen gleich von Anfang an vernünftigen Code zu schreiben. Auch in Beispielen.
Über get_list() muss ich hoffentlich keine Worte mehr verlieren. Templates abholen, Variablen ggf. verifizieren und desinfizieren, Liste basteln und ausgeben.
Ein konkretes Beispiel
Im Prinzip sind wir schon fertig und haben alle unsere gesteckten Ziele erreicht. Die Templates (Ausgabe) stehen in der einen Datei, die Verarbeitung der Daten (Logik) in einer anderen. Beides ist sauber voneinander getrennt. Kommen wir noch mal kurz darauf zurück warum das so sinnvoll sein kann.
Nehmen wir einfach mal den Fall das wir relativ viele Templates haben in denen eine Footer definiert ist . Der könnte nach traditionellem HTML in etwa so aussehen:
<div class="footer">
<p>Copyright © 2001-2012</p>
</div>
Nun kommt unser Kunde der gelesen hat das HTML5 der letzte Schrei ist und er möchte nun seine Webseite auf HTML5 umgestellt haben weil er befürchtet das sie ansonsten morgen nicht mehr erreichbar ist. Dazu müssten auch alle Footer-Bereiche nach HTML5-Footer konvertiert werden. Aus den Code oben müsste also in etwa folgendes werden:
<footer>
<p>Copyright © 2001-2012</p>
</footer>
Und das dann nicht nur auf einer Seite, sondern auf allen. Und nicht nur die Footer, sondern auch die Bereiche die eine Section, Navigation, Artikel, usw. darstellen. Der richtige Zeitpunkt um eine Reihe böser Flüche los zu werden, denn das bedeutet tagelanges Wühlen in unübersichtlichen Codezeilen.
Nun werfen wir einen Blick auf unsere Template-Klasse. Dort habe ich eine “Div-Liste” definiert mit der man z.B. solche Footer-Bereiche erzeugen kann. Anstatt nun den kompletten Code neu zu schreiben, tauscht man einfach seine Template-Klasse aus. Aus Simple_List_Templates
wird dann z.B. HTML5_List_Templates
. Das macht die Sache doch erheblich einfacher als in jeder HTML-Datei Änderungen manuell durchzuführen.
Mit etwas Überlegung kommt man dann auch recht schnell darauf das es eine relativ blöde Idee ist die Template-Klasse beim erzeugen der Lister-Klasse fest zu verdrahten. Zu den Problem komme ich in einen anderen Artikel, dort will ich noch mal auf ein paar Feinheiten eingehen. Dann gehe ich auch noch mal auf die Key-Value-Paare in der Foreach-Schleife ein.
Aber wir sehen schon so, dass man mit der Trennung von Ausgabe und Logik für die Zukunft programmiert. Man kann sowohl die Logik als auch die Templates in verschiedenen Projekten erneut verwenden. Zudem sind Änderungen nicht mehr der Weltuntergang weil man unzählige Dateien durchsuchen und kontrollieren muss. Ich hoffe dem einen oder anderen einen kleinen Denkanstoß gegeben zu haben und wünsche viel Spaß beim selber Ausprobieren.
Keine Kommentare | Schreibe einen Kommentar