Versionen von CodingStyle.StudipCsFehlerbehandlung

Unwichtige Korrekturen ausblenden - Änderungen im Wiki Quelltext

 
 
22.08.2008 22:06 Uhr von chueser - navigation & toc
Zeilen 3-5 bearbeitet:

[ Zurück: Namenskonventionen | Index: Übersicht | Vor: Visual Style Guide? ]

geändert in:
 
 
08.03.2008 15:10 Uhr von chueser -
Zeilen 1-476 hinzugefügt:

Fehlerbehandlung

[ Zurück: Namenskonventionen | Index: Übersicht | Vor: Visual Style Guide? ]

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.

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:

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

(:source lang=php linenum:)
<?php
class DB_Exception extends 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

(:source lang=php linenum:)
<?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 zeigt die Verwendung von try und catch.

Beispiel: Verwendung der um Ausnahmebehandlung erweiterten Klasse

(:source lang=php linenum:)
<?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.

Beispiel: Nachbildung von finally in PHP

(:source lang=php linenum:)
<?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: Eine unbehandelte Ausnahme führt zum Abbruch der Programmausführung

(:source lang=php linenum:)
<?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: Anpassen der Verarbeitung von unbehandelten Ausnahmen

(:source lang=php linenum:)
<?php
function my_exception_handler($e) {
  // Exception $e behandeln.
}

set_exception_handler('my_exception_handler');
?>

(Quelle: Fehlerbehandlung, 28.02.2008)

Richtlinen zur Fehlerbehandlung

Was ist ein Fehler?

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

(:source lang=php linenum:)
<?php
/**
 * Verbinde zur angegebenen Datenbank
 *
 * @throws Example_Datasource_Exception wenn der Verbindungsaufbau fehlschlägt
 */
function connectDB($dsn) {
    $this->db =& DB::connect($dsn);
    if (DB::isError($this->db)) 
    {
        throw new Example_Datasource_Exception(
                "Unable to connect to $dsn:" . $this->db->getMessage()
        );
    }
}
?>

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

(:source lang=php linenum:)
<?php
/*
 * Verbinde mit einer der möglichen Datenbanken
 *
 * @throws Example_Datasource_Exception wenn keine der gewählten
 *         Datenbank angesprochen werden konnte.
 *
 * @throws Example_Config_Exception wenn keine Datenbanken
 *         konfiguriert wurden
 */

function connect(Config $conf) 
{
    $dsns =& $conf->searchPath(array('config', 'db'));
    if ($dsns === FALSE) throw new Example_Config_Exception(
        'Unable to find config/db section in configuration.'
    );

    $dsns =& $dsns->toArray();

    foreach($dsns as $dsn) {
        try {
            $this->connectDB($dsn);
            return;
        } catch (Example_Datasource_Exception e) {
            // Warn-/Logging-Code um den Verbindungsfehler
            // aufzuzeichnen
        }
    }
    throw new Example_Datasource_Exception(
        'Unable to connect to any of the configured databases'
    );
}
?>

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

(:source lang=php linenum:)
<?php
/**
 * loadConfig wertet die angegebene Konfiguration aus. Wenn die
 * Konfiguration unkorrekt ist, dann wird auf die Standard-
 * zurückgegriffen
 *
 */
function loadConfig(Config $conf) 
{
    try {
        $this->config = $conf->parse();
    } catch (Config_Parse_Exception e) {
        // Warn-/Logging-Code
        // Unvollständige Fehlerbehebung
        $this->config = $this->defaultConfig;
    }
}
?>

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.

Fehler-Benachrichtigung

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:

  1. Fehler, die während der Vorabprüfung auftreten können.
  2. Fehler, die durch untergeordneten Bibliotheksaufrufe auftreten und durch Fehlercodes oder -Objekte signalisiert werden
  3. Nicht-korrigierbare Exceptions von untergeordneten Bibliotheken.

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

(:source lang=php linenum:)
<?php
function divide($x, $y)
{
    if ($y == 0) {
        throw new Example_Aritmetic_Exception('Division by zero');
    }
}
?>

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

(:source lang=php linenum:)
<?php
/**
 * Verbinde zur angegebenen Datenbank
 *
 * @throws Example_Datasource_Exception wenn der Verbindungsaufbau fehlschlägt
 */
function connectDB($dsn) {
    $this->db =& DB::connect($dsn);
    if (DB::isError($this->db)) {
        throw new Example_Datasource_Exception(
                "Unable to connect to $dsn:" . $this->db->getMessage()
        );
    }
}
?>

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

(:source lang=php linenum:)
<?php
function preTaxPrice($retailPrice, $taxRate) 
{
    try {
        return $this->divide($retailPrice, 1 + $taxRate);
    } catch (Example_Aritmetic_Exception e) {
        throw new Example_Tax_Exception('Invalid tax rate.', e);
    }
}
?>

Beispiel: Eine Exception weiterleiten

(:source lang=php linenum:)
<?php
function preTaxPrice($retailPrice, $taxRate) 
{
    return $this->divide($retailPrice, 1 + $taxRate);
}
?>

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:

  1. Die originale Excpetion ist von einem anderen Package. Wenn diese weitergeleitet wird, dann würden Details der Implementierung nach aussen dringen.
  2. Die Methode kann nützliche Debug-Informationen ergänzen.

Exceptions und der normale Programmfluß

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

(:source lang=php linenum:)
<?php
/**
 * Rekursive Suche in einem Baum nach einem String
 * @throws ResultException
 */
public function search(TreeNode $node, $data)  
{
    if ($node->data === $data) {
         throw new ResultException( $node );
    } else {
         search( $node->leftChild, $data );
         search( $node->rightChild, $data );
    }
}
?>

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.

Die Klassen-Hierarchie von Exceptions

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.

Exceptions dokumentieren

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

(:source lang=php linenum:)
<?php
/**
 * Diese Methode sucht nach Aliens.
 *
 * @return array Array von Alien-Objekten.
 * @throws AntennaBrokenException wenn die Impedanz-Leser anzeigt, dass die
 *         Antenne nicht funktioniert
 *
 * @throws AntennaInUseException wenn ein anderer Prozess die Antenne
 *         bereits benutzt
 */
public function findAliens($color = 'green');
?>

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

(:source lang=php linenum:)
<?php
/**
 * Lade Session-Objekte in den Shared-Memory
 *
 * @throws LoadingException Jede untergeordnete IOException wird als
 *         LoadingException neu verpackt.
 */
public function loadSessionObjects();
?>

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

(:source lang=php linenum:)
<?php
/**
 * Führt eine Reihe von Datenbankanfragen aus (atomar, nicht innerhalb einer Transaktion).
 * @throws SQLException Low-level SQL-Fehler werden direkt weitergeleitet.
 */
public function batchExecute();
?>

Exceptions als Teil der API

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:

  • Jede Änderung an Methoden, die Exceptions werfen.
  • Wenn eine Exception-Klasse verwendet wird, die höher in der Vererbungskette liegt, als die ursprüngliche. Zum Beispiel, wenn Sie in einer neueren Version eine Exception werfen würden, in der alten aber z.B. IOException verwendet haben.

Dinge, die nicht zum Bruch führen:

  • Wenn eine abgeleitete Klasse der originalen Exception verwendet wird. Zum Beispiel, wenn Sie in der aktuellen Version eine IOException werfen, und in älteren Versionen Exception. Natürlich nur unter der Voraussetzung, dass IOException von Exception) abgeleitet ist.

(Quelle: Richtlinien, 28.02.2008)

 
 
28.02.2008 18:36 Uhr von chueser -
Zeile 3 bearbeitet:

[ Zurück: Namenskonventionen | Index: Übersicht | Vor: Tests? ]

geändert in:

[ Zurück: Namenskonventionen | Index: Übersicht | Vor: Visual Style Guide? ]

 
 
28.02.2008 16:08 Uhr von chueser -
Zeile 476 hinzugefügt:

(Quelle: Richtlinien, 28.02.2008)

 
 
28.02.2008 16:06 Uhr von chueser -
Zeilen 211-219 bearbeitet:

Richtlinen zur Fehlerbehandlung

Dieser Abschnitt beschreibt wie Fehler behandelt werden sollen. Fehler werden über Exceptions abgewickelt, sie wurden mit PHP 5.0 und der Zend Engine 2 eingeführt.

geändert in:

Richtlinen zur Fehlerbehandlung

Zeilen 238-239 bearbeitet:

Beispiel 4-2. Fehlerbehandlung mit Behebung

geändert in:

Beispiel: Fehlerbehandlung mit Behebung

Zeilen 277-278 bearbeitet:

Beispiel 4-3. Unvollständige Behebung

geändert in:

Beispiel: Unvollständige Behebung

Zeilen 317-318 bearbeitet:

Beispiel 4-4.

geändert in:

Beispiel

Zeilen 330-331 bearbeitet:

Beispiel 4-5.

geändert in:

Beispiel

Zeilen 350-351 bearbeitet:

Beispiel 4-6. Eine Exception neu verpacken

geändert in:

Beispiel: Eine Exception neu verpacken

Zeilen 363-364 bearbeitet:

Beispiel 4-7. Eine Exception weiterleiten

geändert in:

Beispiel: Eine Exception weiterleiten

Zeilen 386-387 bearbeitet:

Beispiel 4-8.

geändert in:

Beispiel

Zeilen 409-410 bearbeitet:

Alle Exceptions, die von Packages geworfen werden, müssen von Exception abstammen. Exception bietet zusätzliche Fähigkeiten, um andere Exceptions zu verpacken. Sie finden diese nicht in der obersten PHP Exception-Klasse, sind aber notwendig, um die oben gestellten Anforderungen zu erfüllen.

geändert in:

Alle Exceptions, die von Packages geworfen werden, müssen von Exception abstammen.

Zeilen 418-419 bearbeitet:

Beispiel 4-9. Exceptions werden dokumentiert mit dem @throws-Schlüsselwort

geändert in:

Beispiel: Exceptions werden dokumentiert mit dem @throws-Schlüsselwort

Zeilen 436-437 bearbeitet:

Beispiel 4-10.

geändert in:

Beispiel

Zeilen 450-451 bearbeitet:

Beispiel 4-11.

geändert in:

Beispiel

Zeile 463 bearbeitet:

Exceptions spielen eine kritische Rolle in der API ihrer Bibliothek. Entwickler, die Ihre Bibliothek verwenden, sind abhängig von der angemessenen Beschreibung wo und warum Exceptions auftreten in Ihrem Package. Auch die sorgfältige Planung der Fehlermeldungen ist ein wichtiger Faktor für die Erhaltung der Rückwärts-Kompatibilität.

geändert in:

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.

 
 
28.02.2008 15:38 Uhr von chueser -
Zeilen 3-9 gelöscht:

E_STRICT-kompatibler Code

Jeder Code muss E_STRICT-kompatibel sein. Das heisst, er darf keine Fehler oder Warnungen erzeugen, wenn das Fehler-Reporting von PHP auf E_STRICT eingestellt ist.

Die Entwicklung existierender Packages, die dieser Konvention noch nicht entsprechen, müssen dies auch noch nicht.

 
 
28.02.2008 12:52 Uhr von chueser -
Zeile 215 bearbeitet:

(Quelle: http://professionelle-softwareentwicklung-mit-php5.de/oop.foundations.exceptions.htmlFehlerbehandlung, 28.02.2008)

geändert in:

(Quelle: Fehlerbehandlung, 28.02.2008)

 
 
28.02.2008 12:51 Uhr von chueser - + link
Zeilen 215-217 bearbeitet:
geändert in:

(Quelle: http://professionelle-softwareentwicklung-mit-php5.de/oop.foundations.exceptions.htmlFehlerbehandlung, 28.02.2008)

 
 
28.02.2008 12:40 Uhr von chueser - + exceptions
Zeilen 10-222 hinzugefügt:

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.

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:

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

(:source lang=php linenum:)
<?php
class DB_Exception extends 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

(:source lang=php linenum:)
<?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 zeigt die Verwendung von try und catch.

Beispiel: Verwendung der um Ausnahmebehandlung erweiterten Klasse

(:source lang=php linenum:)
<?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.

Beispiel: Nachbildung von finally in PHP

(:source lang=php linenum:)
<?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: Eine unbehandelte Ausnahme führt zum Abbruch der Programmausführung

(:source lang=php linenum:)
<?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: Anpassen der Verarbeitung von unbehandelten Ausnahmen

(:source lang=php linenum:)
<?php
function my_exception_handler($e) {
  // Exception $e behandeln.
}

set_exception_handler('my_exception_handler');
?>
 
 
24.02.2008 16:46 Uhr von chueser - + code format
Zeile 22 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 43 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 82 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 122 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 135 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 155 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 168 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 191 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 223 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 241 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

Zeile 255 bearbeitet:

[@<?php

geändert in:

(:source lang=php linenum:)[@<?php

 
 
22.02.2008 21:43 Uhr von chueser - überarbeitet
Zeile 5 hinzugefügt:
Zeilen 14-15 bearbeitet:

Dieser Abschnitt beschreibt wie Fehler in PEAR-Packages behandelt werden sollen, die für PHP 5 und 6 entwickelt werden. Fehler werden über Exceptions abgewickelt, sie wurden mit PHP 5.0 und der Zend Engine 2 eingeführt.

geändert in:

Dieser Abschnitt beschreibt wie Fehler behandelt werden sollen. Fehler werden über Exceptions abgewickelt, sie wurden mit PHP 5.0 und der Zend Engine 2 eingeführt.

Zeilen 39-40 bearbeitet:

In diesem Beispiel soll die Methode die Verbindung mit der Datenbank mit dem gegegebenem DSN herstellen. Die Methode ruft ihrerseits nur PEAR::DB, wenn dieses Package einen Fehler wirft, kann nur eine Exception erzeugt und geworfen werden, ohne weiter Einfluß nehmen zu können.

geändert in:

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.

Zeilen 103-106 bearbeitet:

Fehler-Benachrichtigung PHP 5 PEAR-Packages

Fehlerhafte Zustände in PEAR-Packages für PHP 5 müssen über Exceptions gemeldet werden. Nicht mehr verwendet werden sollten Fehlercodes oder ein PEAR_Error-Objekt. Diese Regel gilt natürlich nicht, wenn das Package kompatibel mit PHP 4 bleiben muss. In diesem Fall gelten die Konvention der PEAR Coding Standards unter PHP 4 weiter.

geändert in:

Fehler-Benachrichtigung

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.

Zeilen 110-111 bearbeitet:

Die Basis-Klasse PEAR_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.

geändert in:

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.

Zeile 180 hinzugefügt:
Zeilen 208-214 bearbeitet:

Die Klassen-Hierarchie von Exceptions

Alle Exceptions, die von Packages geworfen werden, müssen von PEAR_Exception abstammen. PEAR_Exception bietet zusätzliche Fähigkeiten, um andere Exceptions zu verpacken. Sie finden diese nicht in der obersten PHP Exception-Klasse, sind aber notwendig, um die oben gestellten Anforderungen zu erfüllen.

Zusätzlich sollte jedes PEAR-Package seine eigene Exception-Klasse definieren; der Name der Klasse entspricht dem Muster: <Package_Name>_Exception. Jede Exception sollte von dieser Klasse abgeleitet werden. Exceptions dokumentieren

geändert in:

Die Klassen-Hierarchie von Exceptions

Alle Exceptions, die von Packages geworfen werden, müssen von Exception abstammen. Exception bietet zusätzliche Fähigkeiten, um andere Exceptions zu verpacken. Sie finden diese nicht in der obersten PHP Exception-Klasse, sind aber notwendig, um die oben gestellten Anforderungen zu erfüllen.

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.

Exceptions dokumentieren

Zeilen 273-274 bearbeitet:
  • Wenn eine Exception-Klasse verwendet wird, die höher in der Vererbungskette liegt, als die ursprüngliche. Zum Beispiel, wenn Sie in einer neueren Version eine PEAR_Exception werfen würden, in der alten aber z.B. PEAR_IOException verwendet haben.
geändert in:
  • Wenn eine Exception-Klasse verwendet wird, die höher in der Vererbungskette liegt, als die ursprüngliche. Zum Beispiel, wenn Sie in einer neueren Version eine Exception werfen würden, in der alten aber z.B. IOException verwendet haben.
Zeile 277 bearbeitet:
  • Wenn eine abgeleitete Klasse der originalen Exception verwendet wird. Zum Beispiel, wenn Sie in der aktuellen Version eine PEAR_IOException werfen, und in älteren Versionen PEAR_Exception. Natürlich nur unter der Voraussetzung, dass PEAR_IOException von PEAR_Exception) abgeleitet ist.
geändert in:
  • Wenn eine abgeleitete Klasse der originalen Exception verwendet wird. Zum Beispiel, wenn Sie in der aktuellen Version eine IOException werfen, und in älteren Versionen Exception. Natürlich nur unter der Voraussetzung, dass IOException von Exception) abgeleitet ist.
 
 
21.02.2008 17:58 Uhr von chueser - + E_strict
Zeilen 4-9 hinzugefügt:

E_STRICT-kompatibler Code

Jeder Code muss E_STRICT-kompatibel sein. Das heisst, er darf keine Fehler oder Warnungen erzeugen, wenn das Fehler-Reporting von PHP auf E_STRICT eingestellt ist.

Die Entwicklung existierender Packages, die dieser Konvention noch nicht entsprechen, müssen dies auch noch nicht.

 
 
20.02.2008 23:38 Uhr von chueser - + fehlerbehandlung
Zeilen 1-2 bearbeitet:
geändert in:

Fehlerbehandlung

Zeilen 4-265 hinzugefügt:

Richtlinen zur Fehlerbehandlung

Dieser Abschnitt beschreibt wie Fehler in PEAR-Packages behandelt werden sollen, die für PHP 5 und 6 entwickelt werden. Fehler werden über Exceptions abgewickelt, sie wurden mit PHP 5.0 und der Zend Engine 2 eingeführt.

Was ist ein Fehler?

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

<?php
/**
 * Verbinde zur angegebenen Datenbank
 *
 * @throws Example_Datasource_Exception wenn der Verbindungsaufbau fehlschlägt
 */
function connectDB($dsn) {
    $this->db =& DB::connect($dsn);
    if (DB::isError($this->db)) 
    {
        throw new Example_Datasource_Exception(
                "Unable to connect to $dsn:" . $this->db->getMessage()
        );
    }
}
?>

In diesem Beispiel soll die Methode die Verbindung mit der Datenbank mit dem gegegebenem DSN herstellen. Die Methode ruft ihrerseits nur PEAR::DB, wenn dieses Package einen Fehler wirft, kann nur eine Exception erzeugt und geworfen werden, ohne weiter Einfluß nehmen zu können.

Beispiel 4-2. Fehlerbehandlung mit Behebung

<?php
/*
 * Verbinde mit einer der möglichen Datenbanken
 *
 * @throws Example_Datasource_Exception wenn keine der gewählten
 *         Datenbank angesprochen werden konnte.
 *
 * @throws Example_Config_Exception wenn keine Datenbanken
 *         konfiguriert wurden
 */

function connect(Config $conf) 
{
    $dsns =& $conf->searchPath(array('config', 'db'));
    if ($dsns === FALSE) throw new Example_Config_Exception(
        'Unable to find config/db section in configuration.'
    );

    $dsns =& $dsns->toArray();

    foreach($dsns as $dsn) {
        try {
            $this->connectDB($dsn);
            return;
        } catch (Example_Datasource_Exception e) {
            // Warn-/Logging-Code um den Verbindungsfehler
            // aufzuzeichnen
        }
    }
    throw new Example_Datasource_Exception(
        'Unable to connect to any of the configured databases'
    );
}
?>

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 4-3. Unvollständige Behebung

<?php
/**
 * loadConfig wertet die angegebene Konfiguration aus. Wenn die
 * Konfiguration unkorrekt ist, dann wird auf die Standard-
 * zurückgegriffen
 *
 */
function loadConfig(Config $conf) 
{
    try {
        $this->config = $conf->parse();
    } catch (Config_Parse_Exception e) {
        // Warn-/Logging-Code
        // Unvollständige Fehlerbehebung
        $this->config = $this->defaultConfig;
    }
}
?>

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.

Fehler-Benachrichtigung PHP 5 PEAR-Packages

Fehlerhafte Zustände in PEAR-Packages für PHP 5 müssen über Exceptions gemeldet werden. Nicht mehr verwendet werden sollten Fehlercodes oder ein PEAR_Error-Objekt. Diese Regel gilt natürlich nicht, wenn das Package kompatibel mit PHP 4 bleiben muss. In diesem Fall gelten die Konvention der PEAR Coding Standards unter PHP 4 weiter.

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 PEAR_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:

  1. Fehler, die während der Vorabprüfung auftreten können.
  2. Fehler, die durch untergeordneten Bibliotheksaufrufe auftreten und durch Fehlercodes oder -Objekte signalisiert werden
  3. Nicht-korrigierbare Exceptions von untergeordneten Bibliotheken.

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 4-4.

<?php
function divide($x, $y)
{
    if ($y == 0) {
        throw new Example_Aritmetic_Exception('Division by zero');
    }
}
?>

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 4-5.

<?php
/**
 * Verbinde zur angegebenen Datenbank
 *
 * @throws Example_Datasource_Exception wenn der Verbindungsaufbau fehlschlägt
 */
function connectDB($dsn) {
    $this->db =& DB::connect($dsn);
    if (DB::isError($this->db)) {
        throw new Example_Datasource_Exception(
                "Unable to connect to $dsn:" . $this->db->getMessage()
        );
    }
}
?>

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 4-6. Eine Exception neu verpacken

<?php
function preTaxPrice($retailPrice, $taxRate) 
{
    try {
        return $this->divide($retailPrice, 1 + $taxRate);
    } catch (Example_Aritmetic_Exception e) {
        throw new Example_Tax_Exception('Invalid tax rate.', e);
    }
}
?>

Beispiel 4-7. Eine Exception weiterleiten

<?php
function preTaxPrice($retailPrice, $taxRate) 
{
    return $this->divide($retailPrice, 1 + $taxRate);
}
?>

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:

  1. Die originale Excpetion ist von einem anderen Package. Wenn diese weitergeleitet wird, dann würden Details der Implementierung nach aussen dringen.
  2. Die Methode kann nützliche Debug-Informationen ergänzen.

Exceptions und der normale Programmfluß

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 4-8.

<?php
/**
 * Rekursive Suche in einem Baum nach einem String
 * @throws ResultException
 */
public function search(TreeNode $node, $data)  
{
    if ($node->data === $data) {
         throw new ResultException( $node );
    } else {
         search( $node->leftChild, $data );
         search( $node->rightChild, $data );
    }
}
?>

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. Die Klassen-Hierarchie von Exceptions

Alle Exceptions, die von Packages geworfen werden, müssen von PEAR_Exception abstammen. PEAR_Exception bietet zusätzliche Fähigkeiten, um andere Exceptions zu verpacken. Sie finden diese nicht in der obersten PHP Exception-Klasse, sind aber notwendig, um die oben gestellten Anforderungen zu erfüllen.

Zusätzlich sollte jedes PEAR-Package seine eigene Exception-Klasse definieren; der Name der Klasse entspricht dem Muster: <Package_Name>_Exception. Jede Exception sollte von dieser Klasse abgeleitet werden. Exceptions dokumentieren

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 4-9. Exceptions werden dokumentiert mit dem @throws-Schlüsselwort

<?php
/**
 * Diese Methode sucht nach Aliens.
 *
 * @return array Array von Alien-Objekten.
 * @throws AntennaBrokenException wenn die Impedanz-Leser anzeigt, dass die
 *         Antenne nicht funktioniert
 *
 * @throws AntennaInUseException wenn ein anderer Prozess die Antenne
 *         bereits benutzt
 */
public function findAliens($color = 'green');
?>

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 4-10.

<?php
/**
 * Lade Session-Objekte in den Shared-Memory
 *
 * @throws LoadingException Jede untergeordnete IOException wird als
 *         LoadingException neu verpackt.
 */
public function loadSessionObjects();
?>

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 4-11.

<?php
/**
 * Führt eine Reihe von Datenbankanfragen aus (atomar, nicht innerhalb einer Transaktion).
 * @throws SQLException Low-level SQL-Fehler werden direkt weitergeleitet.
 */
public function batchExecute();
?>

Exceptions als Teil der API

Exceptions spielen eine kritische Rolle in der API ihrer Bibliothek. Entwickler, die Ihre Bibliothek verwenden, sind abhängig von der angemessenen Beschreibung wo und warum Exceptions auftreten in Ihrem Package. 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:

  • Jede Änderung an Methoden, die Exceptions werfen.
  • Wenn eine Exception-Klasse verwendet wird, die höher in der Vererbungskette liegt, als die ursprüngliche. Zum Beispiel, wenn Sie in einer neueren Version eine PEAR_Exception werfen würden, in der alten aber z.B. PEAR_IOException verwendet haben.

Dinge, die nicht zum Bruch führen:

  • Wenn eine abgeleitete Klasse der originalen Exception verwendet wird. Zum Beispiel, wenn Sie in der aktuellen Version eine PEAR_IOException werfen, und in älteren Versionen PEAR_Exception. Natürlich nur unter der Voraussetzung, dass PEAR_IOException von PEAR_Exception) abgeleitet ist.
 
 
20.02.2008 23:31 Uhr von chueser - + navi
Zeilen 1-2 hinzugefügt:

[ Zurück: Namenskonventionen | Index: Übersicht | Vor: Tests? ]

 

 

Quelle: Basis-Wiki-Hilfe | Letzte Änderung: 22.08.2008 22:06 Uhr, chueser | Local view: Basis-Hilfe