Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Artfiles.de
Bietet Serviceorientierte...
https://www.Artfiles.de
Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
 
 
 

Ausnahmebehandlung in Php

Sie befinden sich: Home > Php > Ausnahmebehandlung in Php

In diesem Artikel möchte ich Ihnen, erklären, wie sie eine ordentliche Ausnahmebehandlung machen. In den meisten Fällen ist es sehr sinnvoll, anstatt das Skript mit die(); oder echo und exit; abzubrechen. Da man, durch die Ausnahmebehandlung der Fehler, diese zum Beispiel in einer Textdatei speichern kann. Wenn man dies beachtet, wird man später bei der Fehlersuche im Skript viel Zeit sparen.

Natürlich ist es nicht ganz einfach in großen Projekten das Konzept einzubauen, aber wer kennt das nicht? Zitat: „Mal wieder die Nadeln im Heuhaufen suchen.“ Wenn man dies umgehen möchte, sollte man sich die Mühe machen.

Die Ausnahmebehandlung wurde in der PHP Version 5 wie in anderen Programmiersprachen hinzugefügt. Daher ist in den meisten Fachbüchern recht wenig, genauer gesagt gar nicht auf die Ausnahmebehandlung eingegangen. Daher versuche ich Ihnen in diesem kleinen Beispiel, die Ausnahmebehandlung zu erklären.

Beispiel:

<?php
/*
* Als erstes müssen wir die Funktion
* von den Script festlegen, damit
* wir diese verwenden können.
*
* Die Funktion macht nichts anderes,
* als Prüfen, ob dieser eine Zahle
* übergeben wurde. Wenn eine Zahl
* übergeben wurde, gibt diese Funktion
* die Zahl zurück.
*
* Wenn der Funktion keine Zahl übergeben
* wurde, wird eine Fehlermeldung
* gespeichert, durch den Aufruf
* von Exception().
*/
function check_zahl($zahl = '0') {
if (is_numeric($zahl)) return $zahl;
else throw new Exception("Es wurde keine " .
"Zahl der Funktion check_zahl() übergeben.<br>");
}

try {
/*
* So nun wollen wir unser Script
* ausfüheren. Dazu fangen wir mit
* try an und lassen hier ein
* Fehler enstehen in der Variable
* $zahl.
*
* Dazu müssen wir einfach
* einen String übergeben und schon
* wird eine Exception gestartet und
* in der Variable $e die Fehlermeldung
* gespeichert in ein Objekt.
*
*/
$zahl = 'a';
echo 'Es ist in der Variable'.
' die Zahl '.check_zahl($zahl).' ' .
'gespeichert.<br>';
}
catch (Exception $e) {
/*
* Dieser Fehler kann man nun auch z.b.
* in einer Log Datei speichern um
* diese später auszuwerten.
*/
echo 'Script Fehler: '.$e->getMessage();
}
?>

Die Ausgabe würde dann so aussehen:

Script Fehler: Es wurde keine Zahl der Funktion check_zahl() übergeben.

Erklärung:

Mit dieser Funktion können wir eine Zahl überprüfen. Wird der Funktion eine Zahl übergeben, gibt sie diese zurück. Wenn keine Zahl übergeben wurde, wird eine Fehlermeldung gespeichert, indem ein Exception Objekt aufgerufen wird.

Um die Funktion auszuführen, müssen wir mit try beginnen und in der Variable $zahl einen String übergeben. Dadurch wird eine Exception gestartet und die Fehlermeldung in der Variable $e gespeichert. Der Fehler kann dann auch in einer Log-Datei gespeichert werden, um ihn später auszuwerten.

 

Zu der Ausnahmebehandlung finden Sie im PHP Handbuch noch mehr ausführliche Dokumentationen zu diesem Thema.

Soweit alles klar? Wenn ja, so können Sie hier das nächste Thema Anfang. Dabei geht es darum, Formulardaten an ein Script zusenden.

Eigene Exception-Klassen schreiben

Sobald Ihre Anwendung mehr ist als ein kleines Skript, reichen die eingebauten Exceptions nicht mehr aus. Sie wollen zwischen "Datenbank weg" und "Eingabe ungueltig" unterscheiden, und genau dafuer schreiben Sie eigene Exception-Klassen. Eine eigene Exception ist eine Klasse, die von Exception ableitet. Mehr braucht es im einfachsten Fall nicht. Der eigentliche Gewinn liegt darin, dass Sie im catch-Block gezielt nach genau dieser Klasse fragen können, ohne sich auf Fehlermeldungen oder Codes verlassen zu müssen.

<?php
declare(strict_types=1);

class ValidationException extends Exception
{
    private array $errors;

    public function __construct(string $message, array $errors)
    {
        parent::__construct($message);
        $this->errors = $errors;
    }

    public function getErrors(): array
    {
        return $this->errors;
    }
}

try {
    throw new ValidationException(
        'Formular ungueltig',
        ['email' => 'Ungueltige E-Mail-Adresse']
    );
} catch (ValidationException $e) {
    foreach ($e->getErrors() as $feld => $fehler) {
        echo $feld . ': ' . $fehler . "
";
    }
}

Was Sie hier sehen, ist der typische Aufbau. Sie erweitern Exception, ergaenzt eigene Properties und stellst Getter bereit. Im catch arbeiten Sie dann nicht mehr mit nackten Strings, sondern mit strukturierten Daten. Genau das macht eigene Exceptions so wertvoll. Sie tragen mehr Information als nur eine Nachricht, und sie machen Ihre Fehlerbehandlung typsicher.

Die Exception-Hierarchie verstehen

PHP organisiert seine Fehler in einer Klassenhierarchie. An der Spitze steht das Interface Throwable. Alles, was Sie mit throw werfen oder mit catch fangen können, implementiert dieses Interface. Sichekt darunter teilen sich zwei große Aeste auf: Error und Exception. Diese Trennung ist mehr als Kosmetik. Sie beschreibt zwei grundverschiedene Arten von Problemen, und Sie solltest sie auch unterschiedlich behandeln.

Exception steht für Fehler, mit denen Ihre Anwendung rechnen muss. Eine ungueltige Eingabe, eine fehlende Datei, ein Netzwerk-Timeout. Solche Faelle gehoeren in Ihre Geschaeftslogik und werden bewusst gefangen. Error dagegen markiert Probleme, die normalerweise auf Programmierfehler hindeuten. TypeError, DivisionByZeroError oder ParseError sind Vertreter dieser Familie. Sie solltest sie fast nie pauschal fangen, denn sie zeigen oft Bugs, die Sie beheben müssen, statt sie zu verstecken.

<?php
declare(strict_types=1);

function teile(int $a, int $b): int
{
    return intdiv($a, $b);
}

try {
    echo teile(10, 0);
} catch (DivisionByZeroError $e) {
    echo 'Mathe-Problem: ' . $e->getMessage();
} catch (Throwable $t) {
    echo 'Etwas ist schiefgelaufen: ' . $t->getMessage();
}

Die Faustregel lautet: In normalen catch-Bloecken arbeiten Sie mit Exception oder einer eigenen Subklasse. Throwable oder Error fangen Sie nur an wenigen, klar definierten Stellen, etwa in einem globalen Error-Handler oder am Eingang einer API. Wenn Sie Throwable in jedem Service fangen wollen, machen Sie sich die Fehlersuche unnoetig schwer.

Multi-Catch seit PHP 8.0

Frueher müssenest Sie für jede Exception-Klasse einen eigenen catch-Block schreiben, selbst wenn die Reaktion identisch war. Seit PHP 8.0 gibt es Multi-Catch, und das spart sich nicht nur Tippen, sondern macht auch klar, dass mehrere Faelle zur gleichen Reaktion führen. Die Syntax ist eine senkrechte Linie zwischen den Klassen, genau wie bei Union Types für Parameter.

<?php
declare(strict_types=1);

class NetworkException extends Exception {}
class TimeoutException extends Exception {}
class DnsException extends Exception {}

try {
    sendeRequest('https://api.example.com/');
} catch (NetworkException | TimeoutException | DnsException $e) {
    error_log('Temporaerer Netzwerkfehler: ' . $e->getMessage());
} catch (Exception $e) {
    error_log('Unerwarteter Fehler: ' . $e->getMessage());
    throw $e;
}

function sendeRequest(string $url): void {}

Wichtig zu wissen ist die Variable $e im Multi-Catch. Sie ist vom kleinsten gemeinsamen Typ, also in der Regel Throwable oder Exception. Wenn Sie auf Methoden zugreifen wollen, die nur eine der Klassen kennt, müsstest Sie vorher mit instanceof prüfen.

Der finally-Block für Cleanup

Eine Exception kann jederzeit Ihren Code-Fluss unterbrechen. Wenn Sie vorher eine Datei geoeffnet, ein Lock gesetzen oder eine Datenbank-Transaktion gestartet hast, wirst Sie diese Ressource garantiert nicht mehr ordentlich schliessen, sobald die Exception nach oben durchschlaegt. Genau hier setzen finally an. Der Block wird ausgeführt, egal ob im try alles glatt lief oder eine Exception flog. Auch ein return aus dem try heraus haelt finally nicht auf.

<?php
declare(strict_types=1);

function lockedAktion(string $lockFile): void
{
    $fp = fopen($lockFile, 'c');
    if ($fp === false) {
        throw new RuntimeException('Lock konnte nicht geoeffnet werden');
    }

    if (!flock($fp, LOCK_EX)) {
        fclose($fp);
        throw new RuntimeException('Lock nicht erhalten');
    }

    try {
        kritische_aktion();
    } finally {
        flock($fp, LOCK_UN);
        fclose($fp);
    }
}

function kritische_aktion(): void {}

Das Muster ist immer gleich: Ressource holen, im try arbeiten, im finally aufraeumen. Der Vorteil gegenueber einem Cleanup nach dem catch ist, dass Sie die Exception nicht zwingend abfangen müssen. finally laeuft trotzdem.

try-catch ohne Variable seit PHP 8.0

Manchmal interessiert sich die Exception als Objekt gar nicht. Sie wollen nur wissen, dass etwas schiefgelaufen ist, und einen Standardwert zurueckgeben. Bis PHP 7.4 müssenest Sie trotzdem $e hinschreiben, auch wenn Sie sie nie benutzen hast. Seit PHP 8.0 dürfen Sie den Variablennamen einfach weglassen.

<?php
declare(strict_types=1);

function nutzerAlter(string $geburtstag): ?int
{
    try {
        $datum = new DateTimeImmutable($geburtstag);
        $jetzt = new DateTimeImmutable();
        return (int) $jetzt->diff($datum)->y;
    } catch (Exception) {
        return null;
    }
}

Diese Form macht Ihre Absicht offensichtlich. Wer den Code lesen, sieht sofort: Hier ist der konkrete Fehler unwichtig, nur der Erfolgsfall zaehlt. Trotzdem solltest Sie sich die Frage stellen, ob das wirklich okay ist. In den meisten produktiven Codepfaden hilft error_log() mit der Original-Message, das schweigende Verschlucken nachher zu rekonstruieren.

Re-Throwing mit Kontext

In größeren Anwendungen reicht es nicht, eine Exception einfach durchzureichen. Sie wollen zusaetzlichen Kontext mitgeben: aus welchem Service kam der Fehler, welcher API-Endpunkt war beteiligt, was war der Auftrag? Dafuer fangen Sie die urspruengliche Exception, packst sie in eine eigene und werfen diese neu. Der dritte Parameter des Exception-Konstruktors heißt $previous und nimmt die Original-Exception entgegen. So bleibt die komplette Ursachen-Kette erhalten.

<?php
declare(strict_types=1);

class ApiException extends Exception {}

function ladeNutzer(int $id): array
{
    try {
        $pdo = new PDO('mysql:host=db;dbname=app', 'user', 'pass');
        $stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($row === false) {
            throw new RuntimeException('Nutzer nicht gefunden');
        }
        return $row;
    } catch (PDOException $e) {
        throw new ApiException(
            'Datenbankzugriff fuer Nutzer ' . $id . ' fehlgeschlagen',
            0,
            $e
        );
    }
}

try {
    ladeNutzer(42);
} catch (ApiException $e) {
    echo $e->getMessage() . "
";
    if ($e->getPrevious() !== null) {
        echo 'Ursache: ' . $e->getPrevious()->getMessage();
    }
}

Der Aufrufer bekommt jetzt eine sprechende Fehlermeldung mit klarem Kontext, kann aber per getPrevious() bei Bedarf in die Tiefe gehen. Mehr zur Datenbankseite finden Sie im PDO-Tutorial zu PDO und PDOException.

Logging statt die-and-display

Ein Anti-Muster, das man immer noch oft sieht, ist das berühmte die($e->getMessage()). Auf Entwicklerseite ist das vielleicht noch akzeptabel, in Produktion ist es ein doppeltes Eigentor. Erstens leakst Sie moeglicherweise interne Details an Angreifer, etwa Datenbankstrukturen oder Pfade. Zweitens löschen Sie sich den Fehler aus dem Blick, denn ohne Log können Sie im Nachhinein keine Statistik führen und keine Patterns erkennen.

<?php
declare(strict_types=1);

function speichereAuftrag(array $daten): int
{
    try {
        return 42;
    } catch (Throwable $t) {
        error_log(sprintf(
            '[ORDER] %s in %s:%d - %s',
            $t::class,
            $t->getFile(),
            $t->getLine(),
            $t->getMessage()
        ));
        throw new RuntimeException('Auftrag konnte nicht gespeichert werden');
    }
}

Das Muster ist klar getrennt: intern loggst Sie alles, was Sie für die Fehlersuche brauchen, mit error_log(). Nach aussen kommt eine generische Meldung. Wenn Sie in Entwicklung mehr Details sehen wollen, steuerst Sie das über error_reporting() und display_errors, niemals Sierch festes Echo der Original-Message. Auf produktiven Servern gehoert display_errors grundsaetzlich auf Off.

Mermaid: try-catch-finally Flow

Visuell lässt sich der Kontrollfluss eines vollstaendigen try-catch-finally am schnellsten begreifen, wenn Sie sich den Erfolgs- und den Fehler-Pfad parallel anschauen. Beide Pfade laufen am Ende durch denselben finally-Block, bevor sie nach aussen weitergehen.

flowchart TD
    A[try-Block startet] --> B{Exception?}
    B -- nein --> C[Code laeuft durch]
    B -- ja --> D[catch passenden Block]
    C --> E[finally]
    D --> E
    E --> F[Weiter im Aufrufer]

Das Diagramm zeigt drei wichtige Punkte. Erstens trifft der Code immer auf finally, egal ob im try ein Fehler auftrat oder nicht. Zweitens werden mehrere catch-Bloecke wie ein Match abgearbeitet, der erste passende Typ gewinnt. Drittens kann ein catch selbst wieder eine Exception werfen, dann landet die im naechsthoeheren try, das finally davor laeuft trotzdem noch.

Praxisbeispiel: Datei-Operation mit Cleanup

Zum Abschluss bringen wir alle Konzepte in einem realistischen Beispiel zusammen. Sie wollen eine Konfigurationsdatei lesen, validieren und im Erfolgsfall ein Ergebnis zurueckgeben. Geht etwas schief, wird sauber geloggt und eine eigene Exception nach aussen gegeben. Egal was passiert, das File-Handle wird zuverlaessig geschlossen.

<?php
declare(strict_types=1);

class ConfigException extends RuntimeException {}

function ladeConfig(string $pfad): array
{
    $fp = @fopen($pfad, 'rb');
    if ($fp === false) {
        throw new ConfigException('Config nicht lesbar: ' . $pfad);
    }

    try {
        if (!flock($fp, LOCK_SH)) {
            throw new ConfigException('Shared-Lock fehlgeschlagen');
        }

        $inhalt = stream_get_contents($fp);
        if ($inhalt === false) {
            throw new ConfigException('Lesen fehlgeschlagen');
        }

        try {
            $daten = json_decode($inhalt, true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException $e) {
            throw new ConfigException('Ungueltiges JSON in ' . $pfad, 0, $e);
        }

        if (!isset($daten['version'])) {
            throw new ConfigException('Pflichtfeld version fehlt');
        }

        return $daten;

    } catch (ConfigException $e) {
        error_log('[CONFIG] ' . $e->getMessage());
        throw $e;
    } finally {
        flock($fp, LOCK_UN);
        fclose($fp);
    }
}

try {
    $config = ladeConfig(__DIR__ . '/app.json');
    echo 'Version: ' . $config['version'];
} catch (ConfigException $e) {
    http_response_code(500);
    echo 'Konfiguration konnte nicht geladen werden.';
    if ($e->getPrevious() !== null) {
        error_log('Ursache: ' . $e->getPrevious()->getMessage());
    }
}

Die Funktion zeigt fünf der wichtigsten Bausteine in einem einzigen, ueberschaubaren Beispiel. Eine eigene Exception-Klasse macht den Fehler-Typ greifbar. JsonException wird gefangen und mit Kontext neu geworfen, die Original-Exception bleibt als $previous erhalten. error_log() schreibt die Details ins Log, ohne sie an den Nutzer zu leaken. finally garantiert, dass flock und fclose immer laufen. Und der aufrufende Code bekommt eine saubere, typsichere Exception, mit der er nach Belieben umgehen kann.

Wenn Sie dieses Muster verinnerlichst, wirst Sie Exceptions nicht mehr als laestige Stoerung im Code sehen, sondern als eigenen, ausdrucksstarken Kanal für Fehlerinformation. Eine gut geworfene Exception ist im Endeffekt eine genaue, typsichere Beschreibung dessen, was im aktuellen Kontext nicht funktioniert hat. Genau das macht den Unterschied zwischen einem Skript, das irgendwie laeuft, und einer Anwendung, die Sie auch in zwei Jahren noch debuggen und erweitern können.




weiter zum nächsten Kapitel: Variablen an ein Formular übergeben