Fehlerbehandlung mit Ausnahmen

AusnahmenPHP bietet seit Version 5 auch eine Ausnahmebehandlung ähnlich anderen objektorientierten Programmiersprachen wie C++, Java oder Python. Die Fehlerbehandlung mittels Ausnahmen bietet gegenüber der Fehlerbehandlung mittels Rückgabewerten einige Vorteile, und ermöglicht eine übersichtlichere und flexiblere Behandlung von Fehlern.

Vorteile von Ausnahmen

  • Mit Ausnahmen lässt sich die Fehlerbehandlung vom normalen Code trennen, wodurch der Quelltext übersichtlicher wird.
  • Ausnahmen bieten einem mehr Kontrolle und Flexibilität bei der Fehlerbehandlung. Fehler können an der Stelle im Quelltext behandelt werden, an der man am besten darauf reagieren kann.
  • Sie erleichtern die Fehlersuche und vermeiden das Übersehen von Fehlern. Bei der Fehlerbehandlung mittels Rückgabewerten kann es passieren das man vergisst einen Rückgabewert zu prüfen. Das Skript arbeitet dann einfach weiter, und es kann zu unerwarteten Ergebnissen kommen. Die Ursache solcher Fehler zu finden ist sehr schwer, besonders wenn sie nur selten oder unter bestimmten Umständen auftreten. Mit Ausnahmen kann das nicht passieren, eine nicht behandelte Ausnahme bricht die Skript-Ausführung ab.

Ein kleines Beispiel

Der folgende Quelltext gibt eine zufällige Zeile aus der Datei zitate.txt aus, zur Fehlerbehandlung wird der Rückgabewert überprüft.

function zitat() {
	$handle = fopen('zitate.txt', 'r');
	if ($handle === FALSE) {
		echo "Öffnen der Datei ist fehlgeschlagen";
		return NULL;
	}
 
	$zitate = array();
	while (!feof($handle)) {
		$zitat = fgets($handle);
		if (!empty($zitat))
			$zitate[] = $zitat;
	}
 
	fclose($handle);
 
	if (!count($zitate)) {
		echo "Keine Zitate gefunden.";
		return NULL;
	}
 
	return $zitate[array_rand($zitate)];
}
 
$lustiges_zitat = zitat();
if ($lustiges_zitat !== NULL)
	echo $lustiges_zitat;

Die Funktion zitat() gibt eine Fehlermeldung aus und liefert den Wert NULL zurück wenn ein Fehler auftritt. Man könnte die Funktion auch so umschreiben, dass sie anstatt NULL einen Fehlercode zurückgibt.

Anstatt jedoch die Funktion einen Rückgabewert, der einen Fehler anzeigt, zurückgeben zu lassen kann man sie auch einfach eine Ausnahme auslösen lassen die alle für die Fehlerbehandlung benötigten Informationen enthält. Denn möglicherweise kann man innerhalb der Funktion bzw. bei deren Aufruf gar nicht angemessen auf den Fehler reagieren. Ausnahmen werden so lange an aufrufende Funktionen zurückgereicht bis sie gefangen werden. So kann den Fehler an der Stelle behandeln an der man am besten darauf reagieren kann.

Beispielsweise könnte man den Fehler das keine Zitate gefunden wurden an einer Stelle behandeln an der bekannt ist ob der Benutzer eingeloggt ist und neue Zitate in die Textdatei einfügen darf. Ist das der Fall könnte man zusätzlich zur Fehlermeldung noch einen Link zu einer Seite anzeigen, auf der man neue Zitate in die Datei schreiben kann.

Der selbe Quelltext mit Ausnahmen zur Fehlerbehandlung könnte so aussehen:

function zitat() {
	$handle = fopen('zitate.txt', 'r');
	if ($handle === FALSE)
		throw new Exception("Konnte die Datei nicht öffnen.", 1);
 
	$zitate = array();
	while (!feof($handle)) {
		$zitat = fgets($handle);
		if (!empty($zitat))
			$zitate[] = $zitat;
	}
 
	fclose($handle);
 
	if (!count($zitate))
		throw new Exception("Keine Zitate gefunden.", 2);
 
	return $zitate[array_rand($zitate)];
}
 
try {
	echo zitat();
}
catch (Exception $e) {
	echo "<h1>Fehler</h1>" . $e->getMessage();
	if ($e->getCode() == 2 && $berechtigungen['zitat_hinzufuegen'])
		echo '<a href="neu.php">Neues Zitat hinzufügen</a>';
}

In dieser Variante wird ein Link zum Hinzufügen neuer Zitate angezeigt wenn keine Zitate gefunden wurden. Wenn man an dieser Stelle nicht überprüfen kann ob der Benutzer überhaupt die Rechte dazu hat, könnte man die Ausnahme auch an einer anderen Stelle abfangen, an der man das überprüfen kann.

Funktionsweise von Ausnahmen

Eine Ausnahme wird mit dem Schlüsselwort throw “geworfen” und mit catch gefangen bzw. behandelt. Es können nur Objekte der Ausnahmeklasse Exception oder davon abgeleiteten Klassen geworfen werden.

throw new Exception("Es ist was schiefgegangen.");

Jede geworfene Ausnahme muss auch irgendwo gefangen werden, ansonsten bricht die Ausführung des Skripts mit einer Fehlermeldung wie dieser ab:

Fatal error: Uncaught exception 'Exception' with 
message 'Es ist was schiefgegangen.' in /www/exception.php:37 
Stack trace: #0 {main} thrown in /www/exception.php on line 37

Innerhalb eines try-Blocks werden Ausnahmen gefangen, auf einen try-Block muss immer mindestens ein catch-Block folgen welcher die Ausnahme behandelt. Es ist möglich mehrere catch-Blöcke an einen try-Block anzuhängen um verschiedene Ausnahme-Klassen verschieden zu behandeln. Ein kleines Beispiel:

class SchlimmerFehler extends Exception {}
 
try {
    /* Hier der Quelltext in dem eine Ausnahme auftreten kann */
    if ($var == 1) {
        throw new Exception("Es ist was schiefgegangen.");
        echo "Dieser Text wird niemals ausgegeben!";
    }
    else
        throw new SchlimmerFehler("Das hat nicht geklappt.");
}
catch (SchlimmerFehler $e) {
    echo "Ein ganz schlimmer Fehler ist aufgetreten! ", $e->getMessage();
    die();
}
catch (Exception $e) {
    echo "Ein Fehler ist aufgetreten: ", $e->getMessage();
}

Wird keine Ausnahme geworfen, überspringt PHP einfach die catch-Blöcke und macht dahinter weiter. Wenn im try-Block eine Ausnahme geworfen wird, springt PHP direkt, ohne den restlichen Code im try-Block auszuführen, zum ersten catch-Block.

Wenn die Klasse im catch-Block mit der Klasse der geworfenen Ausnahme übereinstimmt, oder von ihr abgeleitet ist, wird der catch-Block ausgeführt, ansonsten springt PHP zum nächsten catch-Block weiter. Es wird immer nur der erste catch-Block ausgeführt der passt.

Es ist auch möglich innerhalb eines catch-Blocks eine Ausnahme zu werfen, und try-Blöcke können auch verschachtelt werden.

class SchlimmerFehler extends Exception {}
class GanzSchlimmerFehler extends Exception {}
 
try {
	try {
		try {
			throw new SchlimmerFehler("Das hat nicht geklappt.");
		}
		catch (GanzSchlimmerFehler $e) {
			// Wird nicht ausgeführt. Dieser catch-Block wird
			// nur ausgeführt wenn die geworfene Ausnahme ein 
			// Objekt der Klasse GanzSchlimmerFehler ist (oder
			// von ihr abgeleitet).
		}
	}
	catch (Exception $e) {
		// SchlimmerFehler ist von Exception abgeleitet, also
		// wird dieser Block ausgeführt.
 
		// Man kann Ausnahmen auch einfach weiterwerfen.
		throw $e;
	}
}
catch (Exception $e) {
	// Hier wird die Ausnahme wieder gefangen und die Fehler-
	// meldung wird ausgegeben.
    echo "Ein Fehler ist aufgetreten: ", $e->getMessage();
}
catch (SchlimmerFehler $e) {
	// Dieser catch-Block wird niemals ausgeführt. SchlimmerFehler
	// ist von der Klasse Exception abgeleitet, und der catch-Block 
	// vor diesem hier fängt bereits alle von Exception  
	// abgeleiteten Ausnahme-Klassen ab.
}

Wie man an dem Beispiel sehen kann, ist es wichtig das die catch-Blöcke in der richtigen Reihenfolge sind. Der letzte catch-Block des Beispiels würde nie ausgeführt werden, weil der Block davor bereits alle Ausnahmen (bzw. alle der Klasse Exception und davon abgeleiteten, was allen entspricht) abfängt.

Die Klasse Exception

Die Ausnahmeklasse Exception bietet noch mehr als nur die Möglichkeit eine Fehlermeldung und einen Fehlercode auszugeben. Sie ist wie folgt aufgebaut.

class Exception {
	protected $message = 'Unknown Exception'	// Fehlermeldung
	protected $code = 0;				// Fehlercode
 
	// Dateiname der Datei in der die Ausnahme aufgetreten ist.
	protected $file;
 
	// Zeile in der die Ausnahme aufgetreten ist.
	protected $line;
 
	function __construct($message = NULL, $code = 0);
 
	// Formatierte Zeichenkette für die Ausgabe
	function __toString();
 
	// Diese Funktionen können nicht überschrieben werden.
	final function getMessage();
	final function getCode();
	final function getFile();
	final function getLine();
	final function getTrace();
	final function getTraceAsString();
}

Eine Besonderheit stellt die Funktion getTrace bzw. getTraceAsString dar. Mit ihr lässt sich der Ablauf bis zum Fehler zurückverfolgen, ein Beispiel:

#0 /www/exception.php(4): test('Test', 123)
#1 /www/exception.php(6): test2(123)
#2 {main}

Der dazugehörige Code ist folgender:

3
4
5
6
7
8
9
function test($a, $b) { throw new Exception("Es ist was schiefgelaufen.", 100); }
function test2($var) { test("Test", $var); }
 
try { test2(123); }
catch (Exception $e) {
	echo  $e->getTraceAsString();
}

Eigene Exception-Klassen

Es ist auch möglich eigene Klassen von Exception abzuleiten, und diese je nach Bedarf noch um eigene Informationen oder Funktionen zu erweitern. Man könnte beispielsweise eine Ausnahmeklasse für nicht gefundene Dateien schreiben.

class DateiNichtGefunden extends Exception {
	protected $dateiname;
 
	public function __construct($name) {
		$this->dateiname = $name;
 
		// Sicherstellen das alles richtig zugewiesen wird
		parent::__construct("Datei nicht gefunden", 123);
	}
 
	public function datei() {
		return $this->dateiname;
	}
 
	public function __toString() {
		return "Die Datei &raquo;{$this->dateiname}&laquo; wurde nicht gefunden.";
	}
}

Man kann so viele Methoden und Eigenschaften hinzufügen wie man möchte, wichtig ist nur das man im Konstruktor am Ende parent::__construct($fehlermeldung, $fehlercode); aufruft, damit sichergestellt ist, dass alle Daten korrekt zugewiesen werden.

Ungefangene Ausnahmen behandeln

Wenn eine Ausnahme nicht gefangen wird, wird die Ausführung des Skripts abgebrochen und die Standardfunktion zum Behandeln von Ausnahmen aufgerufen. Es ist aber auch möglich diese Standardfunktion durch eine eigene zu ersetzen.

function ungefangene_ausnahme($e) {
	echo "Ungefangene Ausnahme: ", $e->getMessage();
}
 
set_exception_handler('ungefangene_ausnahme');

Dazu wird die Funktion set_exception_handler('name_der_funktion') verwendet. Wichtig ist, dass die Funktion einen Parameter annimmt (die ungefangene Ausnahme), und das sie deklariert wird bevor eine Ausnahme geworfen wird die nirgends gefangen wird. Mit restore_exception_handler lässt sich die vorherige Funktion zur Ausnahmebehandlung wiederherstellen.

Die Standardausgabe von PHP die angezeigt wird wenn eine Ausnahme nicht gefangen wird ist ziemlich unübersichtlich, wie man diese mittels set_exception_handler etwas übersichtlicher gestalten kann beschreibt Stephan Linzner in seinem Artikel PHP Exceptions Formatieren.

Wofür sind Ausnahmen nicht gedacht?

Ausnahmen sollten nur verwendet werden wenn, wie der Name es schon sagt, eine Ausnahme bzw. ein Fehler auftritt. Sie sind nicht dazu gedacht den Programmablauf zu steuern! Im Normalfall sollte ein Skript komplett durchlaufen, ohne das eine Ausnahme geworfen wird.

Beispiel: Für solche Zwecke sollte man Ausnahmen nicht verwenden

try {
	if (array_search($benutzername, $benutzer) !== FALSE)
		throw BenutzerExistiert();
	else
		throw BenutzerExistiertNicht();
}
catch (BenutzerExistiert $e)
// ...

Torsten Zander gibt in seinem Artikel Exceptions Fangen und Werfen noch ein paar Tipps wie man Ausnahmen richtig einsetzt.

Informationen

1 Stern2 Sterne3 Sterne4 Sterne5 Sterne (1 Bewertungen, Durchschnitt: 5,00 von 5)
Loading ... Loading ...
Kategorie: PHP
Ansichten: 4.621

Kommentare

Bisher wurde ein Kommentar geschrieben.

Kommentar schreiben

XHTML: Folgende Elemente sind erlaubt: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

Angetrieben durch Wordpress Thema erstellt von Antu