PHP/Die Fehlerklasse "Exception" und "try/catch"

Aus Mikiwiki
Zur Navigation springen Zur Suche springen

Ausnahmen: Benutzerdefinierte Laufzeitfehler abfangen

Das Prinzip der Anweisungen "try" und "catch" ist einfach. Ein Programmzweig, in dem Fehler auftreten könnten, wird mit einem "try"-Block umschlossen.

try
{
  ## Hier können Fehler auftreten
}

Tritt zur Laufzeit ein Fehler in diesem Bereich auf, so erzeugt PHP keine Fehlerausschrift, sondern sucht zuerst die nächste Anweisung "catch". Wird ein solches Schlüsselwort gefunden, so werden die darin befindlichen Anweisungen ausgeführt; vorzugsweise wird dort die Fehlerbehandlung hineingeschrieben.

Es können mehrere "catch"-Zweige hintereinander geschrieben werden, um auf verschiedene Fehlerarten zu reagieren. Als Auslöser dient das Schlüsselwort "throw", das eine sogenannte Fehlerklasse instantiiert und an den passenden "catch"-Zweig schickt.

Untere PHP ist der Entwickler selbst verantwortlich, alle erdenklichen Fehler zu beachten und passende Abfragen zu erstellen. Echte Laufzeitfehler (z. B. die Division durch 0) lassen sich nicht abfangen. Es gibt auch kein Schlüsselwort "finally" wie in anderen Programmiersprachen. Die Fehlerbehandlung mit "try" und "catch" ist kein Mittel, um den Programmfluss zu steuern. Es sollten nur dann Fehlerroutinen aufgebaut werden, wenn ungewöhnliche Abläufe zu behandeln sind. Soll auf Dateien geprüft werden, so wird zuerst eine Funktion wie "file_exists" verwendet, um das Vorhandensein einer Datei festzustellen. Erst wenn dies angezeigt wird und der Ladevorgang dann misslingt, sollte die Fehlerverwaltung greifen. Alle "Fehler", die im normalen Betrieb auftreten können, sind deshalb durch normale Programmbefehle zu behandeln.

Einfache Fehlerbehandlungen

Die Fehlerbehandlung mit "try" und "catch" erlaubt einfacheren und lesbareren PHP-Quellcode. Statt die vielfältigen Fehlerausgaben innerhalb eines Skripts weit zu verstreuen, kann man sich auf wenige Punkte beschränken, was leichter zu pflegen ist. So spricht trotz der nicht ganz gelungenen Umsetzung einiges für die Anwendung. Folgendes Beispiel zeigt die grundsätzliche Arbeitsweise.

<?php
$a = 45;
$b = $a - 45;
$c = 0;
try
{
  if ($b == 0)
  {
    throw new Exception("Division durch Null");
  }
  $c = $a / $b;
}
catch(Exception $exception)
{
  echo "Ein Fehler trat auf: {$exception->getMessage()}";
}
?>
Ein Fehler trat auf: Division durch Null

Ohne entsprechende Fehlerbehandlung erscheint ein Laufzeitfehler.

<?php
$a = 45;
$b = $a - 45;
$c = $a / $b;
?>
Warning: Division by zero in /WWW/test/test.php on line 4

Die Klasse "Exception"

PHP verfügt über die eingebaute Klasse "Exception" (dt. Ausnahme), welche die Fehlerbehandlung kontrolliert und folgendermassen definiert ist.

Mitglied Typ Beschreibung
protected $message E Fehlernachricht. Kann in abgeleiteten Klassen überschrieben werden.
protected $code E Benutzerdefinierter Fehlercode. Kann in abgeleiteten Klassen überschrieben werden.
protected $file E Dateiname der Datei, in welcher der Fehler auftrat. Kann in abgeleiteten Klassen überschrieben werden.
protected $line E Zeile, auf der mit "throw" der Fehler ausgelöst wurde. Kann in abgeleiteten Klassen überschrieben werden.
__construct(string $message=NULL, int code=0) K Erzeugt das Fehlerobjekt mit einer bestimmten Meldung und einem Code.
public getMessage() M Ermittelt die Fehlermeldung.
public getCode() M Ermittelt den Fehlercode.
public getFile() M Ermittelt die Fehlerdatei.
public getTrace() M Ermittelt den Ablaufstapel und gibt die Informationen als Array zurück.
public getTraceAsString() M Ermittelt den Ablaufstapel und erzeugt eine Zeichenkette. Diese Methode kann nicht überschrieben werden.
public toString() M Zeichenkettendarstellung.

Werden nun eigene Ausnahmen definiert, so wird sinnvollerweise von dieser Klasse "Exception" abgeleitet. Das folgende Beispiel zeigt, wie das grundsätzlich aussieht.

<?php

## Direkte Ableitung der Klasse "NumberFormatException" von der Klasse
## "Exception". Sie verfügt deshalb über die passenden Methoden zum
## Aufnahmen und Ausgeben einer Fehlermeldung.
class NumberFormatException extends Exception
{
  function __construct($exception)
  {
    ## Aufruf des Konstruktors der Stammklasse, damit diese Methoden
    ## auch benutzt werden können.
    parent::__construct($exception);
  }
}
$i = "Hello!";
try
{
  ## Fehlerprüfung
  if(!is_int($i))
  {
    ## Im Bedarfsfall Auslösung eines Fehlers
    throw new NumberFormatException("\$i ist keine Zahl.");
  }
  else
  {
    echo "OK";
  }
}

## Reaktion auf den Fehler
catch (NumberFormatException $exception)
{
  echo $exception->getMessage();
}
?>
$i ist keine Zahl.

Welche konkrete Reaktion ein Skript verlangt, hängt von der Schwere des Fehlers ab. Entweder erfolgt eine entsprechende Fehlerausgabe mit nachfolgendem Programmabbruch oder beispielsweise ein "stiller" Neustart. Zum unmittelbaren Beenden des Programms bietet sich die Anweisung "die" an.

die($exception->getMessage());

Praktischer Einsatz der Fehlerbehandlung

Fehlerklassen sind normalerweise Teil der Konstruktion von Bibliotheken. Der Entwickler einer solchen Funktionssammlung definiert nicht nur die Verwendung, sondern auch die Fehlerbehandlung. Der Nutzer der Klasse ist dann dafür zuständig, auf die Fehler in entsprechender Weise zu reagieren. Erst bei diesem Szenario wird deutlich, wozu das "try/catch"-Konzept tatsächlich in der Lage ist. Es degradiert sich selbst nur deshalb, weil die interne Laufzeitbehandlung nicht in das Konzept eingeschlossen ist, was den praktischen Nutzen auf die Bibliotheksprogrammierung beschränkt.

Zur Verdeutlichung wird nochmals das letzte Beispiel verwendet. Funktion und Fehlerklasse sind nun beides Klassen und stehen als externe Bibliothek zur Verfügung.

<?php
## Name     error_trynumexlib.php

class NumberFormatException extends Exception
{
  const BASEERROR = "Error in NumberFormatException: ";    

  function __construct($exception)
  {
    parent::__construct(self::BASEERROR.$exception);
  }
}

class NumberCheck
{
  function __construct($i)
  {
    if(!is_int($i))
    {
      throw new NumberFormatException("<b>$i</b> ist keine Zahl.");
    }
    else
    {
      echo "OK";
    }
  }
}   
?>

Folgender Code käme dann zum Testen in Frage.

try
{
  $number = new NumberCheck("Zahl?");
}
catch (NumberFormatException $ex)
{
  echo $ex->getMessage();
}

Das typische Szenario sieht nun wie folgt aus: Diese Bibliothek wird von einem Entwickler erstellt und der Allgemeinheit verfügbar gemacht. Ein anderer Entwickler möchte die Klasse nutzen. Er weiss, welche Klassen enthalten sind und welche Fehler ausgelöst werden, wenn bestimmte Bedingungen nicht erfüllt sind. Er nutzt die Klasse folgendermassen.

<?php
include_once("./error_trynumexlib.php");
try
{
  $number = new NumberCheck("Hallo");
}
catch (NumberFormatException $ex)
{
  echo $ex->getMessage();
}
?>
Error in NumberFormatException: Hallo ist keine Zahl.

Hier kann der Anwender der Bibliothek nun entscheiden, ob und wie er Fehler abfängt und wie er im "catch"-Zweig mit ihnen verfährt. Um die eigentliche Fehlerprüfung dagegen braucht er sich nicht zu kümmern. Natürlich kann ein und derselbe Prozess auch mehrere Fehler erzeugen. So könnte sich an die Zahlenprüfung noch eine solche auf negative Zahlen anschliessen. Durch die Aneinanderreihung von "catch"-Zweigen kann auf jeden Fehler einzeln reagiert werden. Als abschliessender Zweig wird die Grundklasse "Exception" angegeben, womit dann alle Fehler gezielt abgefangen werden können.