mitmario.dev

Werfen statt zurückgeben

PHP Sandbox 3 Min Lesezeit 4 BeispieleLektion 4 von 6

Eine Funktion, bei der etwas schiefgehen kann, hat zwei Möglichkeiten. Sie kann einen besonderen Wert zurückgeben, an dem der Aufrufer den Fehlschlag erkennt, oder sie kann eine Ausnahme werfen. Beides ist zulässig, und die Wahl hat Folgen.

Die Falle, die PHP selbst mitbringt

false und 0 sehen im if gleich aus
<?php

$satz = "Apfel und Birne";

// strpos gibt die Position zurueck, an der das Wort anfaengt.
var_dump(strpos($satz, "Apfel"));
var_dump(strpos($satz, "Birne"));
var_dump(strpos($satz, "Kirsche"));

// Und jetzt die Falle:
if (!strpos($satz, "Apfel")) {
    echo "Apfel kommt angeblich nicht vor.\n";
}

// Mit === stimmt es wieder:
if (strpos($satz, "Apfel") !== false) {
    echo "Apfel kommt vor, und zwar an Stelle 0.\n";
}

strpos() liefert die Position, an der ein Wort anfängt, und false, wenn es nicht vorkommt. Klingt vernünftig, bis das gesuchte Wort ganz am Anfang steht. Dann ist die Position 0, und 0 gehört zur Falsch-Liste aus Lektion 2.5.

if (!strpos(...)) behandelt den besten Treffer, den es gibt, also wie einen Fehlschlag. Das Programm läuft durch, meldet nichts, und die Antwort ist falsch.

Entschärfen lässt sich das mit !== false. Der dreifache Vergleich sieht nicht nur auf den Wert, sondern auch auf den Typ, und 0 ist eben eine Zahl und kein false. Immer wenn eine Funktion false als Fehlerzeichen benutzt und auch 0 oder einen leeren Text liefern kann, brauchst du === oder !==. Das gilt für strpos(), array_search() und einige mehr.

Kein Ergebnis ist kein Fehler

Kein Treffer ist normal, eine kaputte Frage nicht
<?php

$kunden = [
    ["nr" => 1001, "ort" => "Kiel"],
    ["nr" => 1002, "ort" => "Ulm"],
];

// Kein Treffer ist normal und bekommt eine leere Liste.
// Ein leerer Ort ist kein Ergebnis, sondern ein Fehler.
function kundenInOrt(string $ort, array $kunden): array
{
    if (trim($ort) === "") {
        throw new InvalidArgumentException("Ort ist leer");
    }

    return array_values(array_filter($kunden, fn(array $k): bool => $k["ort"] === $ort));
}

foreach (["Kiel", "Bonn", " "] as $ort) {
    try {
        $treffer = kundenInOrt($ort, $kunden);
        echo count($treffer), " Kunden gefunden\n";
    } catch (InvalidArgumentException $fehler) {
        echo "Nicht gesucht: ", $fehler->getMessage(), "\n";
    }
}

Und hier kommt die Unterscheidung, um die es in dieser Lektion geht.

Eine Suche, die nichts findet, ist kein Fehler. Das ist ein völlig normales Ergebnis, mit dem jeder rechnet, und die ehrliche Antwort darauf ist eine leere Liste. Der Aufrufer schreibt dann 0 Kunden gefunden hin und macht weiter.

Ein leerer Suchbegriff dagegen ist etwas anderes: Da hat jemand eine Frage gestellt, die keine ist. Die Funktion kann darauf nicht sinnvoll antworten, und deshalb wirft sie.

Die Frage, die du dir bei jeder Funktion stellst, lautet also nicht „kann hier etwas schiefgehen”, sondern: Ist dieser Ausgang ein Ergebnis oder ein Unfall? Ergebnisse gehören in den Rückgabewert, Unfälle in eine Ausnahme.

Warum eine Ausnahme sicherer ist

Was vergessen werden kann, wird vergessen
<?php

// Version mit false: der Aufrufer kann die Pruefung weglassen.
function alterAusJahrOderFalse(string $jahr): int|false
{
    if (!ctype_digit($jahr)) {
        return false;
    }

    return 2026 - (int) $jahr;
}

echo "Mit false, ohne Pruefung: ";
echo alterAusJahrOderFalse("neunzehn") + 0, "\n";

// Version mit Ausnahme: weglassen geht nicht.
function alterAusJahr(string $jahr): int
{
    if (!ctype_digit($jahr)) {
        throw new InvalidArgumentException("Kein Jahr: " . $jahr);
    }

    return 2026 - (int) $jahr;
}

echo "Mit Ausnahme: ";
try {
    echo alterAusJahr("neunzehn"), "\n";
} catch (InvalidArgumentException $fehler) {
    echo $fehler->getMessage(), "\n";
}

Beide Funktionen im Beispiel machen dasselbe, und beide behandeln den Fehlerfall. Der Unterschied liegt darin, was passiert, wenn der Aufrufer nicht aufpasst.

Bei der false-Version rechnet PHP mit dem false einfach weiter. false + 0 ergibt 0, ohne Warnung, ohne Meldung, mit Exit-Code 0. Aus einem nicht erkannten Jahr ist ein Alter von null Jahren geworden, und niemand hat etwas gemerkt.

Bei der Ausnahme geht das nicht. Wer sie nicht fängt, dessen Programm bricht ab, und zwar laut. Das klingt nach dem schlechteren Angebot und ist das bessere: Ein Fehler, den man übersehen kann, wird übersehen.

Daraus die Faustregel für eigene Funktionen: Wirf, wenn der Aufrufer das Problem wirklich behandeln muss. Gib etwas zurück, wenn der leere Fall ein normaler Fall ist. Und benutze false nie als Fehlerzeichen in einer Funktion, die auch 0, "" oder ein leeres Array liefern kann.

Alte Funktionen an der Grenze übersetzen

Eine alte Funktion übersetzen
<?php

// So antworten die alten Funktionen: mit false und einer Warnung.
$inhalt = file_get_contents("preise.csv");
var_dump($inhalt);
echo "Und das Programm laeuft weiter.\n";

// So uebersetzt man das in eine Ausnahme:
function liesDatei(string $pfad): string
{
    if (!is_readable($pfad)) {
        throw new RuntimeException("Datei nicht lesbar: " . $pfad);
    }

    return (string) file_get_contents($pfad);
}

try {
    echo liesDatei("preise.csv");
} catch (RuntimeException $fehler) {
    echo "Abgebrochen: ", $fehler->getMessage(), "\n";
}

Die eingebauten Funktionen wirst du nicht los, file_get_contents() antwortet auch morgen noch mit false und einer Warnung. Was du tun kannst: sie einmal einpacken.

Die kleine liesDatei() prüft vorher, wirft eine Ausnahme mit einer brauchbaren Meldung, und ab da gilt in deinem Code nur noch eine Regel. Das ist derselbe Gedanke wie in Lektion 6.5: Ein Detail gehört an eine Stelle, und dahinter arbeitest du mit dem, was dir besser gefällt.

Genau diesen Handgriff brauchst du ab Abschnitt 8 laufend, denn dort geht es um Dateien, und ab Abschnitt 14 bei der Datenbank sowieso. PDO wirft von sich aus, das wird dir dann leicht fallen.

Zum Mitnehmen

PHPs eingebaute Funktionen antworten auf Fehler seit dreißig Jahren mit false. Wer das nachahmt, baut sich eine Fehlerbehandlung, die man vergessen kann, und vergessen wird sie. Diese Lektion ist die Entscheidung dazwischen, und sie gehört bewusst getroffen.

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 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.