Auf dieser Seite... (ausblenden)
Häufig kommt es vor, dass ein Fehler in dem Kontext, in dem er auftritt (beispielsweise in einer Methode), nicht behandelt werden kann. Ein Grund hierfür kann sein, dass die für eine korrekte Behandlung benötigten Informationen nicht verfügbar sind. Vielmehr muss der Fehler an die aufrufende Instanz des Kontextes, in dem er aufgetreten ist, zur Behandlung signalisiert werden. Diese Signalisierung eines Fehlers an den aufrufenden Kontext kann auf zwei Arten erfolgen: "in-band" oder "out-of-band".
Bei der "In-band"-Methode wird ein Fehler durch einen ausgezeichneten Wert des Wertebereiches des Rückgabewertes signalisiert. So liefert die getpriority()-Funktion der UNIX-C-Bibliothek beispielsweise einen Wert vom Typ int als Ergebnis. Wie in der UNIX-C-Bibliothek üblich, dient auch hier der Wert -1 als Signal für einen Fehler. Dies führt jedoch zu einem Problem, da -1 ein legitimer Rückgabewert für getpriority() ist. Um unterscheiden zu können, ob eine zurückgegebene -1 einen Fehler signalisiert oder ein normaler Rückgabewert ist, muss der Aufrufer von getpriority() die Variable errno zu Rate ziehen.
Auszug aus der Manual-Page zu getpriority()
Since getpriority can legitimately return the value -1, it is necessary to clear the external variable errno prior to the call, then check it afterwards to determine if a -1 is an error or a legitimate value. The setpriority call returns 0 if there is no error, or -1 if there is.
Durch die Überprüfung einer zusätzlichen Variablen ist die "In-band"-Methode nicht nur umständlich, sondern auch häufig Grund für Programmierfehler, da keine syntaktische Unterscheidung von Fehlerzustand und Rückgabewert möglich ist. Ausnahmen, im Englischen Exceptions genannt, sind eine Möglichkeit, Fehlerzustände "out-of-band" zu kommunizieren. Hierbei treten die genannten "In-band"-Probleme nicht auf.
Code, der für die Behandlung einer Ausnahme typisch ist, wird in so genannten Exception-Klassen gekapselt, die sich in PHP von der Standardklasse Exception ableiten müssen.
Abbildung zeigt das UML-Klassendiagramm der Standardklasse Exception:
Attach:ClassDiagramException.png Δ
getCode()
liefert den optionalen numerischen Code der Ausnahme. Dieser Code kann beispielsweise einem Fehlercode entsprechen, wie ihn die PHP-Datenbankschnittstellen im Fehlerfall liefern.
getFile()
liefert den Namen der Datei, in deren Quelltext die durch das Exception-Objekt repräsentierte Ausnahme ausgelöst wurde.
getLine()
liefert die Nummer der Zeile, in der die durch das Exception-Objekt repräsentierte Ausnahme ausgelöst wurde.
getMessage()
liefert die optionale Nachricht der Ausnahme. Diese Nachricht kann beispielsweise über den Konstruktor einer Exception-Klasse gesetzt werden.
getTrace()
liefert die Aufrufliste (englisch: Stack-Trace) bis zur Auslösung der Ausnahme als Array. Dieses hat dieselbe Struktur wie das Ergebnis der PHP-Funktion debug_backtrace().
getTraceAsString()
liefert die Aufrufliste als String.
Die Methoden der Klasse Exception sind final und können daher in einer Kindklasse nicht redefiniert werden.
Eine Ausnahme (ein Objekt einer von Exception abgeleiteten Klasse) wird mit dem throw-Operator "geworfen". Die Ausführung des aktuellen Programmkontextes wird hierdurch abgebrochen und die Ausnahme wird an den aufrufenden Kontext zurückgegeben.
Wie in C# so wird auch in PHP (im Gegensatz zu beispielsweise Java) in der Signatur einer Methode nicht deklariert, welche Ausnahmen in ihrem Rumpf ausgelöst werden können. Ausnahmen sind in PHP "unchecked", was die Erweiterung und Vererbung von Klassen erleichtert.
Das folgende Beispiel zeigt die Klasse DB_Exception, die wir im Folgenden in unserer Datenbankklasse einsetzen möchten. Diese Klasse definiert keine eigene Funktionalität. Sie wird nur eingeführt, damit bei der Ausnahmenbehandlung anhand des Exception-Typs zwischen Datenbankfehlern (repräsentiert durch DB_Exception) und allgemeinen Fehlern (Exception) unterschieden werden kann.
Beispiel: Die Klasse DB_Exception
Das Beispiel zeigt, wie in den Methoden connect() und query() mit dem throw-Operator bei Auftreten eines Fehlers eine Ausnahme, in diesem Fall vom Typ DB_Exception, ausgelöst wird.
Beispiel: Fehlerbehandlung mit Ausnahmen
Ein Codeblock, in dem Ausnahmen ausgelöst werden können, wird mit dem try-Schlüsselwort ausgezeichnet und mit geschweiften Klammern umschlossen. Direkt nach diesem Block steht eine beliebige Anzahl von catch-Blöcken, für jede mögliche Ausnahme ein Block. Ein solcher Block beginnt mit dem catch-Schüsselwort, danach folgen in runden Klammern der Name der erwarteten Exception-Klasse und der Name einer Variablen, in der das entsprechende Objekt im catch-Block bereitgestellt werden soll. Der eigentliche catch-Block folgt direkt im Anschluss und ist analog zum try-Block mit geschweiften Klammern umschlossen. Beispiel zeigt die Verwendung von try und catch.
Beispiel: Verwendung der um Ausnahmebehandlung erweiterten Klasse
In anderen Programmiersprachen, die Ausnahmebehandlung anbieten (beispielsweise Java), kann nach einem catch-Block optional noch ein finally-Block folgen. Ein solcher Block wird auf jeden Fall ausgeführt. Eine typische Anwendung ist die Freigabe von Ressourcen oder das Schließen von Dateien. PHP bietet diesen Mechanismus in Version 5.0 noch nicht an, dieser kann jedoch nachgebildet werden.
Beispiel: Nachbildung von finally in PHP
Eine unbehandelte Ausnahme führt zum Abbruch der Programmausführung. Hierbei wird ein so genannter Stack-Trace ausgegeben, der bei der Suche nach der Ursache der Ausnahme helfen soll.
Beispiel: Eine unbehandelte Ausnahme führt zum Abbruch der Programmausführung
Fatal error: Uncaught exception 'Exception' with message 'Test' in /home/sb/uncaught_exception.php:2 Stack trace: #0 {main} thrown in /home/sb/uncaught_exception.php on line 2
Für die Verarbeitung von unbehandelten Ausnahmen kann eine PHP-Funktion mit der Funktion set_exception_handler()
registriert werden. Auf diesem Weg kann die Ausnahme beispielsweise in einem Logfile protokolliert oder die Ausgabe der Fehlermeldung angepasst werden.
Beispiel: Anpassen der Verarbeitung von unbehandelten Ausnahmen
(Quelle: Fehlerbehandlung, 28.02.2008)
Ein Fehler ist definiert als ein unerwartet, nicht korrekter Programmzustande, der nicht wieder behoben werden kann. Zur Vereinfachung dieser Definition gilt, dass die Behebung des Fehlers nicht innerhalb einer Methode möglich ist. Eine unvollständige Behebung gilt trotzdem als Behebung.
Beispiel 4-1. Eine typische Fehlersituation
In diesem Beispiel soll die Methode die Verbindung mit der Datenbank mit dem gegegebenem DSN herstellen. Die Methode ruft ihrerseits nur DB, wenn dieses Package einen Fehler wirft, kann nur eine Exception erzeugt und geworfen werden, ohne weiter Einfluß nehmen zu können.
Beispiel: Fehlerbehandlung mit Behebung
Das zweite Beispiel zeigt, wie eine Exception empfangen und behandelt wird. Die verwendete connectDB()-Methode kann nur einen Fehler melden, wenn die Verbindung fehlschlägt. Die übergeordnete Methode connect() hingegen weiss, dass das Objekt auch mit einer der anderen Datenbank-Verbindungen lauffähig ist. Deshalb kann der Fehler als behoben angesehen werden und die Exception wird nicht weitergeleitet.
Beispiel: Unvollständige Behebung
Die Fehlerbehebung führt zu Seiteneffekten, deshalb ist sie nicht vollständig. Das Programm kann weiterlaufen, die Exception gilt als behandelt und muss nicht weitergeleitet werden. Wie im vorherigen Beispiel sollte die aufgetretene Exception aber trotzdem geloggt werden oder eine andere Form der Warnung stattfinden.
Fehlerhafte Zustände müssen über Exceptions gemeldet werden. Nicht mehr verwendet werden sollten Fehlercodes oder ein Error-Objekt. Diese Regel gilt natürlich nicht, wenn das Package kompatibel mit PHP 4 bleiben muss.
Eine Exception sollte immer geworfen werden, wenn ein fehlerhafter Zustand auftritt, entsprechend der Definition im vorherigen Abschnitt. Die geworfene Exception sollte genügend Informationen enthalten, um den Fehler debuggen zu können und schnell dessen Grund herauszufinden. Bedenken Sie, dass in Produktionsumgebungen keine Exception an den Endanwender durchdringen sollten. Deshalb muss man sich keine Gedanken machen über die Komplexität der Fehlermeldung.
Die Basis-Klasse Exception enthält eine wörtliche Beschreibung des Fehlers, womit der Programmzustand beschrieben wird, der zum Fehler führte, und - optional - Execptions, die durch untergeordnete Programmaufrufe herbei geführten wurden und die ursprüngliche Ursache des Fehlers darstellen können.
Die Arten von Informationen die in einer Exception enthalten sein müssen, hängt von der Art des Fehlers ab. Es gibt drei Varianten von Exceptions:
Fehler, die während der Vorabprüfung auftreten können, sollten eine Beschreibung der fehlgeschlagenen Prüfung enthalten. Wenn möglich sollte der fehlerhafte Wert mit angegeben werden.
Beispiel
Fehler, die durch untergeordneten Bibliotheksaufrufe auftreten und durch Fehlercodes oder -Objekte signalisiert werden, sollten in Exceptions umgewandelt werden, wenn diese nicht behoben werden können. Die Fehlerbeschreibung sollte die originale Fehlerinformationen enthalten bzw. entsprechend konvertiert werden. Am Beispiel der obigen connect()-Methode:
Beispiel
Nicht-korrigierbare Exceptions von untergeordneten Bibliotheken sollten weitergeleitet oder erneut geworfen werden. Wenn sie weitergeleitet werden soll, dann behandeln Sie die Exception nicht weiter. Wenn Sie die Exception erneut werfen, dann müssen Sie die originale Exception in der neuen Exception verpacken.
Beispiel: Eine Exception neu verpacken
Beispiel: Eine Exception weiterleiten
Die Entscheidung, ob eine Exception neu verpackt oder weitergeleitet werden soll, ist eine Frage der Software-Architektur. Exceptions sollten weitergeleitet werden, ausser in zwei Fällen:
Exceptions sollten niemals als Bestandteil des normalen Programmflußes benutzt werden. Wenn alle Logik zur Behandlung von Exceptions entfernt würde (try-catch-Statements), dann sollte der verbliebende Code den "wahren Pfad" repräsentieren -- dem Programmfluß, wenn keinerlei Fehler auftreten würden.
Diese Forderung entspricht der Erwartung, dass Exceptions nur bei fehlerhaften Zuständen geworfen werden sollten und niemals bei regulären Zuständen.
Ein Beispiel für die falsche Benutzung der Exception-Weiterleitung ist die Rückgabe eines Wertes eines rekursiven Aufrufs:
Beispiel
Im Beispiel wird die ResultException benutzt, um "schnell" wieder aus der Rekursion heraus zu kommen. Das ist im Fehlerfall tatsächlich praktisch, in diesem Fall, aber nur ein Beispiel für einen faulen Programmierer.
Alle Exceptions, die von Packages geworfen werden, müssen von Exception abstammen.
Zusätzlich sollte jedes Package seine eigene Exception-Klasse definieren; der Name der Klasse entspricht dem Muster: <Package_Name>_Exception. Jede Exception sollte von dieser Klasse abgeleitet werden.
Da PHP, im Gegensatz zu Java, es nicht erfordert, mögliche Exceptions in der Funktionssignatur aufzunehmen, ist deren sorgfältige Dokumentation im Methodenkopf wichtig.
Beispiel: Exceptions werden dokumentiert mit dem @throws-Schlüsselwort
In vielen Fällen wandelt die mittlere Schicht einer Anwendung Exceptions von untergeordneten Methoden in aussagekräftiger, anwendungsspezifische Exceptions. Das sollte ebenfalls angesprochen werden:
Beispiel
In anderen Fällen kann ihre Methode als Filter fungieren, der nur bestimmte Exceptions weiterleitet. Dann sollten Sie dokumentieren, welche Exceptions nicht von Ihrer Methode abgefangen werden.
Beispiel
Exceptions spielen eine kritische Rolle in der API ihrer Bibliothek. Entwickler sollten angemessene Beschreibungen wo und warum Exceptions auftreten liefern. Auch die sorgfältige Planung der Fehlermeldungen ist ein wichtiger Faktor für die Erhaltung der Rückwärts-Kompatibilität.
Da Exceptions ein integraler Bestandteil der API ihres Packages sind, darf bei Änderungen daran die Rückwärts-Kompatibilität (BC) nicht grundlos gebrochen werden.
Dinge, die zum Bruch führen:
Dinge, die nicht zum Bruch führen:
(Quelle: Richtlinien, 28.02.2008)