mitmario.dev

Konfiguration

PHP Sandbox 4 Min Lesezeit 3 BeispieleLektion 5 von 8

Jede Anwendung hat eine Handvoll Werte, die sich zwischen deinem Rechner und dem Server unterscheiden. Wo die Datenbank liegt. Wie die Seite heißt. Ob Fehler auf der Seite stehen dürfen. Welche Zeitzone gilt. Und Zugangsdaten, die nirgends stehen sollten.

Fünf Werte, und du findest sie nicht wieder

Fünf Werte, drei Dateien
<?php

// Und hier stehen die Zeitzone und die Betriebsart. Fuenf Werte,
// drei Dateien, und keine Datei weiss von den anderen. Zaehl beim
// Lesen mit, an wie vielen Stellen du etwas aendern muesstest, um
// diese Anwendung auf einen Server zu bringen.

date_default_timezone_set("Europe/Berlin");

if ("entwicklung" === "entwicklung") {
    ini_set("display_errors", "1");
    error_reporting(E_ALL);
}

require __DIR__ . "/src/kopf.php";
require __DIR__ . "/src/datenbank.php";

$db = datenbank();
$anzahl = (int) $db->query("SELECT count(*) FROM notizen")->fetchColumn();

echo kopf();
echo "\n    <p>Notizen: " . $anzahl . "</p>";
echo "\n    <p>Zeitzone: " . date_default_timezone_get() . "</p>";
echo "\n</body>\n</html>\n";

Sieh dir das erste Beispiel an, aber diesmal nicht die Seite, sondern die drei Reiter. Der Name der Anwendung steht in src/kopf.php, und zwar zweimal. Der Pfad zur Datenbank steht in src/datenbank.php. Die Zeitzone und die Betriebsart stehen in index.php. Fünf Werte, drei Dateien, sechs Stellen.

Die Anwendung ist völlig in Ordnung, sie tut, was sie soll. Nur ist das Hochladen auf einen Server jetzt eine Suchaktion, und Suchaktionen gehen schief. Man findet vier von fünf Stellen, die Seite läuft trotzdem, und die fünfte fällt erst auf, wenn ein Besucher eine Fehlermeldung mit deinem Dateipfad sieht.

Alles an eine Stelle

Dieselben Werte an einer Stelle
<?php

// Dieselbe Anwendung, dieselbe Ausgabe. Nur steht jetzt oben eine
// Zeile, die alles holt, und darunter kein einziger fester Wert mehr.

$konfig = require __DIR__ . "/konfiguration.php";

date_default_timezone_set($konfig["zeitzone"]);

if ($konfig["modus"] === "entwicklung") {
    ini_set("display_errors", "1");
    error_reporting(E_ALL);
}

require __DIR__ . "/src/kopf.php";
require __DIR__ . "/src/datenbank.php";

$db = datenbank($konfig);
$anzahl = (int) $db->query("SELECT count(*) FROM notizen")->fetchColumn();

echo kopf($konfig);
echo "\n    <p>Notizen: " . $anzahl . "</p>";
echo "\n    <p>Zeitzone: " . date_default_timezone_get() . "</p>";
echo "\n</body>\n</html>\n";

Das zweite Beispiel gibt Zeichen für Zeichen dieselbe Seite aus. Der Unterschied ist eine Datei mehr und in den anderen dreien kein einziger fester Wert. konfiguration.php gibt ein Array zurück, und wer etwas davon braucht, bekommt es gesagt.

Warum ein zurückgegebenes Array und nicht ein Haufen Konstanten. Eine Konstante mit define ist bequem, weil sie überall gilt, und genau das ist ihr Problem. Sie gilt überall, auch dort, wo du sie nicht erwartest. Du kannst sie nicht als Parameter weiterreichen, du kannst sie nicht überschreiben, und in einem Test kannst du sie nicht durch etwas anderes ersetzen, denn ein zweites define ändert nichts, wie du in Lektion 2.6 gesehen hast. Ein Array ist ein gewöhnlicher Wert: Du reichst es weiter, du kannst zwei davon nebeneinander haben, und eine Funktion, die es als Parameter nimmt, sagt in ihrer Signatur, dass sie Konfiguration braucht.

Und warum require und kein JSON. Die Konfiguration ist PHP, also darfst du darin rechnen: __DIR__ einsetzen, eine Umgebungsvariable lesen, einen Rückfall angeben. Eine JSON-Datei kann das alles nicht, und dann steht der Pfad absolut darin, und beim nächsten Umzug stimmt er nicht mehr.

Der Schalter zwischen Entwicklung und Betrieb

Ein Schalter, zwei Betriebsarten
<?php

// Derselbe Fehler, zweimal ausgeloest, einmal je Betriebsart. Sieh
// dir die Seite an und danach das Protokoll des Servers (im Terminal:
// grep Warning /tmp/lern-web.log): Der Unterschied steht an beiden
// Orten, und er ist nicht derselbe.

$daten = ["titel" => "Notizbuch"];

echo "<!doctype html>\n<html lang=\"de\"><head><meta charset=\"utf-8\">";
echo "<title>Zwei Betriebsarten</title></head><body>\n";

foreach (["entwicklung", "betrieb"] as $modus) {
    if ($modus === "entwicklung") {
        ini_set("display_errors", "1");
        error_reporting(E_ALL);
    } else {
        ini_set("display_errors", "0");
        ini_set("log_errors", "1");
    }

    echo "<h2>Modus " . $modus . "</h2>\n<p>Wert: ";
    echo $daten["fehlt"];
    echo "</p>\n";
}

echo "</body>\n</html>\n";

Der wichtigste Eintrag ist modus, und er hängt an einer Sache, die du schon kennst. In Lektion 7.5 und 15.5 ging es darum, dass auf einem öffentlichen Server display_errors aus und log_errors an gehört. Beim Entwickeln willst du genau das Gegenteil.

Das dritte Beispiel löst denselben Fehler zweimal aus, einmal je Betriebsart. Im ersten Block steht die Warnung mitten auf der Seite, im zweiten Block ist die Zeile leer. Sieh danach ins Protokoll des Servers: Es liegt in /tmp/lern-web.log, und grep Warning /tmp/lern-web.log im Terminal zeigt beide Warnungen. Das ist kein Widerspruch, sondern der Beweis, dass die zwei Schalter unabhängig sind. display_errors entscheidet, ob die Meldung zusätzlich in die Ausgabe geht, log_errors, ob sie protokolliert wird. Auf dem Server willst du nur das zweite.

Damit ist der Modus mehr als eine Beschriftung: An ihm hängt, was ein Besucher zu sehen bekommt, wenn etwas schiefgeht. Genau deshalb steht er in der Konfiguration und nicht irgendwo in einem if in der Mitte der Anwendung.

Was nicht in die Konfiguration gehört

Der Unterschied ist einfach: In die Konfiguration gehört, was die Anwendung beschreibt. Nicht hinein gehört, was sie geheim halten muss.

Ein Datenbankpasswort, ein API-Schlüssel, das Geheimnis, mit dem Sitzungen signiert werden: Diese Werte stehen nicht im Quelltext, auch nicht in einer Datei, die konfiguration.php heißt. Der Grund ist nicht Paranoia, sondern der Alltag: Der Quelltext liegt in der Versionsverwaltung, wird kopiert, geteilt, in ein Backup gelegt und irgendwann von jemandem gelesen, an den beim Schreiben niemand gedacht hat. Ein Passwort, das einmal in der Versionsgeschichte steht, bekommt man da auch nicht wieder heraus.

Der übliche Weg dafür ist eine Umgebungsvariable. Der Server setzt sie, PHP liest sie mit getenv("DB_PASSWORT"), und in der Konfiguration steht nur diese Zeile statt des Werts. Auf dem eigenen Rechner legt man die Werte dafür in eine Datei, die nicht mit eingecheckt wird, und in fast jedem Projekt heißt sie .env. Wichtig ist dabei nur eins: Diese Datei steht in .gitignore, und zwar bevor sie zum ersten Mal einen echten Wert enthält.

getenv gibt false zurück, wenn es die Variable nicht gibt. Deshalb steht in der Konfiguration getenv("DB_PASSWORT") ?: "" und nicht nur der nackte Aufruf: Der Kurzform-Operator aus Lektion 4.1 macht daraus einen leeren Text, statt ein false weiterzureichen, das an der nächsten Stelle als Warnung auffällt.

Zum Mitnehmen

Jede Anwendung hat eine Handvoll Werte, die sich zwischen deinem Rechner und dem Server unterscheiden: ein Pfad, ein Schalter, ein Zugang. Solange sie verstreut im Code stehen, ist jedes Hochladen eine Suchaktion. Diese Lektion sammelt sie ein, und einen davon nimmt sie ganz aus dem Quelltext heraus.

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, auf dem Server geprüft

    Öffnet sich mit dem Basis Konto.