mitmario.dev

Eingaben prüfen

PHP Sandbox 4 Min Lesezeit 3 BeispieleLektion 2 von 7

Ein Formular nimmt alles entgegen, was jemand hineinschreibt. Was danach passiert, entscheidest du, und deshalb steht zwischen dem Absenden und dem Speichern immer eine Prüfung.

Die drei Fragen

Zu jedem einzelnen Feld gehören drei Fragen, und sie bauen aufeinander auf.

Ist es überhaupt da? Das ist keine überflüssige Frage, obwohl das Feld ja im Formular steht. Wer die Anfrage selbst zusammenbaut, lässt es einfach weg. Zuständig ist isset(), und danach trim(), weil ein Feld mit drei Leerzeichen für einen Menschen leer ist und für PHP nicht.

Hat es die richtige Form? Sieht die E-Mail-Adresse wie eine aus, ist die Menge eine Zahl, hat die Postleitzahl fünf Stellen? Dafür gibt es fertige Prüfer.

Ist der Wert plausibel? Die Form kann stimmen und der Wert trotzdem Unsinn sein: eine Menge von minus drei, ein Geburtsdatum im Jahr 2093, eine Bestellung über null Stück. Das weiß keine Funktion für dich, das sind deine Regeln.

Da oder nicht da

Ist es da, oder sieht es nur so aus
<?php

// Drei Fragen an dasselbe Feld, und sie haben drei verschiedene
// Antworten. Schick das Formular einmal ganz leer ab und einmal
// mit nichts als zwei Leerzeichen darin.

$bericht = [];

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $roh = $_POST["name"] ?? null;

    $bericht["Feld angekommen (isset)"] = isset($_POST["name"]) ? "ja" : "nein";
    $bericht["Roher Wert"] = var_export($roh, true);
    $bericht["Nach trim() noch etwas übrig"] = trim((string) $roh) !== "" ? "ja" : "nein";
    $bericht["Was empty() sagt"] = empty($roh) ? "leer" : "nicht leer";
}

?>
<!doctype html>
<html lang="de">
<head>
    <meta charset="utf-8">
    <title>Ist es da</title>
</head>
<body>
    <h1>Ist es da, oder sieht es nur so aus</h1>

    <form method="post" action="/">
        <p><label>Name: <input type="text" name="name"></label></p>
        <button type="submit">Absenden</button>
    </form>

    <ul>
<?php foreach ($bericht as $frage => $antwort) { ?>
        <li><?= htmlspecialchars($frage) ?>: <strong><?= htmlspecialchars($antwort) ?></strong></li>
<?php } ?>
    </ul>
</body>
</html>

isset() beantwortet die erste Frage und sonst nichts: Es sagt, ob der Schlüssel existiert und nicht null ist. Ein leer abgeschicktes Textfeld existiert sehr wohl, es enthält nur eine leere Zeichenkette. isset() sagt dafür also ja.

empty() stellt eine andere Frage, nämlich die aus Lektion 2.5: Hält PHP diesen Wert für falsch? Damit ist ein leeres Feld leer, aber auch die Eingabe 0, und genau deshalb ist empty() für Formularfelder eine Falle. Eine Menge von 0 ist eine Angabe und kein fehlendes Feld.

Im Alltag prüfst du deshalb meistens so: Wert mit ?? "" abholen, trim() darauflegen, und dann gegen "" vergleichen. Drei Schritte, die zusammen genau das bedeuten, was du meinst.

Die Form prüfen, ohne sie selbst zu beschreiben

filter_var prüft die Form
<?php

// Beim ersten Aufruf stehen zwei Werte im Formular, die beide
// knapp danebenliegen. Druck einfach auf Absenden und sieh dir
// an, was filter_var dazu sagt.

$istPost = $_SERVER["REQUEST_METHOD"] === "POST";

$email = $istPost ? trim($_POST["email"] ?? "") : "mia@example";
$menge = $istPost ? trim($_POST["menge"] ?? "") : "3 Stück";

// Zurueck kommt der geprueft brauchbare Wert oder false. Bei der
// Zahl ist der Rueckgabewert deshalb interessant: aus dem Text
// "3" wird ein echtes int.
$emailGeprueft = filter_var($email, FILTER_VALIDATE_EMAIL);
$mengeGeprueft = filter_var($menge, FILTER_VALIDATE_INT);

?>
<!doctype html>
<html lang="de">
<head>
    <meta charset="utf-8">
    <title>filter_var</title>
</head>
<body>
    <h1>Hat es die richtige Form?</h1>

    <form method="post" action="/">
        <p><label>E-Mail: <input type="text" name="email" value="<?= htmlspecialchars($email) ?>"></label></p>
        <p><label>Menge: <input type="text" name="menge" value="<?= htmlspecialchars($menge) ?>"></label></p>
        <button type="submit">Prüfen</button>
    </form>

    <ul>
        <li>FILTER_VALIDATE_EMAIL gibt zurück:
            <strong><?= htmlspecialchars(var_export($emailGeprueft, true)) ?></strong></li>
        <li>FILTER_VALIDATE_INT gibt zurück:
            <strong><?= htmlspecialchars(var_export($mengeGeprueft, true)) ?></strong></li>
    </ul>

    <p>Probier auch <code>mia@example.com</code> und <code>3</code>.</p>
</body>
</html>

Eine E-Mail-Adresse mit einem eigenen Muster zu prüfen, ist eine Übung, die man einmal macht und danach bereut. Die Regeln sind unangenehm, und die meisten selbst gebauten Prüfungen weisen gültige Adressen ab.

filter_var() nimmt dir das ab. Der zweite Parameter sagt, worauf geprüft wird: FILTER_VALIDATE_EMAIL für Adressen, FILTER_VALIDATE_INT für ganze Zahlen, FILTER_VALIDATE_FLOAT für gebrochene, FILTER_VALIDATE_URL für Adressen im Netz.

Wichtig ist der Rückgabewert. Die Funktion gibt nicht true oder false zurück, sondern den geprüften Wert oder false. Bei der Zahl ist das ein Geschenk: Aus dem Text "3" wird ein echtes int, mit dem du sofort rechnen kannst. Bei "3 Stück" kommt false, denn eine Zahl mit Anhang ist keine Zahl.

Und weil false zurückkommt, prüfst du mit === false und nicht mit !. Bei einer eingegebenen 0 wäre das sonst der Fehler, der einen halben Nachmittag kostet: 0 ist gültig, aber PHP hält es für falsch.

Warum der Browser die Prüfung nicht übernimmt

Im HTML-Kurs hast du required, type="email" und min kennengelernt, und die sind auch weiterhin richtig. Sie sind allerdings ausschließlich eine Bequemlichkeit für den Besucher: Der Browser sagt sofort Bescheid, statt ihn eine Seite weiterzuschicken.

Aufhalten tun sie niemanden. Wer die Anfrage nicht über dein Formular schickt, sondern selbst zusammenbaut, sieht dein Markup nie. Und das ist nicht einmal böse gemeint, das macht jedes Werkzeug so, mit dem man Schnittstellen ausprobiert. Eines davon hast du im Terminal: curl mit -d schickt einen POST mit genau den Feldern, die du ihm nennst, an jedem Formular vorbei. Die Prüfliste macht seit Abschnitt 10 nichts anderes, und in der Aufgabe unten tust du es einmal selbst.

Die Regel lautet deshalb: Der Browser prüft für den Besucher, der Server prüft für dich. Beides gehört hin, und nur eines davon zählt.

Alle Fehler auf einmal

Alle Fehler auf einmal statt einem nach dem anderen
<?php

// Dieselben drei Pruefungen, zweimal ausgewertet. Links bricht die
// Auswertung beim ersten Fehler ab, rechts sammelt sie alles ein.
// Schick das Formular leer ab, dann siehst du den Unterschied.

$istPost = $_SERVER["REQUEST_METHOD"] === "POST";

$name = trim($_POST["name"] ?? "");
$email = trim($_POST["email"] ?? "");
$menge = trim($_POST["menge"] ?? "");

$alle = [];

if ($name === "") {
    $alle[] = "Bitte trag deinen Namen ein";
}

if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
    $alle[] = "Die E-Mail-Adresse stimmt nicht";
}

if (filter_var($menge, FILTER_VALIDATE_INT) === false) {
    $alle[] = "Die Menge muss eine ganze Zahl sein";
}

$erster = $alle === [] ? [] : [$alle[0]];

?>
<!doctype html>
<html lang="de">
<head>
    <meta charset="utf-8">
    <title>Fehler sammeln</title>
    <style>
        .spalten { display: flex; gap: 2rem; }
    </style>
</head>
<body>
    <h1>Ein Fehler oder alle</h1>

    <form method="post" action="/">
        <p><label>Name: <input type="text" name="name"></label></p>
        <p><label>E-Mail: <input type="text" name="email"></label></p>
        <p><label>Menge: <input type="text" name="menge"></label></p>
        <button type="submit">Absenden</button>
    </form>

<?php if ($istPost) { ?>
    <div class="spalten">
        <div>
            <h2>Beim ersten Fehler abgebrochen</h2>
            <ul>
<?php foreach ($erster as $meldung) { ?>
                <li><?= htmlspecialchars($meldung) ?></li>
<?php } ?>
            </ul>
        </div>
        <div>
            <h2>Alles eingesammelt</h2>
            <ul>
<?php foreach ($alle as $meldung) { ?>
                <li><?= htmlspecialchars($meldung) ?></li>
<?php } ?>
            </ul>
        </div>
    </div>
<?php } ?>
</body>
</html>

Beim ersten Fehler abzubrechen, ist die naheliegende Bauart und die unangenehmste Bedienung, die man sich ausdenken kann: Der Besucher schickt ab, korrigiert eine Kleinigkeit, schickt ab, korrigiert die nächste. Bei drei Feldern sind das drei Runden.

Die bessere Form ist ein Array. Jede Prüfung, die scheitert, hängt ihre Meldung hinten an, und danach fragst du einmal, ob etwas darin steht. Ist das Array leer, war alles in Ordnung, und du darfst weiterarbeiten. Genau diese Form baust du in der Aufgabe, und ab Lektion 11.5 begleitet sie dich durch den Rest des Kurses.

Zum Mitnehmen

Drei Fragen stellst du an jedes Feld, und sie sind wirklich verschieden: Ist es da? Hat es die richtige Form? Ist der Wert plausibel? Wer eine davon überspringt, merkt es meistens erst, wenn ein Datensatz in der Datenbank steht, mit dem niemand mehr etwas anfangen kann.

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.