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.
Abbildung 1.3. 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 1.4
zeigt das UML-Klassendiagramm dieser Klasse.
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. Eine interessante Diskussion zu diesem Thema mit Anders Hejlsberg, dem Lead-Designer der Programmiersprache C#, gibt es in [TheTroublewithCheckedExceptions].
Beispiel 1.29
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 1.30 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 1.30: Fehlerbehandlung mit Ausnahmen
<?php
class DB_MySQL {
// ...
public function connect($host, $database, $user, $pass) {
$this->connection = @mysql_connect(
$host,
$user,
$pass,
TRUE
);
if (!$this->connection) {
throw new DB_Exception(
'Konnte keine Verbindung zur Datenbank aufbauen.',
@mysql_errno()
);
}
if (!@mysql_select_db($database, $this->connection)) {
throw new DB_Exception(
'Konnte die gewünschte Datenbank nicht auswählen.',
@mysql_errno($this->connection)
);
}
}
// ...
public function query($query) {
if (is_resource($this->connection)) {
if (is_resource($this->result)) {
@mysql_free_result($this->result);
}
$this->result = @mysql_query(
$query,
$this->connection
);
if (!$this->result) {
throw new DB_Exception(
@mysql_error($this->connection),
@mysql_errno($this->connection)
);
}
}
}
// ...
}
?>
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 1.31
zeigt die Verwendung von try und
catch.
Beispiel 1.31: Verwendung der um Ausnahmebehandlung erweiterten Klasse
<?php
require_once 'DB_MySQL.php';
try {
$mysql = new DB_MySQL(
'localhost',
'test',
'root',
'wrongPass'
);
$mysql->query('SELECT spalte FROM tabelle');
while ($row = $mysql->fetchRow()) {
// ...
}
}
catch (DB_Exception $e) {
printf(
'Ein Datenbankfehler ist aufgetreten: %s',
$e->getMessage()
);
}
catch (Exception $e) {
printf(
'Ein allgemeiner Fehler ist aufgetreten: %s',
$e->getMessage()
);
}
?>
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 (siehe Beispiel 1.32).
Beispiel 1.32: Nachbildung von finally in PHP
<?php
class FinallyImplementierung {
public function methode() {
$ausnahme = NULL;
try {
// Code, der eine Ausnahme auslösen kann.
}
catch (Exception $e) {
// Ausnahme-Objekt speichern.
$ausnahme = $e;
// Code, der eine Ausnahme behandelt.
}
// finally {
// Code, der immer ausgeführt wird.
// }
// Gespeichertes Ausnahme-Objekt weiterreichen.
if ($ausnahme !== NULL) {
throw $ausnahme;
}
// Code, der nur ausgeführt wird,
// wenn keine Ausnahme ausgelöst wurde.
}
}
?>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 1.33: Eine unbehandelte Ausnahme führt zum Abbruch der Programmausführung
<?php
throw new Exception('Test');
?>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 1.34: Anpassen der Verarbeitung von unbehandelten Ausnahmen
<?php
function my_exception_handler($e) {
// Exception $e behandeln.
}
set_exception_handler('my_exception_handler');
?>