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.

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.

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.

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.

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

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

Eine Exception neu verpacken

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

Eine Exception weiterleiten

<?php
function preTaxPrice($retailPrice$taxRate
{
    return 
$this->divide($retailPrice$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:

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

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:

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

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

E_STRICT-kompatibler Code (Previous) Best practices (Next)
Last updated: Sun, 19 Dec 2010 — Download Documentation
Do you think that something on this page is wrong? Please file a bug report or add a note.
View this page in:

User Notes:

There are no user contributed notes for this page.