MB: PDOException die() unformatiert!

Hallo Community,

Ich habe eine Frage zu PDOException. Mir ist aufgefallen das jenachdem wie ich es einstell, die Methode $this->getError(); in catch aufgerufen wird oder aber ein Fatal Error.

try {
  $this->DBH = new PDO( $dsn, $user, $pw );
  $this->DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
} catch ( PDOException $err ) {
  $this->getError();
}

mit die( $string ) kann ich den Fatal Error "überdecken?". Trotzdem bleib n unformatierter Bildschirm mit einer Meldung die im Fehlerfall durch die() angezeigt wird.

try {
  ...
} catch ( PDOException $err ) {
  die( $string );
}

Gibt es eine möglichkeit diese Fehler Meldung in einem try-catch-Block abzufangen mit formatiertem Text? z.B.: eben über eine Methode $this->getError()?

Grüße MB

  1. hi,

    Gibt es eine möglichkeit diese Fehler Meldung in einem try-catch-Block abzufangen mit formatiertem Text? z.B.: eben über eine Methode $this->getError()?

    Setz doch Deine ganze Webanwendung in einen Try-Block , reiche Exceptions durch (Stichwort: Exception Chaining) und sende beim catch() den Content-Type: text/plain;

    Als Nebnwirkung hast Du eine erstklassige Entwicklungsumgebung wo du im Zweifelsfalle einfach ein die+dump machenund gleich im Browser sehen kannst.

    1. nabend pl,

      Setz doch Deine ganze Webanwendung in einen Try-Block , reiche Exceptions durch (Stichwort: Exception Chaining) und sende beim catch() den Content-Type: text/plain;

      wie sähe die dann aus? Ich lerne am besten über anwendungsbeispiele daman nicht viel erklären muss nur der Kommentar im Scourcecode. Sry, hatte ich vergessen zu sagen.

      Als Nebnwirkung hast Du eine erstklassige Entwicklungsumgebung wo du im Zweifelsfalle einfach ein die+dump machenund gleich im Browser sehen kannst.

      sehr schön. Du arbeitest mir perl? ich glaube die programmiesprachen ähneln sich sehr aber ich bin auf Anfänger bis Fortgeschrittenen Niveau. Seit Gnäde mit mir wenn ich nicht sofort was raffe.

      vlg MB

      PS: Anfänger-Fortgeschrittener in PHP meine ich

  2. Moin!

    try {
      $this->DBH = new PDO( $dsn, $user, $pw );
      $this->DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
    } catch ( PDOException $err ) {
      $this->getError();
    }
    
    try {
      ...
    } catch ( PDOException $err ) {
      die( $string );
    }
    

    Wenn Code eine Exception wirft, will er damit der aufrufenden Stelle mitteilen: "Sorry, die Funktionalität, die du gerade aktiviert hast, funktioniert nicht - ich habe ohne Ergebnis abgebrochen, und du solltest das auch tun."

    Wenn die Stelle, die die Exception wirft, in keinerlei try/catch-Block steht, wird sie durch alle Aufrufe im Stack "nach oben bubbeln", dabei auch die oberste Ebene des aufgerufenen PHP-Skripts erreichen und beim PHP-Interpreter landen, der diese nicht gefangene Exception mit einem "fatal error" quittiert. Das ist eigentlich ein wünschenswerter Zustand, denn offenbar wurde für diesen Fehlerfall keinerlei Alternativstrategie programmiert.

    Diese Alternativstrategien sind pauschal in zwei Gruppen zu unterteilen:

    1. Alternativen, die sich mit den Details auskennen und ein Notprogramm aus dem Hut zaubern können.
    2. Alternativen, die nur den Mangel verwalten und deshalb nur eine Fehlermeldung liefern können.

    Fangen wir mit Nr. 2 zuerst an: Um grundsätzlich zu verhindern, dass ungefangene PHP-Exceptions einen "fatal error" erzeugen, gibt es als letzte Bastion die Möglichkeit, eine Funktion zu registrieren, die sich um alle nicht gefangenen Exceptions kümmert. Siehe set_exception_handler().

    Die Funktion, deren Aufruf du registrieren musst, muss nur einen einzigen Parameter, nämlich eine Klasse vom Typ Exception (bzw. ab PHP 7 besser das Interface Throwable), empfangen, und darf danach im Prinzip alles tun, was man in so einer Situation tun kann - mit einer Einschränkung: Wenn komplizierte Dinge getan würden, die ihrerseits wieder eine Exception triggern, die nicht gefangen wird, landet man kein zweites Mal im Exception-Handler. Also ist es sinnvoll, nichts allzu komplexes zu tun. Man kann auf dem Produktivserver eine schlichte HTML-Nachricht der Art "Irgendetwas unerwartetes ist fehlgeschlagen - unsere Admins sind bereits benachrichtigt." an den User zurückgeben, und auf dem Entwicklungsserver auch noch direkt den gesamten Stacktrace inklusive Exception-Message ausgeben, was das schnelle Reagieren beim Entwickeln erleichtert. (Die Produktionsmeldung erfordert natürlich, dass sich jemand die Serverlogs anschaut - mit mail() eine Nachricht zu versenden dürfte manchmal funktionieren, aber was, wenn die Exception genau deswegen kam, weil keine Mail versendbar war?)

    Zu derselben Gruppe gehört sowas hier:

    # index.php
    require('../vendor/autoload.php'); // Composer
    $config = require('../config/di.php'); // Dependency Injection
    
    try {
      $app = new App($config);
      $app->run();
    } catch (\Exception $e) {
      echo "<html><body>Irgendwas ist schiefgelaufen...</body></html>";
      exit(1); // Abbrechen
    }
    
    // irgendwelches post-processing oder Aufräumen bei erfolgreichem Request
    

    Die andere Gruppe von Exception-Handling versucht, als Alternative ein Notprogramm aus dem Hut zu zaubern. Das funktioniert aber natürlich nur, wenn es etwas sinnvolles zu tun gibt. Beispielsweise könnte auf einer Webseite das Wetter oder irgendein Aktienkurs eingeblendet werden und soll beispielsweise jede Minute neu vom Quellserver bezogen werden, und den Rest der Minute aus dem Cache kommen.

    Wenn das Neuholen der Daten scheitert, könnte man grundsätzlich ja die schon existierenden Daten weiterverwenden (Datum der letzten Aktualisierung wird einfach mit ausgegeben) - und wenn keine Daten vorliegen, könnte man irgendeine Meldung anzeigen oder die Info ganz weglassen, ohne dass es die komplette Seite am Anzeigen hindert.

    Meist ist das, was man tut, allerdings wichtig, und das Notfallprogramm besteht einfach darin, der darüberliegenden Softwareschicht mitzuteilen, dass es nicht funktioniert hat. Die soll dann entscheiden, ob es SO wichtig war, dass die Aufgabe komplett abgebrochen werden muss, oder ob es eine Alternative gibt.

    Gutes Exception-Handling zeichnet sich dadurch aus, dass man eben gerade kein "Pokemon-Exception-Handling: Catch them all" macht (wie im zweiten Codebeispiel), denn wenn EIN catch für alle Exceptions zuständig ist, kann man entweder nur genau ein Verhalten für alle Exceptions programmieren (hier: Fehlermeldung zeigen und abbrechen), oder es wird sehr sehr kompliziert, für jede individuelle Exception eine Sonderbehandlung zu schreiben. Genauer gesagt ist die oberste Applikationsschicht der falsche Ort für die Details eines Notfallplans, denn die Alternativdaten werden irgendwo im Inneren der Applikation benötigt - und dort wieder hinzuspringen dürfte unmöglich sein.

    Deshalb bringt jede Softwareschicht am Besten ihre eigenen Exceptions mit. Wie du bei PDO gesehen hast, existiert die PDOException: Nur PDO wirft diese Exceptions, und Code, der allgemein Exceptions fangen kann, könnte erkennen, dass beim Auftreten einer PDOException offensichtlich etwas mit der Datenbank nicht stimmt.

    Die Template-Engine "Twig" ebenfalls eine eigene Exception definiert, und im Verzeichnis "Error" noch drei Untervarianten davon. Ein Programmteil, der jetzt irgendwas mit Twig-Templates macht, könnte diesen Twig_Error fangen und anders behandeln, als eine PDOException. Andere Softwarepakete haben üblicherweise ebenfalls auch eigene Exceptions definiert.

    Was üblicherweise passiert: Code ruft eine Funktion einer anderen Schicht auf und fängt eine Exception. Der Code kann deshalb nicht normal weiterarbeiten und muss das weiter nach oben melden. Zwei Möglichkeiten bestehen:

    1. Man kann auf das Fangen der Exception verzichten, oder mit catch (Exception $e) { throw $e; } die gefangene Exception wieder werfen (letzteres ist sinnlos, wenn man nichts zusätzlich macht, es verwirrt nur den codelesenden Entwickler etwas). Dann sieht die Softwareschicht darüber, was diese Schicht für Implementierungsdetails darunter aufgerufen hat - aus Abstraktionsgründen ist das eher schlecht.

    2. Diese Softwareschicht kann auch eine eigene Exception werfen und damit die Implementierungsdetails (z.B. die Verwendung von PDO) vor der aufrufenden Schicht verstecken. Seit PHP 5.3 kann man beim Neuerzeugen von Exceptions die vorhergehende Exception als Parameter dem Konstruktor mitgeben, es muss also keine Information verloren gehen. Der Vorteil ist, dass die neue Exception eine detailiertere Beschreibung des Fehlerzustands enthalten kann, weil höhere Softwareschichten in der Regel abstraktere Funktionen enthalten. Als Entwickler muss man sich also nicht mit "Ein SQL-Query auf die DB ging schief" herumschlagen (Welcher denn? Es gibt vermutlich hundert Querys, und manche sehen fast gleich aus bzw. SIND gleich, werden nur von unterschiedlichen Stellen aus aufgerufen), sondern kriegt "Beim Lesen der User-Tabelle fürs Login hat was nicht funktioniert."

    Grüße Sven