PHP-Templating hat oft einen schlechten Ruf, weil es minderwertigen Code begünstigt – aber das muss nicht sein. Schauen wir uns an, wie PHP-Projekte eine grundlegende Model, View, Controller (MVC)-Struktur erzwingen können, ohne auf eine speziell dafür entwickelte Templating-Engine angewiesen zu sein.
Aber zuerst eine sehr kurze PHP-Geschichtsstunde
Die Geschichte von PHP als Werkzeug für HTML-Templating ist voller Wendungen.
Eine der ersten Programmiersprachen, die für HTML-Templating verwendet wurde, war C, aber es stellte sich schnell als mühsam und für die Aufgabe generell ungeeignet heraus.
Rasmus Lerdorf schuf PHP mit diesem Gedanken im Hinterkopf. Er war nicht gegen die Verwendung von C für die Back-End-Geschäftslogik, wollte aber eine bessere Möglichkeit, dynamisches HTML für das Front-End zu generieren. PHP wurde ursprünglich als Templating-Sprache konzipiert, nahm aber im Laufe der Zeit weitere Funktionen auf und entwickelte sich schließlich zu einer vollwertigen Programmiersprache.
Die einzigartige Fähigkeit von PHP, zwischen Programmier- und HTML-Modus zu wechseln, erwies sich als recht praktisch, verleitete Programmierer aber auch dazu, unwartbaren Code zu schreiben – Code, der Geschäftslogik und Templating-Logik vermischte. Eine PHP-Datei konnte mit etwas HTML-Templating beginnen und dann ohne Vorwarnung in eine erweiterte SQL-Abfrage übergehen. Diese Struktur ist verwirrend zu lesen und erschwert die Wiederverwendung von HTML-Vorlagen.
Im Laufe der Zeit fand die Webentwicklungs-Community immer mehr Wert darin, eine strenge MVC-Struktur für PHP-Projekte zu erzwingen. Templating-Engines wurden geschaffen, um Views effektiv von ihren Controllern zu trennen.
Um diese Aufgabe zu erfüllen, weisen Templating-Engines typischerweise die folgenden Merkmale auf:
- Die Engine ist bewusst unterpowert für Geschäftslogik. Wenn ein Entwickler beispielsweise eine Datenbankabfrage durchführen möchte, muss er diese Abfrage im Controller ausführen und dann das Ergebnis an die Vorlage übergeben. Eine Abfrage mitten im HTML der Vorlage ist keine Option.
- Die Engine kümmert sich im Hintergrund um gängige Sicherheitsrisiken. Selbst wenn ein Entwickler die Benutzereingabe nicht validiert und sie direkt an die Vorlage übergibt, werden gefährliche HTML-Elemente oft automatisch maskiert.
Templating-Engines sind heute ein fester Bestandteil vieler Webtechnologie-Stacks. Sie sorgen für wartbarere und sicherere Codebasen, was kaum überrascht.
Es ist jedoch auch möglich, HTML-Templating mit reinem PHP zu handhaben. Dies kann aus verschiedenen Gründen wünschenswert sein. Vielleicht arbeiten Sie an einem Legacy-Projekt und möchten keine zusätzlichen Abhängigkeiten einführen, oder Sie arbeiten an einem sehr kleinen Projekt und möchten die Dinge so schlank wie möglich halten. Oder vielleicht liegt die Entscheidung gar nicht in Ihrer Hand.
Anwendungsfälle für PHP-Templating
Ein Bereich, in dem ich oft reines PHP-Templating implementiere, ist WordPress, das standardmäßig keine starre MVC-Struktur erzwingt. Ich finde, dass die Einführung einer vollständigen Templating-Engine übertrieben ist, aber ich möchte trotzdem meine Geschäftslogik von meinen Vorlagen trennen und meine Views wiederverwendbar machen.
Was auch immer Ihr Grund ist, die Verwendung von reinem PHP zur Definition Ihrer HTML-Vorlagen ist manchmal die bevorzugte Wahl. Dieser Beitrag untersucht, wie dies auf einigermaßen professionelle Weise geschehen kann. Der Ansatz stellt einen praktischen Mittelweg zwischen dem Spaghetti-Code-Stil, für den PHP-Templating berüchtigt geworden ist, und dem Ansatz ohne Logik, der bei formalen Templating-Engines verfügbar ist.
Tauchen wir in ein Beispiel ein, wie ein grundlegendes Templating-System in die Praxis umgesetzt werden kann. Wieder verwenden wir WordPress als Beispiel, aber dies könnte durch eine reine PHP-Umgebung oder viele andere Umgebungen ersetzt werden. Und Sie müssen kein WordPress-Kenner sein, um folgen zu können.
Ziel ist es, unsere Views in Komponenten aufzuteilen und eine klare Trennung zwischen Geschäftslogik und HTML-Vorlagen zu schaffen. Insbesondere werden wir eine View erstellen, die ein Raster von Karten anzeigt. Jede Karte zeigt den Titel, den Auszug und den Autor eines aktuellen Beitrags an.
Schritt 1: Daten für die Darstellung abrufen
Der erste Schritt ist das Abrufen der Daten, die wir in unserer View anzeigen möchten. Dies kann die Ausführung einer SQL-Abfrage, die Verwendung des ORM oder von Hilfsfunktionen Ihres Frameworks/CMS zum indirekten Zugriff auf Ihre Datenbank beinhalten. Es kann auch das Senden einer HTTP-Anfrage an eine externe API oder das Sammeln von Benutzereingaben aus einem Formular oder einer Query-String beinhalten.
In diesem Beispiel verwenden wir die WordPress-Hilfsfunktion get_posts, um einige Beiträge abzurufen, die auf unserer Homepage angezeigt werden sollen.
<?php // index.php
$wp_posts = get_posts([
'numberposts' => 3
]);
Nun haben wir Zugriff auf die Daten, die wir im Kartenraster anzeigen möchten, müssen aber noch einige zusätzliche Arbeiten erledigen, bevor wir sie an unsere View übergeben können.
Schritt 2: Daten für das Templating vorbereiten
Die Funktion get_posts gibt ein Array von WP_Post-Objekten zurück. Jedes Objekt enthält den Beitragstitel, den Auszug und die Autoreninformationen, die wir benötigen. Wir wollen unsere View jedoch nicht an den WP_Post-Objekttyp koppeln, da wir möglicherweise an anderer Stelle im Projekt andere Arten von Daten auf unseren Karten anzeigen möchten.
Stattdessen ist es sinnvoll, jedes Post-Objekt proaktiv in einen neutralen Datentyp, wie ein assoziatives Array, zu konvertieren.
<?php // index.php
$wp_posts = get_posts([
'numberposts' => 3
]);
$cards = array_map(function ($wp_post) {
return [
'heading' => $wp_post->post_title,
'body' => $wp_post->post_excerpt,
'footing' => get_author_name($wp_post->post_author)
];
}, $wp_posts);
In diesem Fall wird jedes WP_Post-Objekt mithilfe der Funktion array_map in ein assoziatives Array umgewandelt. Beachten Sie, dass die Schlüssel für jeden Wert nicht title, excerpt und author sind, sondern allgemeinere Namen erhalten: heading, body und footing. Dies tun wir, weil die Kartenraster-Komponente für die Unterstützung beliebiger Daten gedacht ist, nicht nur für Beiträge. Sie könnte genauso gut verwendet werden, um ein Raster von Testimonials mit einem Zitat und dem Namen eines Kunden anzuzeigen.
Nachdem die Daten ordnungsgemäß vorbereitet sind, können sie nun in unsere render_view-Funktion übergeben werden.
<?php // index.php
// Data fetching and formatting same as before
render_view('cards_grid', [
'cards' => $cards
]);
Natürlich existiert die Funktion render_view noch nicht. Definieren wir sie.
Schritt 3: Eine Render-Funktion erstellen
// Defined in functions.php, or somewhere else that will make it globally available.
// If you are worried about possible collisions within the global namespace,
// you can define this function as a static method of a namespaced class
function render_view($view, $data)
{
extract($data);
require('views/' . $view . '.php');
}
Diese Funktion akzeptiert den Namen der gerenderten View und ein assoziatives Array, das die anzuzeigenden Daten repräsentiert. Die Funktion `extract` nimmt jedes Element des assoziativen Arrays und erstellt eine Variable dafür. In diesem Beispiel haben wir jetzt eine Variable namens $cards, die die in index.php vorbereiteten Elemente enthält.
Da die View in ihrer eigenen Funktion ausgeführt wird, erhält sie ihren eigenen Scope. Das ist praktisch, da wir einfache Variablennamen ohne Angst vor Kollisionen verwenden können.
Die zweite Zeile unserer Funktion gibt die View aus, die dem übergebenen Namen entspricht. In diesem Fall sucht sie nach der View in views/cards_grid.php. Erstellen wir diese Datei.
Schritt 4: Vorlagen erstellen
<?php /* views/cards_grid.php */ ?>
<section>
<ul>
<?php foreach ($cards as $card) : ?>
<li>
<?php render_view('card', $card) ?>
</li>
<?php endforeach; ?>
</ul>
</section>
Diese Vorlage verwendet die gerade extrahierte Variable $cards und rendert sie als ungeordnete Liste. Für jede Karte im Array rendert die Vorlage eine Unteransicht: die einzelne Kartenansicht.
Das Vorhandensein einer Vorlage für eine einzelne Karte ist nützlich, da es uns die Flexibilität gibt, eine einzelne Karte direkt zu rendern oder sie an anderer Stelle im Projekt in einer anderen View zu verwenden.
Definieren wir die grundlegende Kartenansicht.
<?php /* views/card.php */ ?>
<div class="card">
<?php if (!empty($heading)) : ?>
<h4><?= htmlspecialchars($heading) ?></h4>
<?php endif;
if (!empty($body)) : ?>
<p><?= htmlspecialchars($body) ?></p>
<?php endif;
if (!empty($footing)) : ?>
<span><?= htmlspecialchars($footing) ?></span>
<?php endif; ?>
</div>
Da die an die Renderfunktion übergebene Variable $card die Schlüssel für heading, body und footing enthielt, sind Variablen mit denselben Namen jetzt in der Vorlage verfügbar.
In diesem Beispiel können wir einigermaßen sicher sein, dass unsere Daten frei von XSS-Gefahren sind, aber es ist möglich, dass diese View zu einem späteren Zeitpunkt mit Benutzereingaben verwendet wird. Daher ist es ratsam, jeden Wert durch htmlspecialchars zu leiten. Wenn ein Skript-Tag in unseren Daten vorhanden ist, wird es sicher maskiert.
Es ist auch oft hilfreich zu prüfen, ob jede Variable einen nicht leeren Wert enthält, bevor sie gerendert wird. Dies ermöglicht es, Variablen wegzulassen, ohne leere HTML-Tags in unserem Markup zu hinterlassen.
PHP-Templating-Engines sind großartig, aber manchmal ist es angemessen, PHP für das zu verwenden, wofür es ursprünglich entwickelt wurde: dynamisches HTML generieren.
Templating in PHP muss nicht in unwartbaren Spaghetti-Code münden. Mit ein wenig Voraussicht können wir ein grundlegendes MVC-System erreichen, das Views und Controller voneinander trennt, und das mit überraschend wenig Code.
Alter Weg, um eine Vorlage zu erstellen, aber funktioniert wirklich gut! Ich bevorzuge die Verwendung von Blade oder Twig, aber wenn Sie Ihr eigenes Mini-Framework haben, kann dieser Weg perfekt funktionieren. :)
Sie müssen also daran denken,
htmlspecialcharsjedes Mal zu verwenden, sonst haben Sie eine potenzielle XSS-Schwachstelle. Mit anderen Worten, es ist standardmäßig unsicher, was bedeutet, dass es unsicher ist. Sicherlich können wir nicht empfehlen, "Sie müssen sich nur jedes Mal X merken, und übrigens macht X Ihren Code weniger lesbar" als Weg, um sichere, zuverlässige Software zu bauen?Genau aus diesem Grund können Sie PHP nicht als Vorlagensprache verwenden, was sehr bedauerlich ist, da es das Einzige ist, wofür es wirklich entwickelt wurde.
Könnte PHP das beheben? Nun, es scheint, dass Rasmus glaubt, die Lösung für XSS sei im Wesentlichen dasselbe wie 'magic quotes' (erinnern Sie sich daran?) – hier beschrieben und analysiert: https://lukeplant.me.uk/blog/posts/why-escape-on-input-is-a-bad-idea/
Mein Problem mit Ihrem Argument ist, dass *jede* Templating-Sprache die Anzeige von HTML erlaubt (soweit ich weiß). Daher ist jedes Templating-System genauso anfällig.
Ich benutze Mustache, auch mit PHP, weil ich möchte, dass sich Designer auf das Design konzentrieren und nicht auf den Code. Und sie können es nicht gut genug machen, um eine Vorlage mit PHP zu erstellen. Und diejenigen, die das können, sind keine guten Designer.
Eine Aufteilung der Rollen ist die beste langfristige Lösung. Diejenigen von uns, die beides können (Design und Code), können sich um XSS-Probleme kümmern.
Ein besserer Ansatz ist normalerweise, die Daten zu maskieren, bevor sie die Vorlage erreichen. Die Vorlage sollte eigentlich nicht die Entscheidung treffen, was maskiert werden muss und was nicht. Sie sollte davon ausgehen, dass alle Daten für die Anzeige geeignet sind.
Auf die gleiche Weise, wie ich den Inhalt normalerweise nicht in ein Absatz-Tag einpacken würde, sollte der aufrufende Code entscheiden können, ob der Inhalt ein einzelner Absatz ist oder ob an dieser Stelle etwas mehr benötigt wird. Wenn ich Code wie diesen implementieren würde, würde die aufrufende Funktion den Inhalt normalerweise umschließen und maskieren, bevor sie ihn an die Vorlage übergibt.
Ich benutze die Smarty-Templating-Engine für PHP und sie ist ziemlich gut.
Wunderbar. Deshalb liebe ich PHP, einfach und effektiv, wenn man es richtig einsetzt.
Nette Idee. Alternativ ist Timber eine großartige Lösung für einen robusten Pseudo-MVC-Ansatz für WordPress. Es verwendet Twig für das Templating, was im Vergleich zur Notwendigkeit, die Vorlage mit offenen/geschlossenen PHP-Tags und Echo-Anweisungen zu übersäen, großartig ist. Es bietet auch einige exzellente Werkzeuge für die Bildgrößenänderung, Quelltext-Sets und Caching usw. und ermöglicht Ihnen, nahtlos WP-Hooks und Funktionen usw. aufzurufen.
Wenn Timber zu viele Funktionen hat oder zu viel Refactoring für eine bestehende Codebasis wäre, können Sie die Vorteile von Twig für das Templating immer noch nutzen, indem Sie Twig einfach als Abhängigkeit verwenden – Sie können dann Ihre Kontextdaten an die Render/Compile-Funktion übergeben.
Die Blade-View in Laravel ist eines der besten Beispiele für das MVC-Muster.
Legen Sie die
$cards-Vorbereitung in einen Callback.Beispiel
function callModel() {return function() { ...}
}
Diese Funktion ruft die Datenbereitstellungs-Engine auf, z. B. das Modell.
Dann aus dem HTML-Dokument aufrufen
$cards= ($model->callModel())();Beachten Sie, dass der HTML-Teil keine Erweiterung des Modells (Datenbereitstellungs-Engine) ist, sondern eine unabhängige Schicht der Anwendung.
HTML ist die entkoppelte Karosserie des Autos. Die Kommunikation mit dem Motor erfolgt durch Auslösen des Schlüssels.
Keine Template-Engine mit
extract()usw. benötigt. Bauen Sie einfach die HTML-Struktur mit PHP'sincludeund rufen Sie das Modell aus dem HTML auf, wo immer Sie Daten benötigen.Ich schaue nie wieder auf Template-Engines zurück.
Sieht im Vergleich zu aktuellen Templating-Engines für mich immer noch nach Spaghetti aus.
<?phpstört immer noch die Lesbarkeit. Zumindest für mich.Ich stimme zu, ich aktiviere Kurzcodes für PHP, weil ich die wenigen Male, wenn ich mit XML arbeite, spezielle <?php Tags dort einfügen kann.
Ist VIEL besser als das