Ein Template-System trennt Präsentation (HTML) von Logik (PHP). Statt HTML-Strings im Code zusammenzukleben oder überall echo-Blöcke zu verteilen, liegt das Layout in separaten Template-Dateien. PHP liefert nur die Daten, die in Platzhalter eingesetzt werden.
- Ziel: Ein minimalistischer Kern
- Projektstruktur
- Schritt 1: Template-Klasse vorbereiten
- Schritt 2: Template öffnen und Inhalt laden
- Schritt 3: Einen Platzhalter im Template ersetzen
- Schritt 4: Rendern und Ausgabe
- Praxisbeispiel: Erste Seite rendern
- Mehr: Layouts und Includes
- Typische Erweiterungen und Stolpersteine
Dadurch entsteht ein klarerer Code, der leichter wartbar ist: Designer können Templates anpassen, ohne PHP anfassen zu müssen, und Entwickler können Logik ändern, ohne Markup zu zerreißen. Zusätzlich lassen sich wiederkehrende Strukturen wie Header, Footer oder Komponenten zentral verwalten.
Ein selbst entwickeltes Template-System muss nicht mit großen Engines konkurrieren. Oft reicht ein schlanker Ansatz: Template laden, Variablen ersetzen, optional Includes und einfache Kontrollstrukturen unterstützen. Genau so ein System wird im Folgenden aufgebaut.
Ziel: Ein minimalistischer Kern
Der Kern umfasst vier Aufgaben:
- Template-Dateien lokalisieren und laden
- Daten (Variablen) an die Template-Engine übergeben
- Platzhalter im Template ersetzen
- Ergebnis ausgeben oder als String zurückgeben
Als Platzhalter-Syntax bietet sich etwas an wie {{ title }} oder {{content}}. Diese Syntax ist leicht zu lesen und kollidiert selten mit HTML.
Projektstruktur
Eine mögliche Struktur:
templates/layout.htmlhome.htmlpartials/header.htmlfooter.html
src/Template.php
public/index.php
Die Trennung verhindert, dass Templates versehentlich als PHP ausgeführt werden, und macht klare Zuständigkeiten sichtbar.
Schritt 1: Template-Klasse vorbereiten
Die Klasse bekommt einen Template-Ordner, hält Variablen und bietet Methoden zum Setzen und Rendern.
<?php
// src/Template.php
class Template
{
private string $basePath;
private array $vars = [];
public function __construct(string $basePath)
{
$this->basePath = rtrim($basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
}
public function set(string $key, mixed $value): void
{
$this->vars[$key] = $value;
}
public function setMany(array $data): void
{
foreach ($data as $key => $value) {
$this->set((string)$key, $value);
}
}
private function templateFile(string $name): string
{
// erlaubt z.B. "home.html" oder "partials/header.html"
$file = $this->basePath . ltrim($name, DIRECTORY_SEPARATOR);
if (!is_file($file)) {
throw new RuntimeException("Template nicht gefunden: {$file}");
}
return $file;
}
}
Wichtig ist die zentrale Pfadbehandlung: ein konsistenter Basispfad und eine verlässliche Fehlermeldung, falls eine Datei fehlt.
Schritt 2: Template öffnen und Inhalt laden
Als Nächstes wird das Laden ergänzt. Das Template wird als String eingelesen.
<?php
// innerhalb der Klasse Template
private function load(string $name): string
{
$file = $this->templateFile($name);
$content = file_get_contents($file);
if ($content === false) {
throw new RuntimeException("Template konnte nicht gelesen werden: {$file}");
}
return $content;
}
Damit ist der “Datei → String”-Schritt kapsuliert. Das hilft später, wenn Caching oder ein anderer Loader ergänzt werden soll.
Schritt 3: Einen Platzhalter im Template ersetzen
Der Kern ist ein Ersetzungsmechanismus für {{ key }}. Praktisch ist ein Regex, der Platzhalter findet und durch Variablen ersetzt. Zusätzlich sollte HTML standardmäßig escaped werden, um XSS zu vermeiden.
<?php
// innerhalb der Klasse Template
private function escape(mixed $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
private function replacePlaceholders(string $html): string
{
return preg_replace_callback('/\{\{\s*(.+?)\s*\}\}/', function ($matches) {
$key = $matches[1];
if (!array_key_exists($key, $this->vars)) {
// unbekannte Variablen bleiben leer (oder Platzhalter stehen lassen)
return '';
}
$value = $this->vars[$key];
// Arrays/Objekte nicht blind ausgeben
if (is_array($value) || is_object($value)) {
return '';
}
return $this->escape($value);
}, $html) ?? $html;
}
Diese Variante ersetzt jedes Vorkommen eines Platzhalters. Unbekannte Variablen werden leer gelassen. Alternativ könnte hier ein Debug-Modus Platzhalter sichtbar lassen.
Schritt 4: Rendern und Ausgabe
Jetzt wird alles zusammengesetzt: Template laden, Platzhalter ersetzen, Ergebnis zurückgeben.
<?php
// innerhalb der Klasse Template
public function render(string $name): string
{
$html = $this->load($name);
return $this->replacePlaceholders($html);
}
public function display(string $name): void
{
echo $this->render($name);
}
Damit steht ein kompletter Minimalumfang: Variablen setzen, Template rendern.
Praxisbeispiel: Erste Seite rendern
Template-Datei templates/home.html:
<h1>{{ title }}</h1>
<p>{{ content }}</p>
Front-Controller public/index.php:
<?php
require __DIR__ . '/../src/Template.php';
$tpl = new Template(__DIR__ . '/../templates');
$tpl->setMany([
'title' => 'Willkommen',
'content' => 'Dieses Template wird über Platzhalter befüllt.'
]);
echo $tpl->render('home.html');
So entsteht eine klare Trennung: PHP liefert Daten, HTML bleibt in der Template-Datei.
Mehr: Layouts und Includes
Ein typisches Feature ist ein Layout (Grundgerüst) plus Inhalt. Dafür kann ein zweistufiges Rendern genutzt werden: erst Seiten-Template rendern, dann in ein Layout einsetzen. Ein reservierter Platzhalter wie {{ body }} funktioniert gut.
templates/layout.html:
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
</head>
<body>
{{ body }}
</body>
</html>
Erweiterung in PHP: eine Methode renderWithLayout().
<?php
// innerhalb der Klasse Template
public function renderWithLayout(string $view, string $layout = 'layout.html'): string
{
$body = $this->render($view);
// Body als "roher" HTML-Block: bewusst NICHT escapen.
// Dafür wird ein spezieller Key genutzt und im Layout separat behandelt.
$layoutHtml = $this->load($layout);
// Kopie der Variablen, damit Layout-Rendering Body kennt
$varsBackup = $this->vars;
$this->vars['body'] = $body;
$result = preg_replace_callback('/\{\{\s*(.+?)\s*\}\}/', function ($matches) {
$key = $matches[1];
if (!array_key_exists($key, $this->vars)) {
return '';
}
$value = $this->vars[$key];
// body unescaped, alles andere escaped
if ($key === 'body') {
return (string)$value;
}
if (is_array($value) || is_object($value)) {
return '';
}
return $this->escape($value);
}, $layoutHtml) ?? $layoutHtml;
$this->vars = $varsBackup;
return $result;
}
Aufruf:
echo $tpl->renderWithLayout('home.html');
So wird die Seite in das Layout eingebettet, ohne dass HTML im PHP-Code zusammengesetzt werden muss.
Für Includes (z. B. {{> partials/header.html }}) kann später ein weiterer Parser-Schritt ergänzt werden, der Include-Tags durch geladene Partial-Dateien ersetzt. Eine einfache Regel: zuerst Includes auflösen, dann Platzhalter ersetzen.

Typische Erweiterungen und Stolpersteine
- Caching: gerenderte Ergebnisse oder geladene Dateien im Speicher halten, um Dateizugriffe zu reduzieren.
- Roh-Ausgabe: neben
{{ key }}zusätzlich{{{ key }}}für unescaped Output (mit Vorsicht). - Fehlerverhalten: im Development fehlende Variablen sichtbar markieren, in Production leer lassen.
- Sicherheit: Standard-Escaping ist Pflicht, sobald Inhalte aus Benutzereingaben stammen.
- Logik im Template vermeiden: je weniger if/foreach direkt im Template, desto besser – ansonsten wächst schnell eine zweite Programmiersprache.
Fazit
Ein eigenes Template-System muss nicht kompliziert sein: Laden, Ersetzen, Rendern – das reicht für viele Projekte. Mit einer kleinen, gut strukturierten Template-Klasse entsteht schnell eine saubere Trennung von Logik und Darstellung. Layouts und Partials lassen sich schrittweise ergänzen, ohne das System zu überfrachten. Der größte Gewinn liegt in Wartbarkeit, Klarheit und der Möglichkeit, Markup unabhängig von PHP zu bearbeiten.
- Digitaler Euro: Bedeutung, Chancen und Risiken - 29. März 2026
- Bitcoin Kurs: Entwicklung, Einflussfaktoren und aktueller Stand - 28. März 2026
- NFT erstellen und digitale Ideen in begehrte Sammlerstücke verwandeln - 28. März 2026