© fran_kies/Shutterstock.com
Effiziente Fehlerbehandlung selbst bei wachsender Komplexität

PHP Exceptions strukturieren


Es ist sehr schwierig, Lehrmaterial zum richtigen Umgang mit PHP Exceptions zu finden, das über die wesentlichen Grundlagen hinausgeht. Es sind sich zwar fast alle einig, dass man Exceptions nutzen sollte, doch wie man sie strukturiert und in einem größeren Projekt verwaltet, bleibt außen vor. Dabei ist es bei größeren Projekten umso wichtiger, gleich mit der richtigen Struktur anzufangen, um später ein teures Refactoring zu vermeiden. Wir untersuchen in diesem Artikel, wie man Exceptions optimal in PHP aufsetzt, nutzt und dabei die gröbsten Stolperfallen vermeidet.

Bevor wir Details in der Implementierung diskutieren, halte ich es für sinnvoll, die Hauptvorteile von Exceptions vorzustellen, wenn man sie mit trigger_error() oder auch der Rückgabe eines Fehlerobjekt vergleicht.

Warum überhaupt Exceptions nutzen?

Normale PHP-Fehler und -Warnungen kommen aus der prozeduralen Welt und bieten einen zentralen Mechanismus an, um diese Fehler abzuhandeln: eine Funktion oder Methode, die man als Fehlerbehandlungsroutine mittels set_error_handler() definiert. Man kann zwar unterschiedliche Behandlungsroutinen für unterschiedliche Fehlerstufen festlegen, aber ansonsten hat man keine weitere Möglichkeit, den genauen Punkt in der Ausführung zu bestimmen, an dem man die Fehler abhandeln möchte. Die Fehlerbehandlungsroutine liegt außerhalb des Kontexts des eigentlichen Ausführungsverlaufs. Somit ist es eher schwierig, den Fehler sinnvoll zu beheben oder abzuhandeln, wenn man erst einmal an diesem Punkt angelangt ist.

Exceptions hingegen können an jedem beliebigen Punkt im Quellcode abgehandelt werden. Und da sie über eine Art Eskalation durch die einzelnen Layer in der Applikation wandern, kann man für jede Exception entscheiden, in welchem Layer und in welchem Kontext man sie abhandeln möchte. Dies ermöglicht es, Exceptions direkt in dem aufrufenden Code zu behandeln, wenn das Sinn ergibt, oder sie bis an die Oberfläche durchlaufen zu lassen, wenn das nicht der Fall ist. Ruft der Code einen Eintrag aus der Datenbank ab und wirft zum Beispiel eine Exception, kann das auf nuancierte Weise und je nach Typ der Exception angegangen werden. Konnte die Datenbank kein Resultat liefern, weil die angeforderte ID nicht bekannt ist, kann eventuell ein NullObject zurückgeben werden, statt einen Fehler zu generieren. Das lässt den User weiter mit dem System arbeiten, ohne dass der Server einen fatalen Fehler wiedergibt. Konnte hingegen die Verbindung zur Datenbank nicht aufgebaut werden, muss die Exception vermutlich bis in die Infrastrukturlayer weiterlaufen, wo sie umgehend auf ein größeres Problem hinweisen kann.

Eine weitere populäre Art der Fehlerverwaltung ist es, den Rückgabewert zu nutzen, um einen Fehlerwert oder ein Fehlerobjekt zurückzugeben. Das wird beispielsweise von Frameworks wie WordPress genutzt, das ein WP_Error-Objekt zurückgibt, das eine Fehlermeldung und einen Fehlercode beinhaltet. Ein offensichtlicher Nachteil dieser Methode gegenüber Exceptions ist, dass dadurch die Typdeklaration vom Rückgabewert nutzlos gemacht wird, da man den eigentlich gewollten Typ des Rückgabewerts nicht mehr erzwingen kann, um auch die Rückgabe von einem WP_Error-Objekt zu erlauben. Das ist bei Exceptions nicht der Fall, die einen separaten Transport zum Rückgabewert nutzen. Ein weniger offensichtlicher Nachteil ist es, dass diese Rückgabewerte uns zwingen, Teile des Codes auf Error Conditions zu überprüfen, die weder Ursprung noch Abhandlungspunkt eines Fehlers sind. Jede einzelne Funktion oder Methode dazwischen, die mit dem Rückgabewert interagiert oder ihn weitergibt, muss die Möglichkeit eines Fehlerobjekts mit einbeziehen und die Logik entsprechend anpassen, wohingegen dieser Code sich bei Exceptions ausschließlich auf die reine Geschäftslogik konzentrieren kann.

Auf die SPL Exceptions aufbauen

Die Standard PHP Library (SPL) bietet eine vordefinierte Sammlung von Exceptions, die ich als gutes Fundament wärmstens empfehle. Sie bieten eine verallgemeinerte Klassifizierung, und wer die eigenen Exceptions aus ihnen ableitet, macht es sich bei dem späteren Nutzercode dieser Exceptions leichter, sie korrekt zu catchen. Zum Beispiel kann der Nutzercode sich entscheiden, die allgemeine Gruppe der RuntimeException abzufangen, die die regulären PHP Exceptions ebenso wie die angepassten eigenen Erweiterungen dazu beinhaltet. Für meine eigenen Komponenten habe ich eine Sammlung von Exception-Gruppen, die ich über Composer einbinde. Diese Composer spiegeln die SPL-Gruppen und fügen dabei ein sogenanntes Marker-Interface hinzu, das spezifisch für die Organisation dieser Komponenten ist. Alle meine spezifischen Exceptions erweitern dann eine dieser Gruppen. Der Zweck ist, dass dieser zusätzliche Layer eine weitere Dimension zum Filtern von Exceptions hinzufügt, wenn man Exceptions catchen will. Ich nutze das in großen Projekten oder Projektgruppen, bei denen die Geschäftslogik auf einem eigens entwickelten Framework aufbaut, das aus einzelnen Komponenten besteht.

Jede Basis-Exception in dieser Sammlung erweitert zugleich eine der SPL Exceptions u...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang