Eine Exception wirfst du in PHP mit throw new Exception('message');. Die Ausführung stoppt in dieser Zeile und springt zum nächsten catch-Block, der den Typ der Exception akzeptiert, wo $e->getMessage() die Meldung liefert. Verwende eine bestimmte Klasse wie InvalidArgumentException, damit Aufrufer Probleme unterscheiden können.
Eine Exception ist ein Objekt. throw reicht sie den Aufrufstapel hinauf zum ersten Aufrufer mit passendem catch, und dieser Aufrufer entscheidet, was zu tun ist; die Funktion, die das Problem gefunden hat, muss das nicht wissen. Die Mechanik des Abfangens, finally und das Abfangen mehrerer Typen stehen auf der Seite zu try catch.
Was ein Exception-Objekt enthält
Jede Exception trägt eine Meldung, einen Ganzzahl-Code, die Datei und Zeile, in der sie erzeugt wurde, und einen Stack Trace. Der Konstruktor nimmt ($message, $code, $previous), alles optional:
getLine() ist die Zeile von throw new, nicht die Zeile des catch. Der Trace listet die Aufrufe auf, die dorthin geführt haben (#0 /home/index.php(8): findOrder()), und das liest man meist zuerst, wenn ein Fehler in einem Log auftaucht. Die Exception in einen String umzuwandeln (echo $e; oder (string) $e) gibt alles im Standardformat von PHP aus.
Eingebaute Exception-Klassen
PHP bringt in seiner Standard PHP Library (SPL) eine Familie von Exception-Klassen mit. Diejenige zu werfen, die das Problem benennt, macht deinen Code lesbarer und lässt Aufrufer gezielt abfangen. Alle diese Klassen erweitern Exception:
| Klasse | Wirf sie, wenn |
|---|---|
InvalidArgumentException | ein Argument die falsche Form hat: ein leerer Name, eine unbekannte Option |
DomainException | ein Wert außerhalb der sinnvollen Menge liegt: ein negativer Preis |
OutOfRangeException | der Code nach einem Index gefragt hat, der nie existieren kann (ein Bug beim Aufrufer) |
OutOfBoundsException | ein Schlüssel oder Index in Daten fehlt, die erst zur Laufzeit bekannt sind |
RangeException | ein berechnetes Ergebnis während der Laufzeit außerhalb des gültigen Bereichs liegt |
LengthException | etwas zu lang oder zu kurz ist |
RuntimeException | ein Problem erst zur Laufzeit erkennbar ist: eine volle Festplatte, ein Timeout |
UnexpectedValueException | eine Funktion einen Wert eines unerwarteten Typs geliefert oder bekommen hat |
LogicException | der Code selbst falsch ist, ein Bug statt einer schlechten Eingabe |
JsonException | geworfen von json_encode()/json_decode() mit JSON_THROW_ON_ERROR |
InvalidArgumentException, DomainException, LengthException und OutOfRangeException erweitern LogicException; OutOfBoundsException, RangeException und UnexpectedValueException erweitern RuntimeException. Die Elternklassen jeder Klasse kannst du selbst prüfen:
TypeError hat Error als Elternklasse, nicht Exception: Es gehört zur zweiten Familie, unten.
Fehler, die PHP selbst wirft
Seit PHP 7, und viel stärker seit PHP 8, werfen PHPs eigene Funktionen und Operatoren Unterklassen von Error für Probleme, die früher Warnungen waren. Du fängst sie genauso ab, aber catch (Exception $e) passt nicht auf sie:
DivisionByZeroError, TypeError, ValueError, ArgumentCountError, UnhandledMatchError und das einfache Error erweitern alle Error. Jede davon markiert öfter einen Bug im aufrufenden Code als schlechte Daten, und deshalb leben sie außerhalb des Exception-Baums. declare(strict_types=1) ist das, was str_repeat(5, 2) zu einem TypeError macht; ohne würde PHP 5 in "5" umwandeln.
Eine eigene Exception-Klasse schreiben
Eine einzeilige Unterklasse ist oft alles, was du brauchst: Der Klassenname selbst trägt die Bedeutung, und Aufrufer können genau diesen Typ abfangen. Wenn der Abfangende Daten braucht (welche Bestellung, welcher Betrag), fügst du Eigenschaften hinzu und reichst die Meldung mit parent::__construct() weiter:
Wähl die Elternklasse mit Bedacht: RuntimeException zu erweitern (statt einfach Exception) bedeutet, dass Code, der RuntimeException abfängt, auch deine behandelt. Eine kleine Hierarchie, eine Basis-Exception pro Bibliothek oder Modul mit spezifischen Unterklassen darunter, lässt Aufrufer wählen zwischen „alles aus dem Zahlungsmodul abfangen“ und „nur genau diesen einen Fall abfangen“.
Exceptions mit previous verketten
Wenn du eine Exception einer unteren Ebene abfängst und eine einer höheren Ebene wirfst, übergib die ursprüngliche als drittes Argument. Nichts geht verloren: getPrevious() geht zurück bis zur eigentlichen Ursache.
Der Nutzer sieht „customers.csv could not be imported“; das Log, das die Kette abgeht, bekommt außerdem „row 2 has 2 columns“. Wird eine verkettete Exception nie abgefangen, gibt PHPs fataler Fehler die ganze Kette aus: zuerst die ursprüngliche Ursache, dann jede Hülle unter einer Zeile Next.
throw als Ausdruck
Seit PHP 8.0 ist throw ein Ausdruck und kann damit überall stehen, wo ein Wert erwartet wird: nach ??, in einem ternären Ausdruck, in einer Arrow Function. Das macht „hol das oder scheitere“ zu einer einzigen Zeile:
Wann man nicht wirft
Exceptions sind für Situationen, die die aktuelle Funktion nicht behandeln kann und die der normale Ablauf nicht erwartet. Eine Suche ohne Treffer, ein leeres optionales Feld oder ein Nutzer, der nicht angemeldet ist, sind gewöhnliche Ergebnisse: Liefere null, false oder ein leeres Array, lass den Aufrufer prüfen, und behalte Exceptions für die Fälle, in denen Weitermachen falsch wäre.
Zwei weitere Gewohnheiten halten Exception-Code lesbar: Fang nie eine Exception ab, nur um sie zu ignorieren (ein leeres catch (Exception $e) {} versteckt den nächsten echten Bug), und wirf bestimmte Klassen statt eines bloßen Exception, damit ein Aufrufer nie gezwungen ist, alles abzufangen, um einen Fall zu behandeln.
Häufig gestellte Fragen
Wie werfe ich in PHP eine Exception?
Erzeuge ein Exception-Objekt und wirf es: throw new InvalidArgumentException('Quantity must be positive');. Die Ausführung stoppt in dieser Zeile und springt zum nächsten passenden catch-Block; gibt es keinen, endet das Skript mit einem fatalen Fehler „Uncaught“.
Wie erstelle ich in PHP eine eigene Exception?
Erweitere Exception oder eine ihrer Unterklassen: class PaymentFailedException extends RuntimeException {}. Diese eine Zeile reicht, um sie unter ihrem eigenen Typ zu werfen und abzufangen. Füge Eigenschaften und einen Konstruktor hinzu, wenn der Abfangende zusätzliche Daten braucht, und rufe parent::__construct($message, $code, $previous) auf.
Was ist der Unterschied zwischen getMessage und getCode?
getMessage() liefert den Text, der als erstes Konstruktorargument übergeben wurde, gedacht für Menschen und Logs. getCode() liefert die als zweites Argument übergebene Ganzzahl (standardmäßig 0), nützlich für maschinelle Prüfungen, etwa um Fehler HTTP-Statuscodes zuzuordnen.
Welche Exception sollte ich in PHP werfen?
Verwende eine eingebaute SPL-Klasse, die zum Problem passt: InvalidArgumentException für ein ungültiges Argument, DomainException für einen Wert außerhalb des Erlaubten, RuntimeException für Fehler, die erst zur Laufzeit sichtbar werden (eine volle Festplatte, ein Timeout), LogicException für Programmierfehler. Für Fehler, die deine Aufrufer unterscheiden müssen, erstellst du eine eigene Unterklasse.
Was ist Exception Chaining in PHP?
Die ursprüngliche Exception als drittes Konstruktorargument zu übergeben, wenn du eine neue wirfst: throw new ImportException('Import failed', 0, $e);. Die neue Exception liefert Kontext, und $e->getPrevious() darauf liefert die ursprüngliche Ursache, also geht kein Detail verloren.