Eigene Exceptions in PHP – Fehler, die man auseinanderhalten kann
catch (Exception $e) fängt alles – auch das, was du gar nicht behandeln wolltest. Mit ein paar eigenen Exception-Klassen wird aus „irgendwas ging schief" eine Fehlermeldung, mit der der Nutzer etwas anfangen kann.
Diese Zeile steht in erschreckend vielen PHP-Projekten:
try {
$rechnung = erstelleRechnung($daten);
} catch (Exception $e) {
echo 'Es ist ein Fehler aufgetreten.';
}Das Problem ist nicht der try-Block, sondern dass hier alles in denselben Topf fällt: die fehlende Kundennummer, der abgestürzte Datenbankserver und der Tippfehler im Feldnamen. Der Nutzer bekommt denselben nichtssagenden Satz, und du beim Debuggen auch.
Ein paar eigene Klassen reichen
class RechnungsFehler extends RuntimeException {}
final class KundeFehlt extends RechnungsFehler {}
final class BetragUngueltig extends RechnungsFehler
{
public function __construct(public readonly int $cent)
{
parent::__construct('Betrag ungültig: ' . $cent . ' Cent');
}
}Mehr ist das nicht. Eine leere Klasse, die von RuntimeException erbt, ist eine vollwertige Exception — und trägt jetzt eine Information im Namen.
Damit wird aus dem Sammel-catch etwas Nützliches:
try {
$rechnung = erstelleRechnung($daten);
} catch (KundeFehlt) {
fehlermeldung('Bitte wähle zuerst einen Kunden aus.');
} catch (BetragUngueltig $e) {
fehlermeldung('Der Betrag ' . formatEur($e->cent) . ' ergibt keinen Sinn.');
} catch (PDOException $e) {
log_fehler($e);
fehlermeldung('Die Datenbank antwortet gerade nicht. Bitte später erneut versuchen.');
}Drei Fälle, drei Reaktionen. Die ersten beiden kann der Nutzer selbst beheben — die dritte nicht, deshalb wird sie protokolliert statt erklärt.
Übrigens: Seit PHP 8 darfst du die Variable weglassen (catch (KundeFehlt)), wenn du sie gar nicht brauchst.
Die Ursache mitschleppen
Wenn du einen technischen Fehler in einen fachlichen übersetzt, wirf die Ursache nicht weg:
try {
$db->prepare($sql)->execute($werte);
} catch (PDOException $e) {
throw new RechnungsFehler('Rechnung konnte nicht gespeichert werden.', 0, $e);
}Der dritte Parameter ist die vorherige Exception. Im Log siehst du dann beides: den verständlichen Satz und darunter die echte SQL-Meldung samt Zeilennummer. Ohne das steht im Log nur noch deine eigene Zusammenfassung, und die hilft beim Suchen selten weiter.
finally – das, was immer passieren muss
$fh = fopen($pfad, 'wb');
try {
schreibeDaten($fh, $zeilen);
} catch (IOFehler $e) {
protokolliere($e);
throw $e;
} finally {
fclose($fh); // läuft in JEDEM Fall
}finally läuft, egal wie der Block endet: normal, mit return, oder weil eine Exception nach oben durchgereicht wird. Genau dorthin gehört alles, was aufgeräumt werden muss — Dateien schließen, eine Transaktion zurückrollen, eine Sperre freigeben.
Meine Regel
Wirf eine Exception, wenn die Funktion ihre Aufgabe nicht erfüllen kann. Gib false oder null zurück, wenn „nichts gefunden" ein ganz normales Ergebnis ist. Ein kundeSuchen(), das bei „gibt es nicht" eine Exception wirft, macht jeden Aufrufer umständlicher — ein rechnungSpeichern(), das bei einem Fehler stillschweigend false zurückgibt, produziert dagegen Datenverlust, den niemand bemerkt.
Und: niemals einen leeren catch. Wenn du einen Fehler wirklich ignorieren willst, schreib wenigstens einen Kommentar dazu, warum das in Ordnung ist. In sechs Monaten weißt du es sonst nicht mehr.
Du hast ein PHP-Projekt, das bei Fehlern nur eine weiße Seite zeigt? Da lässt sich mit wenig Aufwand viel gewinnen. Schreib mir.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
json_decode mit JSON_THROW_ON_ERROR – Fehler, die man nicht übersehen kann
Kaputtes JSON gibt bei json_decode ein stilles null zurück – dasselbe null, das auch im gültigen JSON stehen kann. Ein Flag macht daraus eine Ausnahme, und aus der Fehlersuche wird eine Zeile Stacktrace.
Beträge und Zahlen in PHP formatieren – number_format, sprintf und Intl
1234.5 ist keine Anzeige, sondern ein Wert. Auf der Rechnung soll 1.234,50 € stehen, in der API 1234.50 und im Dateinamen 1234-50. Drei Aufgaben, drei Werkzeuge – und ein Fehler, den fast jeder einmal macht.
Benannte Argumente in PHP 8 – Schluss mit true, false, null, true
Ein Aufruf wie erstelle($a, true, false, null, true) sagt nichts darüber, was die Wahrheitswerte bedeuten. Mit benannten Argumenten steht es im Aufruf – und optionale Parameter lassen sich überspringen.