Abschnitt 7 · Lektion 2
Ausnahmen fangen
Ein try-Block sagt: Hier drin kann etwas schiefgehen, und wenn es das tut, mach bitte nicht das
ganze Programm kaputt. Der catch-Block dahinter sagt, was stattdessen passieren soll.
Möglichst wenig in den try-Block
<?php
$eingaben = ["12", "acht", "30"];
foreach ($eingaben as $eingabe) {
try {
if (!is_numeric($eingabe)) {
throw new InvalidArgumentException("keine Zahl: " . $eingabe);
}
echo "Alter: ", (int) $eingabe, "\n";
} catch (InvalidArgumentException $fehler) {
echo "Uebersprungen, ", $fehler->getMessage(), "\n";
}
}
echo "Fertig.\n"; Drei Eingaben, eine davon ist Text. Ohne try wäre nach der zweiten Schluss. Mit try bekommt die
zweite eine eigene Behandlung, und die dritte kommt trotzdem noch dran.
Sieh dir an, wo der try-Block anfängt und aufhört: Er sitzt in der Schleife, nicht darum
herum. Das ist der wichtigste Handgriff dieser Lektion. Läge er außen, würde die kaputte zweite
Eingabe die ganze Schleife beenden, und die 30 käme nie an die Reihe.
Daraus die Regel: In den try-Block gehört so wenig wie möglich. Nur die Zeilen, die wirklich
schiefgehen können, und nichts, was danach sowieso noch passieren soll. Ein try, das eine halbe
Datei umschließt, fängt am Ende Fehler, an die beim Schreiben niemand gedacht hat, und behandelt sie
alle gleich.
throw wirft übrigens selbst eine Ausnahme, und new InvalidArgumentException(...) legt sie an. Das
sieht nach Objekten aus und ist auch welche; für diese Lektion reicht dir, dass hinter new ein Name
steht und in den Klammern die Meldung. Wann du selbst wirfst, ist der Stoff von Lektion 7.4.
finally läuft, egal was passiert
<?php
function lies(string $was): string
{
echo " Datei geoeffnet\n";
try {
if ($was === "kaputt") {
throw new RuntimeException("Datei ist kaputt");
}
return "Inhalt von " . $was;
} finally {
echo " Datei geschlossen\n";
}
}
echo "Erster Versuch:\n";
echo lies("brief.txt"), "\n";
echo "Zweiter Versuch:\n";
try {
echo lies("kaputt"), "\n";
} catch (RuntimeException $fehler) {
echo "Gefangen: ", $fehler->getMessage(), "\n";
} An try darf hinten ein finally hängen, und dessen Block läuft immer. Auch dann, wenn der
try-Block mit return verlassen wird. Auch dann, wenn eine Ausnahme durchfliegt und weiter oben
gefangen wird.
Sieh dir die Reihenfolge im ersten Versuch an: Datei geschlossen steht vor dem Inhalt. Die
Funktion hat ihren Rückgabewert schon fertig, führt aber das finally noch aus, bevor der Wert beim
Aufrufer ankommt. Im zweiten Versuch fliegt eine Ausnahme aus dem try heraus, und das finally
läuft trotzdem. Wenn du das Zeile für Zeile sehen willst, nimm den Reiter Debug und klick auf
Aufzeichnen: Nach der return-Zeile kommt noch einmal die Zeile mit Datei geschlossen, und
erst dann geht es beim Aufrufer weiter.
Dafür ist es da: aufräumen. Eine Datei schließen, eine Verbindung beenden, eine Sperre lösen. Alles, was passieren muss, egal wie die Sache ausgeht.
Ein return in den finally-Block zu schreiben ist übrigens möglich und überschreibt dann das
return aus dem try. Tu es nicht. Es liest sich wie ein Zauberkunststück und niemand rechnet
damit.
Was du aus einer Ausnahme herausholst
<?php
try {
throw new RuntimeException("Die Datenbank antwortet nicht");
} catch (RuntimeException $fehler) {
echo "Nur die Meldung:\n";
echo $fehler->getMessage(), "\n";
echo "\nDie ganze Ausnahme:\n";
echo $fehler, "\n";
}
echo "\nUnd eine Meldung, die selbst einen Pfad enthaelt:\n";
function alterVon(int $jahr): int
{
return 2026 - $jahr;
}
try {
alterVon("neunzehn");
} catch (TypeError $fehler) {
echo $fehler->getMessage(), "\n";
} getMessage() gibt dir den Text, mit dem die Ausnahme angelegt wurde. Das ist fast immer das, was du
willst.
Was du nicht willst, steht darunter. Gibst du die Ausnahme selbst aus, bekommst du den
Klassennamen, die Meldung, den vollen Pfad der Datei, die Zeilennummer und einen Stack trace
obendrauf. In diesem Kurs ist das /vercel/sandbox/lern/index.php, auf einem echten Server ist es
der Pfad, unter dem deine Anwendung liegt. Das ist eine Information, die niemand von außen bekommen
sollte, und in Abschnitt 15 siehst du, was sich damit anfangen lässt.
Der letzte Teil des Beispiels ist die Einschränkung dazu, und sie ist wichtig: Bei einem TypeError
steht der Pfad in der Meldung selbst. getMessage() ist also die bessere Wahl, aber keine
Zusage. Die einzige verlässliche Regel lautet: Was der Benutzer sieht, schreibst du selbst. Die
Einzelheiten gehen ins Protokoll, und wie das geht, steht in Lektion 7.5.
Der Block, der nichts tut
<?php
function preisInCent(string $eingabe): int
{
try {
if (!is_numeric($eingabe)) {
throw new InvalidArgumentException("Das ist keine Zahl");
}
return (int) round((float) $eingabe * 100);
} catch (InvalidArgumentException $fehler) {
// Hier steht nichts. Genau das ist das Problem.
}
return 0;
}
echo "Ein Preis: ", preisInCent("24.99"), "\n";
echo "Ein Tippfehler: ", preisInCent("24,99"), "\n";
echo "Beide Zeilen sehen gleich vertrauenswuerdig aus.\n"; Ein catch-Block ohne Inhalt fängt die Ausnahme und schweigt. Das Programm läuft weiter, als wäre
nichts gewesen, liefert 0 und sieht dabei genauso aus wie ein richtiges Ergebnis.
Vergleich das mit dem Zustand ohne try: Dann wäre das Programm abgestürzt, und jemand hätte den
Fehler gefunden. Der leere catch hat aus einem lauten Problem ein leises gemacht, und das ist
dieselbe Verschlechterung wie die Warnung aus Lektion 7.1.
Wenn du wirklich nichts tun willst, schreib wenigstens hin, warum. Ein Kommentar im catch-Block ist
das Mindeste, eine Meldung auf dem Fehlerkanal ist besser.
Error und Exception sind zwei Familien
<?php
echo "Erster Versuch, mit Exception:\n";
try {
try {
intdiv(100, 0);
} catch (Exception $fehler) {
echo " gefangen als Exception\n";
}
} catch (Throwable $fehler) {
echo " durchgeflogen, es war ein ", get_class($fehler), "\n";
}
echo "Zweiter Versuch, mit Throwable:\n";
try {
intdiv(100, 0);
} catch (Throwable $fehler) {
echo " gefangen als Throwable\n";
} catch (Exception $fehler) fängt nur Ausnahmen aus der Exception-Familie. Ein
DivisionByZeroError gehört nicht dazu, er ist ein Error, und der fliegt an so einem catch
vorbei, als stünde es gar nicht da.
Beide Familien haben ein gemeinsames Dach: Throwable. Wer wirklich alles fangen will, fängt
Throwable.
Für den Alltag trotzdem die umgekehrte Empfehlung: Fang so eng wie möglich. Exception für
Dinge, mit denen du rechnest, und ein eigener Typ, sobald du zwei Fälle unterscheiden willst. Genau
darum geht es in der nächsten Lektion.
Zum Mitnehmen
Bis hierhin war ein Fehler etwas, das dir passiert. Ab jetzt ist er etwas, worüber du entscheidest. Drei Schlüsselwörter reichen dafür, und das dritte davon ist das, das die meisten falsch einschätzen.
Jetzt du
Basis Konto, kostenlosZu 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 5 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.