PHP ist In Ordnung Für Templating

Avatar of Chris Geelhoed
Chris Geelhoed am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

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:

  1. 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.
  2. 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.