mitmario.dev

Logik und Ansicht trennen

PHP Sandbox 3 Min Lesezeit 3 BeispieleLektion 1 von 8

Es gibt einen Moment, an dem man merkt, dass eine Datei zu viel auf einmal macht. Er sieht immer gleich aus: Du suchst eine Zahl, und du suchst sie zwischen zwei div.

Kopf und Fuß in eigene Dateien zu legen, hast du in Lektion 10.6 schon gemacht. Danach kam es fünf Abschnitte lang nicht mehr vor, und das war Absicht: Eine Lektion über Cookies soll von Cookies handeln, und jede Datei, die dabei aufgeht, ist eine Datei, in der man den Stoff sucht. Ab hier ist die Aufteilung selbst das Thema, und dann lohnt sie sich auch.

Woran man es merkt

Eine Zahl zwischen zwei divs
<?php

// Eine Seite, wie sie ganz von selbst entsteht: Gerechnet wird da, wo
// gerade Platz ist. Such einmal die Zeile, in der die Summe gebildet
// wird. Sie steht mitten im Markup, zwischen zwei div.

?>
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Kasse</title>
<style>body{font-family:system-ui,sans-serif;max-width:34rem;margin:2rem auto}
.karte{border:1px solid #ccc;border-radius:6px;padding:1rem}</style></head>
<body>
    <h1>Offene Posten</h1>
    <div class="karte">
        <div class="zeile">
<?php
$db = new PDO("sqlite::memory:", null, null, [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
$db->exec("CREATE TABLE posten (kunde TEXT, betrag INTEGER, bezahlt INTEGER)");
$db->exec("INSERT INTO posten VALUES ('Mia', 1250, 0), ('Ben', 800, 1), ('Zoe', 4990, 0)");
$zeilen = $db->query("SELECT kunde, betrag, bezahlt FROM posten")->fetchAll();

$summe = 0;
foreach ($zeilen as $zeile) {
    if ($zeile["bezahlt"] === 0) {
        $summe += $zeile["betrag"];
    }
}

echo "Summe: <strong>" . number_format($summe / 100, 2, ",", ".") . " Euro</strong>\n";
?>
        </div>
    </div>
</body>
</html>

Sieh dir das erste Beispiel an. Die Seite ist völlig in Ordnung, sie zeigt an, was sie soll. Nur steht die Datenbankabfrage mitten im Markup, die Schleife, die die Summe bildet, gleich daneben, und wer die Rechnung ändern will, muss sich erst durch zwei div und ein style lesen.

Solange die Datei zwanzig Zeilen hat, ist das kein Problem. Bei zweihundert schon, und dann fangen die typischen Fehler an. Man ändert die Ausgabe und die Rechnung stimmt nicht mehr. Man will dieselbe Summe auf einer zweiten Seite zeigen und muss den Block kopieren. Man gibt die Datei an jemanden weiter, der nur das Aussehen ändern soll, und der stolpert über PDO.

Die Trennung in drei Schritte

Erst holen, dann rechnen, dann ausgeben
<?php

// Dieselbe Seite, dieselbe Ausgabe, nur in drei Schritten. Lies die
// Datei von oben nach unten: erst wird geholt, dann gerechnet, dann
// ausgegeben. Und die Ausgabe steht nicht mehr hier.

// 1. Holen
$db = new PDO("sqlite::memory:", null, null, [PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]);
$db->exec("CREATE TABLE posten (kunde TEXT, betrag INTEGER, bezahlt INTEGER)");
$db->exec("INSERT INTO posten VALUES ('Mia', 1250, 0), ('Ben', 800, 1), ('Zoe', 4990, 0)");
$zeilen = $db->query("SELECT kunde, betrag, bezahlt FROM posten")->fetchAll();

// 2. Rechnen
$summe = 0;
foreach ($zeilen as $zeile) {
    if ($zeile["bezahlt"] === 0) {
        $summe += $zeile["betrag"];
    }
}
$summeAlsText = number_format($summe / 100, 2, ",", ".");

// 3. Ausgeben
require __DIR__ . "/ansichten/kasse.php";

Das zweite Beispiel zeigt dieselbe Seite noch einmal, mit derselben Ausgabe. Der Unterschied ist die Reihenfolge, und sie ist immer dieselbe:

Erst holen. Alles, was von außen kommt, wird ganz oben besorgt: aus der Datenbank, aus $_GET, aus einer Datei. Danach steht fest, womit gearbeitet wird.

Dann rechnen. Summen, Filter, Sortierungen, alles, was aus den Rohdaten das macht, was auf der Seite stehen soll. Am Ende dieses Schritts liegen fertige Werte bereit, $summeAlsText zum Beispiel.

Dann ausgeben. Und zwar woanders. Die Vorlage ansichten/kasse.php bekommt die fertigen Werte und setzt sie ein. Mehr tut sie nicht.

Der Gewinn ist nicht die Schönheit. Der Gewinn ist, dass du beim nächsten Mal weißt, wo du suchen musst. Stimmt die Zahl nicht, siehst du in den zweiten Schritt. Sitzt etwas an der falschen Stelle, siehst du in die Vorlage. Und wenn du dieselbe Ansicht ein zweites Mal brauchst, bindest du dieselbe Datei noch einmal ein.

Was in einer Vorlage nichts zu suchen hat

Eine Vorlage gibt aus. Sie fragt nicht nach. Der Satz klingt streng, hat aber einen sehr praktischen Grund: Sobald in der Vorlage eine Abfrage steht, kannst du sie nicht mehr mit anderen Daten verwenden, und du kannst nicht mehr sehen, wie viele Abfragen deine Seite eigentlich macht. Eine Schleife, die pro Zeile noch einmal in die Datenbank geht, ist der Klassiker. Sie fällt bei zehn Zeilen nicht auf und bei tausend sehr.

Erlaubt und normal in einer Vorlage sind dagegen: eine Schleife über die fertigen Daten, ein if für den Fall, dass die Liste leer ist, und das Maskieren mit e() beim Ausgeben. Das ist keine Logik, das ist Darstellung.

Die Kurzsyntax

Zwei Schreibweisen, dieselbe Ausgabe
<?php

// Zweimal dieselbe Liste, zweimal dasselbe Ergebnis. Der Unterschied
// liegt nur darin, wie die Schleife geschlossen wird. Sieh dir beide
// Vorlagen im Reiter daneben an und entscheide selbst, welche du in
// einem Stapel Markup schneller wiederfindest.

$namen = ["Milch", "Brot", "Zahnpasta"];

?>
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Zwei Schreibweisen</title>
<style>body{font-family:system-ui,sans-serif;max-width:34rem;margin:2rem auto}</style></head>
<body>
    <h2>Mit geschweiften Klammern</h2>
<?php require __DIR__ . "/ansichten/mit-klammern.php"; ?>

    <h2>Mit endforeach</h2>
<?php require __DIR__ . "/ansichten/mit-endforeach.php"; ?>
</body>
</html>

In einer Vorlage wechseln sich PHP und Markup ständig ab, und dabei sind geschweifte Klammern unangenehm: Die schließende steht irgendwo unten allein in einer Zeile, zwischen zwanzig Zeilen HTML, und sagt nicht, was sie schließt. Deshalb gibt es für Vorlagen eine zweite Schreibweise. Aus foreach (...) { wird foreach (...):, und statt der schließenden Klammer steht endforeach;. Für if heißt es endif;, für while gibt es endwhile; und für for gibt es endfor;.

Beide Varianten im dritten Beispiel erzeugen exakt dieselbe Ausgabe, sie sind wirklich nur zwei Schreibweisen für dieselbe Sache. In einem gewöhnlichen Skript nimmt man weiter die Klammern, in einer Vorlage lohnt sich das endforeach, weil man am Ende einer langen Datei sofort liest, was dort zu Ende geht.

Ein Detail noch: In den Vorlagen steht <?= und nicht <?php echo. Das ist die Kurzform aus Lektion 1.3, und in einer Vorlage ist sie die Regel und nicht die Ausnahme, weil dort in fast jeder Zeile genau ein Wert eingesetzt wird.

Zum Mitnehmen

Ab hier baust du nichts Neues mehr, du räumst auf. Jede Lektion dieses Abschnitts nimmt sich dieselbe Anwendung vor und stellt sie um, und die Seite im Reiter Browser muss danach genauso aussehen wie vorher. Das ist die Erfahrung, um die es beim Umbauen geht: Wenn sich die Seite ändert, hast du nicht aufgeräumt, sondern etwas kaputt gemacht.

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.