Richtiger Umgang mit Exceptions?

Richtiger Umgang mit Exceptions?

Da ich ja endlich mich mit OOP in PHP auseinandersetze, möchte ich natürlich gleich Exceptions korrekt abfangen.
Die sind natürlich nur für die Signalisierung eines Fehlers zuständig. Sonst würden sie ja nicht „Ausnahmen“ heißen.

Mit dieser Exception kann man dann ein weiteres vorgehen einfacher und korrekt behandeln.
So kann ich sagen wenn Exception XYZ eintrifft möchte ich, das dass Script abbricht und nicht mehr fortgeführt werden. Also ein Fatal Error.
Aber es geht natürlich auch das die Exception einfach nur als Warning oder Notice behandelt wird und danach einfach weiter läuft.

Wie immer ist die Theorie einfach, die Praxis aber nicht.

Die Grundbau einer Exceptions
PHP hat hierfür eine eigene Basisklasse, bei Java und C++ gibt es auch eigene Klassen, also Mehrzahl (Beispiele in WIkipedia).
Jede nicht abgefangene Exception wirft einen Fatal Error auf, dies kann man im Groben meist unterbinden wenn man richtig arbeitet.
Während andere Sprachen gleich mehrere Klassen haben, hat PHP hier die Basisklasse die später in weitere Klassen ableitet.

Hier eine Default Exception:

throw new Exception('Exception message');

Natürlich kann man diese Klassen auch selbst erweitern:

class CustomException extends Exception {
}
 
throw new CustomException ('Exception message');

Für eine Fehlerbehandlung nutzt man dann die „try & catch“ Funktion.
Man behandelt den Fehler und führt danach das Script weiter aus.

Es genügt, wenn man Logs schreibt um später das ganze zu Analysieren.

try {
  // Der Code der später "eventuell" Probleme ausführt
} catch(CustomException $e) {
  // Fehlerbehandlung
}

Die sogenannte Ausnahmen

Exceptions sind nur für Fälle da die niemals eintreten sollten.
Beispielsweise:

  • Fehlgeschlagene Authentifizierung bei API-Zugriffen
  • Fehlgeschlagene Datenbankverbindung
  • Falscher oder unerlaubter Datentyp übergeben (Invalid argument)

Da reguläre Fehler hier eine überflut verursachen würden, muss man hierfür Ausnahmen hinzufügen.
Im verständlicher Form, wir erlauben jede korrekte Abfrage.
Es gilt die Regel, eine Exception ist eine unzulässige Aktion.

Unter folgenden Regeln ist die Ausnahme, eine wirkliche Ausnahme:

  • Die Eingabe ist nicht den Erwartungen gerecht(Leerer String bei Eingabe, Injections Versuch usw.)
  • Korrekter Antwort des Servers(Valides Ergebnis, korrekter Datentyp etc.)

Trifft keiner der Regeln zu, sollte keine Exception ausgerufen werden.
Daher also defentiv ein Ausnahme Fall.
Dennoch wie geschrieben, klingt einfach, klingt logisch, klingt simpel.
Ist es aber nicht!

Da ich an vielen Portalen mit Usern und Usersuche arbeite, zeige ich euch Anhand dessen ein Beispiel.

class UserSearch {
  public function findUserById($id) {
    $user = ... // Die Abfrage um den User per ID zu suchen. 
 
    if($user == 0) {
      throw new UnderflowException("User with id $id noch found!");
    }
  }
}

Was ist doch, wenn der Datentyp nicht stimmt der zurückkommt? Was ist wenn statt einem String ein Int kommt oder gar Blob?
Dann wäre es wiederrum eine eine Exception.

Exception Klassen mit Bedacht wählen

Die SPL, die sogenannte Standard PHP Library bringt bereits sehr, sehr viele Exceptions mit. Die vorgefertigen Fehlerfälle muss man einfach nur nutzen.
Man muss das Rad nicht neu erfinden.

Da wir natürlich uns die Arbeit nicht machen wollen, wie schon oben, das Rad nicht neu zu erfinden, nutzen wir die SPL.

Wir haben nun in meinem Beispiel, ein Gästebuch mit den dazugehören Settern (Getter sind unrelevant in dem Beispiel).
In allen Settern werden InvalidArgumentException geworfen, sofern die Eingaben nicht den Erwartungen entsprechen.

class GuestBook {
  protected $title;
  protected $entry;
 
  public function setTitle($title) {
    if(empty($title)) {
      throw new InvalidArgumentException('GuestBook title must not be empty');
    }
    $this->title = $title;
  }
 
  public function setEntry($entry) {
    if(!is_string($entry)) {
      throw new InvalidArgumentException('Entry count must be an integer number');
    }
    $this->entry = $entry;
  }
}

Typische Fehler
Wenn wir nun in einem Try-Catch Block mehrere Setter versuchen aufzurufen, entsteht ein Problem.

$guestbook = new GuestBook();
try {
  $guestbook->setTitle($_GET['title']);
  $guestbook->setEntry($_GET['entry']);
} catch(InvalidArgumentException $e) {
  echo $e->getMessage();
}

Gut, das entspricht eh jeglichem Coding Verstand, hier gibt es mehrere Möglichkeiten.
Die eine wäre durchschleifen mit Fehler Codes, man nutzt 2 Unterschiedliche Exceptionklassen oder man macht das was ich bevorzuge.
Man erstellt mehrere Try Catch Blöcke, so das es modularer ist und einfacher zu individualisieren.
Hier kommt es auf die Situation an, denn wenn es nur „Lapallien sind, die mit ner einfachen Meldung erledigt sind, so kann man die Exeptions mit dem Fehlercode auch einfach durchschleifen.

Beim schreiben des Artikels wies mich gerade auch jemand darauf hin, das man eventuell einen Mehraufwand hat und bei inkonsequenter Arbeit (Niemand kann verleugnen das es das nicht gibt) man eventuell einen InvalidArgumentException zurückbekommt, was unfein wäre. Hier könnten Fehler vertuscht werden die an anderer Stelle verursacht werden.
Kleines Beispiel, die Meldung kommt das die Tabelle in der Datenbank fehlt, dabei ist die Datenbank gar nicht erreichbar.

Er meinte das man die elegantere Form nutzen sollte verschiedene Exception Klassen zu entwerfen.Hier jedoch muss man aufpassen das man sich nicht in massiver Arbeit verfängt.

Ich packe die Exception Klassen immer separat in einen Exception Ordner, ob das immer so gut ist lass ich mal offen. Für Anregungen anderer Coder bin ich offen.