In PHP setzt du Code, der scheitern könnte, in try { ... } und behandelst das Scheitern in catch (Exception $e) { ... }. Wirft irgendetwas im try-Block, springt PHP direkt zum passenden catch, und $e->getMessage() sagt dir, was schiefgelaufen ist. Wirft nichts, wird der catch-Block übersprungen.
Ändere divide(10, 0) in divide(10, 5) und führe den Code erneut aus: keine Exception, also wird not printed ausgegeben und der catch-Block übersprungen. Ohne try würde die Exception das Skript mit einem fatalen Fehler „Uncaught“ beenden. Wie du eigene Exceptions wirfst und Exception-Klassen schreibst, steht auf der Seite zu Exceptions.
Die Syntax von try catch
Die vollständige Form hat drei Teile. Nur try ist Pflicht, dazu mindestens ein catch oder ein finally:
try {
// code that may throw
} catch (SomeException $e) {
// runs if SomeException (or a subclass) was thrown
} catch (OtherException | ThirdException $e) {
// runs for either of these types
} finally {
// always runs, thrown or not
}
Der Typ in catch entscheidet, was abgefangen wird: die Klasse selbst und jede Klasse, die sie erweitert. Seit PHP 8.0 ist die Variable optional, wenn du sie nicht brauchst: catch (JsonException) { ... }.
Exception vs Error vs Throwable abfangen
Seit PHP 7 werfen Fehler, die PHP selbst erkennt (Aufruf einer fehlenden Methode, ein falscher Argumenttyp, Division durch null), einen Error und keine Exception, und PHP 8 hat viele frühere Warnungen in TypeError und ValueError umgewandelt. Beide implementieren das Interface Throwable, aber keines erweitert das andere, also fängt catch (Exception $e) keinen Error ab:
Throwable
├── Exception (problems a program expects and handles)
│ ├── InvalidArgumentException, RuntimeException, JsonException, ...
└── Error (engine errors, usually a bug in the code)
├── TypeError
├── ValueError
├── ArithmeticError
│ └── DivisionByZeroError
└── UnhandledMatchError, ...
Führe das aus, um zu sehen, welcher catch welches Problem behandelt:
Was solltest du also abfangen?
catch (SomeSpecificException $e), wenn du weißt, was schiefgehen kann und was dann zu tun ist. Das ist der meiste Code.catch (Exception $e)für „alles, was eine Bibliothek oder mein Code absichtlich geworfen hat“.catch (Throwable $e)auf der äußersten Ebene (ein Request-Handler, ein Job-Runner), um jeden Fehler zu protokollieren, PHPs eigene Fehler eingeschlossen, und eine freundliche Meldung anzuzeigen.
Mehrere Exception-Typen abfangen
Schreib mehrere catch-Blöcke, um verschiedene Typen unterschiedlich zu behandeln. PHP probiert sie von oben nach unten und nimmt den ersten Treffer, also muss ein spezifischer Typ vor seiner Elternklasse stehen, sonst fängt die Elternklasse zuerst alles ab. Wenn mehrere Typen gleich behandelt werden, kombinierst du sie in einem catch mit |:
Alle drei Klassen erweitern Exception, also würde auch ein einziges catch (Exception $e) sie abfangen, aber dann könnte der Code nicht mehr für die eine mit 404 und für die anderen mit 400 antworten.
finally: Code, der immer läuft
Ein finally-Block läuft nach try und catch, egal wie sie geendet haben: normal, mit einer abgefangenen Exception, mit einer Exception, die niemand abgefangen hat, oder mit einem return. Hier gibst du frei, was der try-Block belegt hat: eine Datei schließen, eine Sperre lösen, einen Timer stoppen.
Bei good.txt wird die Datei geschlossen, bevor saved ausgegeben wird, weil finally läuft, bevor die Funktion tatsächlich zurückkehrt. Bei bad.txt gibt es in process() kein catch: finally schließt die Datei, dann läuft die Exception weiter bis zum catch des Aufrufers.
Vermeide return in finally: Es ersetzt den Wert, den der try-Block zurückgegeben hat, und verwirft still eine Exception, die gerade unterwegs war:
function f(): string
{
try {
return 'from try';
} finally {
return 'from finally';
}
}
function g(): string
{
try {
throw new RuntimeException('lost');
} finally {
return 'no exception reaches the caller';
}
}
echo f(); // from finally
echo g(); // no exception reaches the caller
Eine Exception erneut werfen oder einpacken
Manchmal kann ein catch-Block ein Problem nur teilweise behandeln: protokollieren, aufräumen und es dann weiterlaufen lassen. throw $e; wirft dieselbe Exception erneut. Öfter willst du Kontext hinzufügen und die ursprüngliche Ursache behalten, und dafür ist das dritte Konstruktorargument $previous da:
Der Aufrufer hat es mit einer ConfigException zu tun und muss nicht wissen, dass die Konfiguration JSON ist, während getPrevious() das Detail der unteren Ebene fürs Log behält.
Nicht abgefangene Exceptions und ein globaler Handler
Eine Exception, die kein catch behandelt, beendet das Skript mit einem fatalen Fehler, der Klasse, Meldung, Datei und Zeile sowie einen Stack Trace enthält:
<?php
// No try/catch anywhere in this file
function charge(int $cents): void
{
if ($cents <= 0) {
throw new DomainException("Amount must be positive, got $cents");
}
}
charge(500);
echo "first charge ok\n";
charge(-1);
echo "never printed\n";
PHP gibt first charge ok aus und dann:
PHP Fatal error: Uncaught DomainException: Amount must be positive, got -1 in /home/index.php:6
Stack trace:
#0 /home/index.php(12): charge()
#1 {main}
thrown in /home/index.php on line 6
Auf einer Live-Website sollte diese Meldung nie bei Besuchern ankommen. set_exception_handler() registriert eine Funktion, die jede Exception bekommt, die niemand abgefangen hat, eine einzige Stelle, um sie zu protokollieren und eine höfliche Seite auszugeben:
Der Handler läuft, und dann stoppt das Skript; nach dem throw gibt es keinen Weg, weiterzumachen. Ob PHPs eigener Fehlertext überhaupt auf der Seite erscheint, steuert display_errors, behandelt unter Fehlerausgabe.
Warnungen sind keine Exceptions
try/catch sieht nur Dinge, die geworfen werden. Viele ältere PHP-Funktionen werfen nicht: Sie liefern false oder null und geben höchstens eine Warnung aus. json_decode() ist ein typischer Fall, und es in try zu setzen ändert nichts, bis du es zum Werfen aufforderst:
Bei Funktionen ohne ein solches Flag prüfst du den Rückgabewert (if ($handle === false)) oder vorher die Voraussetzung (file_exists(), isset()). Ein set_error_handler(), der ErrorException wirft, macht jede Warnung zu einer echten Exception, wenn du eine Regel für alles willst.
Rezept: Formulareingaben mit Exceptions prüfen
Ein praktisches Muster: Validierungsfunktionen werfen, die Seite fängt ab und zeigt die Meldung neben dem Formular. Führe es aus, tippe dann ein Alter ins Formular und klicke auf Send: Dasselbe Skript läuft erneut, mit gefülltem $_POST.
Probier ein leeres Feld, twelve, 9 und 30. Jede verletzte Regel wirft mit ihrer eigenen Meldung, und ein einziges catch macht aus allen dieselbe rote Zeile. Die Seite zu Formularen baut darauf mit mehreren Feldern und Fehlern neben jedem auf.
Häufig gestellte Fragen
Wie funktioniert try catch in PHP?
PHP führt den Code in try { } aus. Wirft etwas darin, überspringt PHP den Rest des try-Blocks und führt den ersten catch-Block aus, dessen Typ passt, mit dem geworfenen Objekt in der Variable: catch (Exception $e) { echo $e->getMessage(); }. Wirft nichts, werden die catch-Blöcke übersprungen.
Wie fange ich in PHP alle Exceptions und Fehler ab?
Fang Throwable ab: catch (Throwable $e). catch (Exception $e) fängt nur Exceptions ab, keine Engine-Fehler wie TypeError, ValueError oder DivisionByZeroError, die Error erweitern. Sowohl Exception als auch Error implementieren Throwable.
Wie fange ich in PHP mehrere Exceptions in einem catch-Block ab?
Trenne die Typen mit einem senkrechten Strich: catch (InvalidArgumentException | RangeException $e). Du kannst auch mehrere catch-Blöcke hintereinander schreiben; PHP verwendet den ersten passenden, also setze die spezifischsten Typen nach vorn.
Läuft finally, wenn in try ein return steht?
Ja. finally läuft nach try und catch, egal ob der Code zurückgekehrt ist, geworfen hat oder normal fertig wurde, auch wenn try ein return enthält. Liefert finally selbst einen Wert zurück, ersetzt dieser den Wert aus try, also vermeide return in finally.
Fängt try catch in PHP Warnungen ab?
Nein. Warnungen und Notices (etwa beim Lesen eines fehlenden Array-Schlüssels) sind keine Exceptions, also sieht catch sie nie. Prüfe die Bedingung vor dem Aufruf, verwende den Exception-Modus der Funktion, wo es einen gibt (json_decode(..., flags: JSON_THROW_ON_ERROR)), oder wandle Warnungen mit set_error_handler() und ErrorException um.