W3docs

PHP Exception

PHP-Exceptions werfen und abfangen mit try/catch/finally, Exception-Methoden, Multi-Catch und eigene Exception-Klassen schreiben.

Was ist eine Exception?

Eine Exception ist ein Objekt, das einen Fehler oder eine unerwartete Bedingung darstellt, die den normalen Programmablauf unterbricht. Anstatt ein Problem das Skript stillschweigend zum Absturz bringen zu lassen, wirft man an der Stelle, wo etwas schiefgeht, eine Exception und fängt sie dort ab, wo man sie behandeln, protokollieren oder melden kann.

Diese Seite erklärt, wie man Exceptions mit try/catch/finally wirft und abfängt, welche Methoden jedes Exception-Objekt bereitstellt, wie man mehrere Exception-Typen abfängt und wie man eigene Exception-Klassen schreibt.

Exceptions sind immer dann sinnvoll, wenn eine Funktion nicht sinnvoll fortfahren kann: bei ungültiger Benutzereingabe, einer fehlgeschlagenen Datenbankverbindung, einer fehlenden Datei oder einem Wert außerhalb des erlaubten Bereichs. Sie halten die Fehlerbehandlungslogik von der normalen Logik getrennt, sodass der „Happy Path" lesbar bleibt.

throw new Exception('Something went wrong');

In PHP 7 und später implementieren sowohl Exception als auch Error das Throwable-Interface, sodass alles, was geworfen werden kann, ein Throwable ist.

Werfen und Abfangen mit try / catch / finally

Riskanten Code umschließt man mit einem try-Block. Wenn eine Anweisung darin eine Exception wirft, stoppt PHP die Ausführung des restlichen try-Blocks und springt zum ersten passenden catch. Der optionale finally-Block wird immer danach ausgeführt — ob eine Exception geworfen wurde oder nicht — was ihn zum richtigen Ort macht, um Ressourcen freizugeben (eine Datei, ein Datenbank-Handle oder eine Sperre zu schließen).

<?php
function divide($a, $b) {
    if ($b === 0) {
        throw new InvalidArgumentException('Division by zero is not allowed.');
    }
    return $a / $b;
}

try {
    echo divide(10, 2), "\n";   // 5
    echo divide(10, 0), "\n";   // throws — the next line never runs
} catch (InvalidArgumentException $e) {
    echo 'Caught: ' . $e->getMessage() . "\n";
} finally {
    echo "Done.\n";
}

Ausgabe:

5
Caught: Division by zero is not allowed.
Done.

Beachten Sie, dass das zweite echo divide(...) nie ausgeführt wird, weil der Throw den try-Block sofort abbricht. Der finally-Block wird dennoch ausgeführt.

Informationen aus einer Exception auslesen

Jedes Exception-Objekt enthält nützliche Details. Die häufigsten Methoden, die alle von der Basisklasse Exception geerbt werden, sind:

MethodeGibt zurück
getMessage()Die lesbare Fehlermeldung
getCode()Den ganzzahligen Fehlercode, der dem Konstruktor übergeben wurde
getFile()Die Datei, in der die Exception erzeugt wurde
getLine()Die Zeilennummer, in der sie erzeugt wurde
getTraceAsString()Den Call-Stack als String, nützlich für das Protokollieren
getPrevious()Die vorherige Exception, wenn eine die andere umschließt
<?php
class InsufficientFundsException extends Exception {}

class Account {
    private float $balance;
    public function __construct(float $balance) { $this->balance = $balance; }

    public function withdraw(float $amount): void {
        if ($amount > $this->balance) {
            throw new InsufficientFundsException(
                "Cannot withdraw $amount; balance is {$this->balance}.",
                100 // a custom error code
            );
        }
        $this->balance -= $amount;
    }
}

$account = new Account(50);
try {
    $account->withdraw(80);
} catch (InsufficientFundsException $e) {
    echo $e->getMessage() . "\n";  // Cannot withdraw 80; balance is 50.
    echo 'Code: ' . $e->getCode() . "\n"; // Code: 100
}

Ausgabe:

Cannot withdraw 80; balance is 50.
Code: 100

Mehrere Exception-Typen abfangen

Ein einzelner try-Block kann mehrere catch-Blöcke haben, die von oben nach unten geprüft werden — der erste, dessen Typ zur geworfenen Exception passt, gewinnt. Wenn zwei nicht verwandte Exception-Typen gleich behandelt werden sollen, kombiniert man sie in einem Block mit dem |-Operator (Pipe), anstatt Code zu duplizieren.

<?php
function parseAge(string $input): int {
    if (!is_numeric($input)) {
        throw new TypeError("'$input' is not a number.");
    }
    $age = (int) $input;
    if ($age < 0) {
        throw new RangeException("Age cannot be negative.");
    }
    return $age;
}

foreach (['42', 'abc', '-5'] as $value) {
    try {
        echo parseAge($value) . "\n";
    } catch (TypeError | RangeException $e) {
        echo get_class($e) . ': ' . $e->getMessage() . "\n";
    }
}

Ausgabe:

42
TypeError: 'abc' is not a number.
RangeException: Age cannot be negative.

Die Reihenfolge ist entscheidend: Da PHP-Exceptions eine Hierarchie bilden, sollte man spezifischere Typen zuerst und allgemeinere (wie Exception oder Throwable) zuletzt angeben, sonst fängt der allgemeine Block alles ab, bevor der spezifische eine Chance bekommt.

Eigene Exceptions erstellen

Über die eingebauten Klassen hinaus kann man eigene Exception-Typen definieren, indem man Exception (oder eine spezifischere eingebaute Klasse wie RuntimeException) erweitert. Eine dedizierte Klasse macht catch-Blöcke ausdrucksstärker — man kann auf eigene Fehler gezielt reagieren — und erlaubt es, zusätzliche Daten anzuhängen.

Im obigen Account-Beispiel ist InsufficientFundsException eine benutzerdefinierte Exception. Oft reicht eine leere Unterklasse aus; Methoden fügt man nur dann hinzu, wenn man zusätzliches Verhalten benötigt:

<?php
class ValidationException extends Exception {
    private array $errors;

    public function __construct(string $message, array $errors = []) {
        parent::__construct($message);
        $this->errors = $errors;
    }

    public function getErrors(): array {
        return $this->errors;
    }
}

Wenn man den Konstruktor überschreibt, sollte man immer parent::__construct() aufrufen, damit Nachricht, Code und vorherige Exception korrekt gesetzt werden.

Throwable abfangen. Um sowohl gewöhnliche Exceptions als auch Fehler auf Engine-Ebene (wie einen TypeError bei einem falschen Argumenttyp) zu behandeln, fängt man Throwable ab. Es ist die sicherste „Catch-all"-Option, sollte aber als letztes Mittel eingesetzt werden, damit man keine Fehler unbemerkt verschluckt, die man eigentlich beheben sollte:

try {
    // risky code
} catch (Throwable $e) {
    error_log($e->getMessage());
}

Best Practices

  • Nur abfangen, was man behandeln kann. Exceptions, von denen man sich nicht erholen kann, sollten bis zu einem zentralen Handler weitergeleitet werden.
  • Früh werfen, spät abfangen. An genau der Stelle werfen, wo ein Wert ungültig wird; dort abfangen, wo man tatsächlich reagieren kann.
  • Spezifische Typen verwenden. Benutzerdefinierte oder eingebaute Unterklassen sind besser als eine einfache Exception für den Kontrollfluss.
  • Informative Meldungen schreiben und getCode() für maschinenlesbare Kategorisierung verwenden.
  • Protokollieren, nicht verschlucken. Exceptions mit error_log() aufzeichnen, anstatt sie zu ignorieren. Für nicht abgefangene Exceptions einen globalen Handler mit set_exception_handler() registrieren.
  • Exceptions nicht für normalen Kontrollfluss verwenden. Sie sind für wirklich außergewöhnliche Bedingungen reserviert.

Wie der Kontrollfluss verläuft

Das folgende Diagramm zeigt den Ausführungsweg durch eine try/catch/finally-Struktur:

graph TD
  Try[Try Block] -->|No Exception| Finally[Finally Block]
  Try -->|Exception Thrown| Catch[Catch Block]
  Catch --> Finally

Übungen

Übung
Was trifft auf die PHP-Exception-Behandlung zu?
Was trifft auf die PHP-Exception-Behandlung zu?
Was this page helpful?