Fehlerbehandlung mit Ausnahmen

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.

Abbildung 1.4. Die Standardklasse Exception

Die Standardklasse Exception


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.29: Die Klasse DB_Exception

<?php
class DB_Exception extends Exception {}
?>


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');
?>