Tutorial - Teil 3 - Die Template Engine

BasicAvid

Member
Guten Morgen,

in diesem Tutorial zeig ich euch, wie man sich selber eine sehr einfache Template Engine basteln kann. Aber warum eine eigene Template Engine implementieren, wenn es doch genügend davon gibt!?
Tja, eine eigene Engine hat viele Vorteile, sie kann schnell, flexibel, leicht erweiterbar, usw. sein. Nehmen wir z.B. Smarty, bei Smarty muss man mit Tags arbeiten, welche danach ersetzt werden, sprich das Template muss geparset werden was bei großen Templates einfach Zeit kostet. Klar gibt es Caching, damit das ganze schneller wird, aber wieso sollte man alles so umständlich mit Tags machen, wenn doch PHP selbst als Template Sprache entwickelt wurde.

Deshalb zeig ich euch wie man schnell eine einfache Template Engine, die ihren Zweck erfüllt, implementiert.

Die Klasse nennen wir Template:

PHP:
<?php

class Template {

Als nächstes brauchen wir ein Array, indem wir die ganzen Daten speichern.

PHP:
    private $vars = array();

Dann brauchen wir noch eine Variable, in der wir die Instanz vom FrontController speichern.

PHP:
    private $controller = null;

So, nun kommen wir zu den einzelnen Methoden der Klasse.

PHP:
    public function __construct() {
        $this->controller = FrontController::getInstance();
    }

    public function assign($name, $value) {
        $this->vars[$name] = $value;
    }

Im Konstruktor holen wir uns einfach die Instanz des FrontControllers. Mit der assign()-Methode werden die Daten ans Template geschickt, sie dient zum Registrieren von Variablen, auf die wir später im Template zugreifen.

Jetzt kommen wir zur wichtigsten Methode dieser Klasse, nämlich der display()-Methode. Dieser Methode, wird der Name des Templates übergeben welches angezeigt werden soll.

PHP:
	public function display($template) {
                //Der Name des Templates ist so aufgebaut, moduleName.TemplateName
                //Der FrontController splittet uns das ganze auf
		$action = FrontController::getActionName($template);
		$module = FrontController::getModuleName($template);

		$strTemplate = './modules/' . $module . '/templates/' . $action.'.tpl.php';
                
                // Hier wird geschaut, ob es das Template überhaupt gibt.
                // Falls es das Template nicht gibt, wird eine Fehlermeldung generiert.
		if ((! file_exists($strTemplate)) || ! is_readable($strTemplate)) {
			$tmpError = "<div style=\"text-align: center; background-color: #E0E0E0; display: block; color: #000000; width: 100%; height: 400px;\" >\n";
			$tmpError.= "\t<div style=\"padding-top: 180px; font-size: 14px; font-weight: bold;\">\n";
			$tmpError.= "\tDas Template " . $action . ".tpl.php konnte nicht angezeigt werden!\n";
			$tmpError.= "\t</div>\n";
			$tmpError.= "</div>\n";
		}
		
                // Fall kein Fehler aufgetreten ist, wird das Template angezeigt. Ansonsten
                // wird die Fehlermeldung ausgegeben.	
		if (is_null($tmpError)) {
			include($strTemplate);
		} else {
			echo $tmpError;
		}		
		return;        
	}

Das wars dann auch schon mit der display-Methode. Man könnte natürlich jetzt noch hergehen, und die Fehlermeldung als eigenständiges Template erstellen.

Kommen wir zu den letzten beiden Methoden der Klasse.

PHP:
        // Mit dieser Methode, kann man andere Actions aus dem Template heraus
        // includieren. Man könnte so z.B. den Header und Footer in eingenes Template
        // auslagern.
	public function includeAction($completeName) {
		$this->controller->includeAction($completeName);
	}
	
        // Mit dieser Methode holen wir uns die vorher registrierten Variablen.
        // Wird eine Variable nicht gefunden, so wird Null zurück gegeben.
	public function get($name) {
		if (! isset($this->vars[$name])) {
			return null;
		}
		
		return $this->vars[$name];
	}
}
?>

Ok, jetzt zeig ich euch wie man diese Template Engine einsetzt.

Main.tpl.php
PHP:
<?php
$this->includeAction('main.IncMainHeader');
?>

<h1>Dies ist unser erstes Template!</h1>

<p>
Hier wird dann ein Text angezeigt!
<br />
<?php
//Hier holen wir uns eine Variable, die wir in der Action registriert haben.
echo $this->get('text'); 

?>
</p>


<?php
$this->includeAction('main.IncMainFooter');
?>

In diesem Template wird zuerst der Header (stellt eine eigene Action dar) includiert, danach kommen die normalen ausgaben, und zuletzt wird der Footer (stellt auch eine eigene Action dar) includiert.

So weit so gut, jetzt brauchen wir aber noch die Action zum Template.

PHP:
<?php

class Main extends Action {

	public function __toString() {
		return 'Die Main Action';
	}

	public function run() {
                // Hier instanzieren wir die Template Engine
		$tpl = new Template();
		// Der Text den wir ausgeben wollen.
		$text = 'Dies ist ein Text der dem Template übergeben wurde!';
		// Jetzt registrieren wir die Variable im Template
		$tpl->assign('text', $text);
		// Und zu guter letzt zeigen wir das Template an.
		$tpl->display('main.Main');
	}
}
?>

Und das wars dann auch schon, wie Ihr seht ist es nicht sonderlich schwer eine einfache aber denoch effektive Template Engine zu implementieren. Man kann natürlich jetzt noch diverse veränderungen sprich verbesserungen vornehmen, so könnte man z.B. mit den Methoden __set() und __get() arbeiten, welche einem das Leben nochmal erleichtern.

Im Anhang findet Ihr alle benötigten Dateien.
 

Tasmas

New member
schön erklärt!

ist mir beim Überfliegen noch augefallen:
PHP:
$tmpError = "<div style=\"text-align: center; background-color: #E0E0E0; display: block; color: #000000; width: 100%; height: 400px;\" >\n";
            $tmpError.= "\t<div style=\"padding-top: 180px; font-size: 14px; font-weight: bold;\">\n";
            $tmpError.= "\tDas Template " . $action . ".tpl.php konnte nicht angezeigt werden!\n";
            $tmpError.= "\t</div>\n";
            $tmpError.= "</div>\n";

warum das so umständlich? wenns doch mit $tmpError = 'hier template error text'; viel einfacher geht?
 

bad_alloc

Member of Honour
sry wenn ich mich so zu wort melden muss aber das gehört doch EIGENTLICH >hier< rein

PS: glaube du hast soeben die tut sektion gerettet ;) >>>KLICK<<<
 

BasicAvid

Member
warum das so umständlich? wenns doch mit $tmpError = 'hier template error text'; viel einfacher geht?

Damit die Fehlermeldung schön verpackt wird. In meinem Framework hab ich das ganze in ein eigenes Template ausgelagert.

@Wolfy
Stimmt, das hätte eigentlich dort rein gehört. Für die nächsten Tutorials merk ich es mir.
 

easteregg

Member of Honour
in den tpl.php files würde ich aber die shortags nutzen:

PHP:
<?= $this->var  ?>

was equivalent zu

PHP:
<?php echo $this->var;  ?>

ist ;)

und wie machst du das mit dynamischen tabellen content?
da ist es ja nichtmehr möglich, nach dem MVC Prinzip zu arbeiten?
 

valenterry

New member
Original von easteregg
in den tpl.php files würde ich aber die shortags nutzen:

PHP:
<?= $this->var  ?>

was equivalent zu

PHP:
<?php echo $this->var;  ?>

ist ;)

Das ist problematisch, wenn gleichzeitig XML eingesetzt wird. Daher sollte man damit (genause wie mit "<?" statt "<?php") gar nicht erst anfangen.
 

easteregg

Member of Honour
wieso, wenn man das xml und php schön getrennt hält, sollte das doch eigentlich kein problem sein?
 

BasicAvid

Member
und wie machst du das mit dynamischen tabellen content?
da ist es ja nichtmehr möglich, nach dem MVC Prinzip zu arbeiten?

Doch das geht, dafür hab ich dann nämlich HTMLTableOutput Layouter. Die werden dann einfach mit einem Dataset gefüttert.

Falls Interesse besteht, dann kann ich ja mal ein Tutorial drüber schreiben.

<?= $this->var ?>

Ich benutze aus zwei Gründen diese Schreibweise nicht:

1) Ich bin die andere Art gewohnt.
2) Performance Gründe (hierüber lässt sich aber streiten, und es wurde schon oft drüber diskutiert)
 

easteregg

Member of Honour
auf jeden fall besteht daran interesse, weil das ist grad noch so ein problem, wo ich die trennung nicht aufrecht erhalten kann!
 

.wired

New member
Hmm, sehr geil wieder :) Aber folgende Frage: Soweit ich sehe, wird der Inhalt selber jetzt mit Klassen übergeben, richtig? Ist das nicht etwas umständlich? Ich meine, ich würde es gerne ohne Klassen auskommen für den Inhalt... ;)

MfG .wired
 

BasicAvid

Member
Soweit ich sehe, wird der Inhalt selber jetzt mit Klassen übergeben, richtig?

Ja, das siehst Du richtig.

Ist das nicht etwas umständlich?

Warum umständlich? Das ganze wird in Verbindung mit einem FrontController viel einfacher, und vorallem erhalte ich mir so das MVC (Model - View - Controller) Prinzip.

Ich meine, ich würde es gerne ohne Klassen auskommen für den Inhalt...

Warum willst Du ohne Klassen auskommen?
 

.wired

New member
Hmm ich finde es halt einfacher wenn ich Platzhalter einbaue wie z.B.

Code:
<body>
<div id="root">
  <div id="text">{Platzhalter}</div>
</div>

</body>

Oder Ähnliches. Sonst müsste ich, sollte ich beispielweise vorhaben ein CMS zu bauen - was ich auch tatsächlich vorhabe ;) - für jede neue Seite Klassen schreiben.
Bei meiner Lösung würde ich einfach eine Datei machen mit

Code:
$text = <<< DOC
Bla bla bla dies ist mein Platzhaltertext
DOC;

echo $text;

// oder mit Querys aus einer Datenbank oder sonstwie... und dann ersetzen lassen

Das Problem bei mir ist einfach, dass ich da ganz ehrlichgesagt noch nicht ganz durchblicke durch dein System, weswegen ich wahrscheinlich die (mir zumindest) einfachere Lösung bevorzuge. Ich will aber nicht mit dir darum streiten, dass deine Lösung vielleicht wirklich besser ist nach dem mvc-Prinzip... Nur ist es mir halt noch etwas zu kompliziert solange ich es nicht verstehe ;)

MfG .wired
 

BasicAvid

Member
Oder Ähnliches. Sonst müsste ich, sollte ich beispielweise vorhaben ein CMS zu bauen - was ich auch tatsächlich vorhabe Augenzwinkern - für jede neue Seite Klassen schreiben.

Das musst Du nicht machen, Du kannst auch eine CMS Klasse erstellen die einfach den jeweiligen Content aus der DB holt, und ans Template übergibt.

Das Problem bei mir ist einfach, dass ich da ganz ehrlichgesagt noch nicht ganz durchblicke durch dein System, weswegen ich wahrscheinlich die (mir zumindest) einfachere Lösung bevorzuge. Ich will aber nicht mit dir darum streiten, dass deine Lösung vielleicht wirklich besser ist nach dem mvc-Prinzip... Nur ist es mir halt noch etwas zu kompliziert solange ich es nicht verstehe

Aber ich will streiten :D ;). Nö, jeder sollte seine Sachen so umsetzen, wie er es denkt und für Ihn am einfachsten ist.
 

easteregg

Member of Honour
wo wir grad dabei sind, ich hab auch schonmal überlegt, ob ich die kompletten texte in verschiedenen sprachen alle aus der datenbank abfrage, aber ich hab irgendwie angst, dass das zu viel load wird?
was meinst du dazu?
 

BasicAvid

Member
Original von easteregg
wo wir grad dabei sind, ich hab auch schonmal überlegt, ob ich die kompletten texte in verschiedenen sprachen alle aus der datenbank abfrage, aber ich hab irgendwie angst, dass das zu viel load wird?
was meinst du dazu?

Das musst Du mir mal genauer erklären, willst Du einfach eine Mehrsprachige Seite oder nur einzelne Texte in einer andere Sprache?
 

easteregg

Member of Honour
nuja ich würde gerne meine seite von den usern direkt übersetzen lassen.

das ganze stell ich mir folgender maßen vor:

ein user fragt an als übersetzer zu dienen,
ich setze das entsprechende rechtelevel in meinem acp
die user bekommen zugriff auf eine oberfläche wo sie die texte bequem per formular ändern können
ich kann problemlos verschiedene sprachen anlegen und entfernen bzw. auf verschiedene "versionen" der einzelnen sprachen zurückgreifen (zb um irgendwas rückgängig zu machen)

und dieses ganze system würd ich über die datenbank laufen lassen und zu jeder seite fragt der dann eben ab

select * form sprache where lang = "de" and site = "start";

und das tplsystem füttert das template damit.

ich will halt das gefummel mit ellen langen phpfiles wo einfach nur nen dickes $lang array drin schlummert vermeiden, habe aber angst vor der datenbanklast!
 

BasicAvid

Member
ich will halt das gefummel mit ellen langen phpfiles wo einfach nur nen dickes $lang array drin schlummert vermeiden, habe aber angst vor der datenbanklast!

Vor der DB-Last brauchst keine Angst haben, die hättest Du auch, wenn Du einfach nur Daten aus DB lesen würdest. Um die DB-Last zu verringern könntest Du natürlich das Template-System um ein Caching-System erweitern, desweiteren sollte man seine Query's optimal schreiben und SELECT * Query's vermeiden wo es geht.
 

easteregg

Member of Honour
jopp, dass mit dem select * war jetzt nur zum verdeutlichen, also bruach ich mir da keine platte machen?
gut, das beruhigt mich, wenn mir das ein erfahrener anwender sagt :)
 

.wired

New member
@BasicAvid:

Ich komme jetzt vielleicht etwas dämlich rüber, aber ich bin noch nicht allzu lange auf dem Gebiet von PHP ;). Also wie genau würde ich jetzt nach deiner Templateengine z.B. den Hauptinhalt (oder auch Footer/Header) je nach Frontcontroller variieren lassen, sprich, dass bei bestimmten Anfragen ein völlig anderer Seitenaufbau kommt?
Und noch eine Frage habe ich: Wenn ich die URL mit MOD-Rewrite umschreiben lasse, so dass nicht mehr ersichtlich ist, dass es eine PHP-Datei ist, wie kann der Controller dann auslesen, welche Instanz der Seite angezeigt werden soll? Also dann habe ich einfach mal als Beispiel: http://meineseite.de/impressum oder so ähnlich... ?

MfG .wired
 
Oben