mitmario.dev

Eine Vorlagenfunktion

PHP Sandbox 3 Min Lesezeit 3 BeispieleLektion 2 von 8

In der letzten Lektion hast du eine Vorlage mit require eingebunden, und es hat funktioniert. Die Vorlage kam an $notizen und $offen heran, ohne dass jemand sie ihr gegeben hätte. Das ist bequem, und es ist genau der Punkt, an dem es später wehtut.

Eine eingebundene Datei sieht alles

Was include alles sieht
<?php

// Die Vorlage nebenan benutzt $titel als Schleifenvariable. Das ist
// dort voellig harmlos gemeint. Nur heisst hier eine Variable
// genauso, und nach der Schleife steht in ihr etwas anderes als
// vorher. Sieh dir die Ueberschrift und den letzten Absatz an.

$titel = "Einkaufsliste";
$notizen = ["Milch", "Brot", "Zahnpasta"];

?>
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Kollision</title>
<style>body{font-family:system-ui,sans-serif;max-width:34rem;margin:2rem auto}</style></head>
<body>
    <h1><?= $titel ?></h1>

<?php require __DIR__ . "/ansichten/liste.php"; ?>

    <p>Diese Seite heisst: <?= $titel ?></p>
</body>
</html>

require fügt eine Datei an genau der Stelle ein, an der es steht, so als hätte man ihren Inhalt dort hingeschrieben. Damit sieht die eingebundene Datei jede Variable, die dort gerade existiert. Nicht nur die, die sie braucht. Alle.

Im ersten Beispiel benutzt die Vorlage $titel als Schleifenvariable, was für sich völlig in Ordnung ist. Nur heißt in index.php eine Variable genauso, und die trägt den Namen der Seite. Nach der Schleife steht in ihr Zahnpasta, und der Absatz unten sagt das auch. Die Überschrift oben ist noch richtig, weil sie vor dem require ausgegeben wurde.

Der Fehler ist unangenehm, weil er nirgends meldet. Keine Warnung, kein Eintrag auf dem Fehlerkanal. Die Seite ist einfach falsch, und zwar an einer Stelle, an der man den Grund nicht vermutet. Und je mehr Vorlagen dazukommen, desto wahrscheinlicher wird er: Irgendwann heißt in zwei Dateien etwas gleich.

Eine Funktion ist ein eigener Raum

Dieselbe Vorlage in einem eigenen Raum
<?php

// Dieselbe Vorlage, dieselben Daten, dieselbe Ausgabe. Nur laeuft die
// Vorlage diesmal in einer Funktion, und was dort passiert, bleibt
// dort. Die Ueberschrift und der letzte Absatz stimmen wieder ueberein.

function rendere(string $datei, array $daten = []): string
{
    extract($daten);
    ob_start();
    require $datei;

    return ob_get_clean();
}

$titel = "Einkaufsliste";
$notizen = ["Milch", "Brot", "Zahnpasta"];

$liste = rendere(__DIR__ . "/ansichten/liste.php", ["notizen" => $notizen]);

?>
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Eigener Raum</title>
<style>body{font-family:system-ui,sans-serif;max-width:34rem;margin:2rem auto}</style></head>
<body>
    <h1><?= $titel ?></h1>

<?= $liste ?>

    <p>Diese Seite heisst: <?= $titel ?></p>
</body>
</html>

Die Lösung kennst du seit Lektion 6.4: Was in einer Funktion passiert, bleibt in der Funktion. Eine Funktion sieht die Variablen ihres Aufrufers nicht, und der Aufrufer sieht hinterher nichts von dem, was drinnen entstanden ist.

Genau das macht rendere(). Sie bekommt den Pfad der Vorlage und ein Array mit den Daten, und weil die Vorlage innerhalb der Funktion eingebunden wird, kann sie draußen nichts mehr anrichten. Im zweiten Beispiel stimmen Überschrift und Absatz wieder überein, obwohl sich an der Vorlage kein Zeichen geändert hat.

Drei Werkzeuge stecken darin, und keins davon ist Zauberei:

extract($daten) macht aus einem Array einzelne Variablen. Aus ["notizen" => [...]] wird $notizen. Das ist nötig, weil in einer Vorlage <?= $notiz["titel"] ?> stehen soll und nicht <?= $daten["notizen"][0]["titel"] ?>. Mit fremden Daten würde man extract nie benutzen, denn es legt Variablen an, deren Namen von außen kommen. Hier ist das Array deins, und der Aufruf steht in genau einer Zeile im ganzen Projekt.

ob_start() schaltet die Ausgabepufferung ein. Alles, was danach ausgegeben wird, geht nicht mehr zum Browser, sondern in einen Puffer. Das ist der Trick, mit dem aus „die Vorlage schreibt hinaus” ein „die Vorlage liefert eine Zeichenkette” wird.

ob_get_clean() holt den Inhalt des Puffers ab, schaltet ihn wieder aus und gibt den Text zurück. Genau dieser Text ist der Rückgabewert von rendere().

Was du dadurch gewinnst, ist mehr als Ordnung. Eine Vorlage, die eine Zeichenkette zurückgibt, kannst du in eine andere Vorlage einsetzen: erst die Liste rendern, dann das Ergebnis in ein Seitengerüst stecken. Genau so bauen die großen Template-Engines ihre Layouts, und du hast es gerade in zwölf Zeilen selbst gemacht.

Die eine Falle, die bleibt

Was die Vorlage trotzdem sieht
<?php

// Die Abschirmung ist keine Mauer, sie ist ein Zimmerwechsel. Was in
// index.php steht, bleibt draussen. Was rendere() selbst dabei hat,
// seine eigenen beiden Parameter, ist drin. Das ist die eine Falle
// der ganzen Konstruktion.

function rendere(string $datei, array $daten = []): string
{
    extract($daten);
    ob_start();
    require $datei;

    return ob_get_clean();
}

$titel = "Einkaufsliste";
$notizen = ["Milch", "Brot"];

$bericht = rendere(__DIR__ . "/ansichten/spion.php", ["notizen" => $notizen]);

?>
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Wer sieht was</title>
<style>body{font-family:system-ui,sans-serif;max-width:34rem;margin:2rem auto}</style></head>
<body>
    <h1>Wer sieht was</h1>
<?= $bericht ?>
</body>
</html>

Ehrlich bleiben: Die Abschirmung ist keine Mauer, sondern ein Zimmerwechsel. Die Vorlage sieht $titel aus index.php nicht mehr, aber sie sieht sehr wohl alles, was rendere() selbst dabei hat, und das sind ihre beiden Parameter $datei und $daten. Eine Vorlage, die zufällig eine Variable $datei benutzt, bekommt hier den Pfad ihrer selbst. Das dritte Beispiel zeigt es.

Die übliche Antwort darauf ist ein Name, der nicht aus Versehen vorkommt, also $__vorlagenDatei statt $datei. Für diesen Kurs bleiben die Namen lesbar, und der Satz hier ersetzt den Unterstrich: Wenn eine Vorlage sich einmal merkwürdig verhält, sieh nach, ob sie eine Variable benutzt, die auch rendere() benutzt.

Und wo bleibt das Maskieren

Die Vorlage ist der Ort, an dem e() steht, und zwar an jeder einzelnen Ausgabestelle. Das ist die Regel aus Lektion 15.2, und sie hat hier einen neuen Grund: Weil in der Vorlage sonst nichts passiert, außer auszugeben, ist sie auch der einzige Ort, an dem man das Maskieren vergessen kann. Eine Datei, die man beim Nachsehen komplett liest, ist dafür der beste Platz.

Zum Mitnehmen

Eine eingebundene Datei sieht alles, was an der Stelle des Einbindens gerade da ist, und kann es überschreiben. Das ist praktisch, solange die Vorlage von dir ist und klein bleibt, und es wird zum Problem, sobald beides nicht mehr stimmt. Die Lösung sind zwölf Zeilen, und sie sind die halbe Idee jeder Template-Engine.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.