Ankündigung

Einklappen
Keine Ankündigung bisher.

Exception Handling bei Datenbankzugriffen mit ORM

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • Exception Handling bei Datenbankzugriffen mit ORM

    Hallo Community,

    ich programmiere gerade eine Webanwendung mit vielen Datenbankzugriffen, wobei ich einen objektrelationalen Mapper (Eloquent) nutze.
    Meine Frage ist schlicht, wann und wie man bei solchen Zugriffen Exception Handling anwendet.
    Aus dem Bauch heraus würde ich nur schreibende Zugriffe in try-catch tun, um Fehler meinerseits bei der vorherigen manuellen Konsistenzprüfung bzw. irgendwelche DB-internen Dinge auszuschließen. Ich sehe es ja richtig, dass man nicht ungeprüft Änderungsoperationen auf einer DB ausführt und sich auf das Exception Handling stützt?
    Auch habe ich mal irgendwie gelernt, dass man Datenbankzugriffe generell mit try-catch umfassen soll, aber das leuchtet mir nicht ein, da lesende Zugriffe i.d.R. keine Exceptions werfen, der Code aufgebläht wird und die Anwendung ohne geladene Daten eh nicht läuft.

    Stimmt das so ungefähr? Gibt es da Best Practices?


  • #2
    Ich käme ehrlich gesagt nicht auf die Idee Methoden zum Datenbankzugriff - z. B: User::find() oder $user->save() - in try-catch-Blöcke zu verpacken. Darum soll sich doch bitte grundsätzlich eine übergeordnete Ebene kümmern - zumindest im Normalfall. Wenn ich z. B. ein neues Model mit von einem Formular per POST-Request übermittelten Werten fülle und dann speichern will, dann muss ich dafür keine individuelle Absicherung machen. Das ist ein Fehler der einfach nur den (ordentlichen) kompletten Abbruch des Skripts nach sich ziehen sollte und der kein spezielles Kontext-Wissen besitzen muss, also kann das gerne außerhalb der Controller-Methode geschehen.

    Kommentar


    • #3
      Also ich verstehe nicht so ganz was du meinst, kannst du das näher erläutern?
      Wenn ich z. B. ein neues Model ... speichern will, dann muss ich dafür keine individuelle Absicherung machen.
      Wenn ich z.B. $user->save() aufrufe und den User gibt es schon, dann wird ja eine Exception geworfen. Also muss man doch vorher prüfen, weil Exceptions sollte man ja schon verhindern wenn voraussehbar, oder?

      Das ist ein Fehler der einfach nur den (ordentlichen) kompletten Abbruch des Skripts nach sich ziehen sollte und der kein spezielles Kontext-Wissen besitzen muss, also kann das gerne außerhalb der Controller-Methode geschehen.
      Ja aber in welcher Form, was ist die übergeordnete Instanz? Ich möchte ja evt. eine Meldung für den User ausgeben wie "User speichern fehlgeschlagen" und will auch nicht immer sofort das Skript abbrechen.

      Kommentar


      • #4
        weil Exceptions sollte man ja schon verhindern wenn voraussehbar
        Hehe, wenn du das so hinbekommst, Gratulation... die Regel ist es aber nicht.

        Exception (=Ausnahme) stellt eine Ausnahme von der Regel dar (wer hätte es gedacht).. eine solche Ausnahmesituation vorauszusehen ist mitunter nicht ganz so einfach - oder kannst du voraussehen, das der User die USB-Festplatte abgezogen hat, auf der dein Script grade etwas speichern wollte (als Beispiel)?
        Über 90% aller Gewaltverbrechen passieren innerhalb von 24 Stunden nach dem Konsum von Brot.

        Kommentar


        • #5
          Zitat von smirkmirkin Beitrag anzeigen
          Wenn ich z.B. $user->save() aufrufe und den User gibt es schon, dann wird ja eine Exception geworfen. Also muss man doch vorher prüfen, weil Exceptions sollte man ja schon verhindern wenn voraussehbar, oder?
          Natürlich solltest du vorher Validieren und prüfen, ob Username, Email, ... schon in nutzung sind.

          Zitat von smirkmirkin Beitrag anzeigen
          Ja aber in welcher Form, was ist die übergeordnete Instanz? Ich möchte ja evt. eine Meldung für den User ausgeben wie "User speichern fehlgeschlagen" und will auch nicht immer sofort das Skript abbrechen.
          Das Framework, da du bei einer unerwarteten Exception ohnehin nichts mehr tun kannst außer Script abbrechen, Fehler anzeigen und später dann Fehler beheben.
          Zitat von nikosch
          Macht doch alle was Ihr wollt mit Eurem Billigscheiß. Von mir aus sollen alle Eure Server abrauchen.

          Kommentar


          • #6
            Exception (=Ausnahme) stellt eine Ausnahme von der Regel dar (wer hätte es gedacht).. eine solche Ausnahmesituation vorauszusehen ist mitunter nicht ganz so einfach
            Naja, wenn man ungeprüft eine DB-Operation ausführt kann man schon eine Exception voraussehen.

            Wenn ich also schlicht folgendes mache:
            PHP-Code:
            // prepare $user ...
            if (! User::find($user->id)) {
                
            $user->save();

            Dann kann es ja trotzdem noch sein, dass save() von der DB durch irgendein obskures Constraint abgelehnt wird und dann wird eine Exception geworfen. In dem Fall möchte ich dann aber natürlich nur eine Meldung a la "User speichern fehlgeschlagen" ausgeben und ansonsten soll die Methode wie gehabt weiterlaufen. Wie soll man das anders realisieren als mit nem try-catch-Block um save()?

            Kommentar


            • #7
              Wenn es um Programmflusskontrolle geht dann arbeite mit try-catch-Blöcken und Exceptions. Behandle Exceptions da wo sie auftreten (im Normalfall). Egal ob lesende oder schreibende Zugriffe (wenn man eine Datei nicht lesen kann ist das nicht weniger schlimm, als wenn man in eine Datei nicht schreiben kann - das gilt natürlich auch für DBs). Wenn es um Fallunterscheidungen/Verzweigungen oder Validierungen geht, dann benutze if/else

              Dein Beispiel:

              PHP-Code:
              // prepare $user ... 
              if (! User::find($user->id)) { 
                  
              $user->save(); 

              gehört wie Du schon richtig angemerkt hast ohnehin in ein try-catch-block:

              PHP-Code:
              try {
                  if (! 
              User::find($user->id)) { 
                      
              $user->save();
                   }
              } catch (
              DatabaseErrorException $e) {

              } catch (
              Exception $e) {


              Die Gründe weshalb ein User nicht gefunden werden konnte, können außerdem vielfältig sein. Ein false oder Null wird Dir darüber keine Auskunft geben können. Das Ergebnis (aus Sicht des Clients/Anwendung) ist jedoch im Endeffekt bei false, NULL und Exception das Gleiche - der ursprünglich vorgesehene Programmfluss muss an dieser Stelle verlassen/geändert werden.

              Wieso also nicht gleich eine Exception werfen, die Dir eine viel differenziertere Behandlung des "Ausnahmefalles" ganz ohne if-else-Spaghetticode erlauben würde:

              PHP-Code:
              try {
                  
              $user->save();
              } catch (
              UserAlreadyExistsException $e) {

              } catch (
              UserIsBlockedException $e) {

              } catch (
              DatabaseErrorException $e) {

              } catch (
              Exception $e) {


              Exceptions sind allerdings NICHT zum Validieren da, d.h. eine Methode wie user::find() sollte entweder ein Userobjekt oder eine Exception zurückgeben (in Deinem Beispiel wird die Finder-Methode leider zum Validieren zweckentfremdet). Zum Prüfen ob ein User bereits existiert, sollte eine Methode wie user::exists() verwendet werden, die im Normalfall true|false oder im Ausnahmefall eben eine Exception liefern sollte

              vg
              jack
              -

              Kommentar


              • #8
                Sollte der Code ungefähr so aussehen oder habe ich es immer noch nicht verstanden?
                PHP-Code:
                try {
                    if (! 
                $user->exists()) { 
                        
                $user->save();
                    }
                    else {
                        
                addErrorMessage('User existiert bereits.');
                    }
                } catch (
                DatabaseException $e) {
                    
                addErrorMessage('Der User konnte nicht gespeichert werden.');

                mit
                PHP-Code:
                public function exists() {
                    if (
                self::find($this->id)) {
                        return 
                true;
                    }
                    return 
                false;

                Also ich verwende try-catch quasi nur, wenn es weitergehen soll und bei sonstigen Exceptions, wo die Ausführung stoppen soll, definiere ich im Framework eine allgemeine Handling-Methode wo dann einfach eine allgemeine Fehlermeldung mit der Ursache ausgegeben wird?

                Kommentar


                • #9
                  Du verwendest Exceptionhandling überall dort, wo unerwartete Ausnahmezustände auftreten können. Und damit ist nicht gemeint, das "Benutzername oder Passwort falsch" sind. Ob die Exception auf gleicher Ebene oder übergeordnet behandelt wird, ist dabei erstmal unerheblich.

                  Eine DatabaseErrorException könnte auftreten, wenn die Verbindungsparameter falsch waren, aber genauso, wenn der Netzwerkstecker gezogen wurde. Mit einem generischen Exceptionhandling sicherst du dich gegen jede Situation mit einem einzigen Code ab, spezifische Fallprüfungen sind nicht mehr notwendig (dh. ohne Exceptionshandling müsstest du jeden denkbaren Fall gesondert prüfen und behandeln).
                  Über 90% aller Gewaltverbrechen passieren innerhalb von 24 Stunden nach dem Konsum von Brot.

                  Kommentar


                  • #10
                    Zitat von tkausl Beitrag anzeigen
                    Natürlich solltest du vorher Validieren und prüfen, ob Username, Email, ... schon in nutzung sind.
                    Eben. Und da du schon Eloquent nutzt - Laravel besitzt genau dafür auch eine Validierungs-Komponente die so etwas kann:
                    PHP-Code:
                    $validator Validator::make(
                        array(
                    'username' => Input::get('username')),
                        array(
                    'username' => 'unique:users,username')) // 'username' darf in Tabelle 'users' nicht zwei Mal auftauchen
                    ); 

                    Kommentar


                    • #11
                      PHP-Code:
                      if (! User::find($user->id)) { 
                          
                      $user->save(); 

                      Bin mir nicht sicher was dieser Code aussagen soll: Egal ob die Bedingung wahr oder falsch ist, speichern kannst du das Model. Das was du da machst ist Validierung, wenn ich das richtig sehe. Bei der Validierung arbeitest du aber nicht mit exceptions denn schließlich ist es keine Ausnahme, wenn z. B. ein Username schon vergeben ist. Das ist ein legitimes Szenario auf dessen Eintreten du reagieren kannst. Aber nicht mit exception handling.

                      Dann kann es ja trotzdem noch sein, dass save() von der DB durch irgendein obskures Constraint abgelehnt wird und dann wird eine Exception geworfen. In dem Fall möchte ich dann aber natürlich nur eine Meldung a la "User speichern fehlgeschlagen" ausgeben und ansonsten soll die Methode wie gehabt weiterlaufen.
                      Aber ist das dann wirklich ein Problem der Methode und nicht vielmehr ein Problem im Kontext des Speicherns des Models? Offensichtlich kannst du das Model nicht speichern obwohl du das können solltest. Wenn dafür z. B. verantwortlich ist, dass der MySQL-Server abgestürzt ist, ist das meiner Meinung nach ein Problem das du nicht in der Speichern-Methode abfangen musst. Das ist ja ein grundlegendes Problem das wahrscheinlich deine Applikation nicht mehr lauffähig macht. Weiterlaufen wäre also keine Option. Dann kann man auch einfach die exception außerhalb der Methode abfangen und z. B. eine Fehlerseite anzeigen mit einer generischen Fehlermeldung.

                      Also was ich nicht ganz einsehe ist, dass du die Methode weiterlaufen lasen möchtest. Wozu? Eine nicht optionale Aufgabe der Methode, das Speichern des Models, ist fehlgeschlagen. Warum weitermachen? Was soll dann noch passieren? Abbruch ist die einzige sinnvolle Wahl. (Nochmal, natürlich nur der Grund ein wirklicher "Fehler" - ein Bug zum Beispiel - ist. Ein wirklich nicht vorhersehbares Verhalten wie der Absturz des MySQL-Servers.) Du belügst den User ja nicht wenn du statt "User speichern fehlgeschlagen" ein generisches "Es ist ein Problem aufgetreten. Die Aktion konnte nicht durchgeführt werden. Bitte versuchen Sie es erneut!" ausgibst.

                      E: Irgendwie hab ich's in letzter Zeit mit Doppelposts.

                      Kommentar


                      • #12
                        Keine Ahnung was genau dieser Code aussagen soll? Egal ob die Bedingung wahr oder falsch ist, speichern kannst du das Model. Das was du da machst ist Validierung.
                        Das war ja nur ein Beispiel, ich prüfe halt vor dem Einfügen eines neuen Users in die Db, ob ein gleichartiger User schon vorhanden ist. Ersetze id halt durch name, dann ist es vllt klarer. Ja das ist eine Validierung, wie soll ich die sonst machen?Ich benutze übrigens nur Eloquent, nicht Laravel.

                        @lstegelitz
                        Naja ich will die Methode halt weiterlaufen lassen um den Fehler auszugeben dass der User nicht gespeichert werden konnte. Nur weil eine Datenbank-Exception auftritt heißt das doch nicht, dass keine Verbindung mit der Db besteht?

                        Kommentar


                        • #13
                          Zitat von smirkmirkin Beitrag anzeigen
                          Das war ja nur ein Beispiel, ich prüfe halt vor dem Einfügen eines neuen Users in die Db, ob ein gleichartiger User schon vorhanden ist. Ersetze id halt durch name, dann ist es vllt klarer. Ja das ist eine Validierung, wie soll ich die sonst machen?
                          Ah ok ich glaube da hatten wir uns in 2# und #3 missverstanden und das hat sich dann in der Folge fortgesetzt.

                          Ja klar vorher abzufragen ob der User existiert ist natürlich in Ordnung bzw. sogar notwendig. Ignoriere meine vorherigen Ausführungen zum Thema Validierung am besten einfach.

                          Kommentar


                          • #14
                            Ihr seid zu schnell für mich mit Schreiben

                            Kommentar


                            • #15
                              Aber dann muss ich ja alle DbErrorExceptions gleich behandeln, aber ich möchte ja nach dem fehlgeschlagenen Speichern eines Users die Fehlermeldung ausgeben.
                              Aber genau das ist der Sinn hinter Exceptionhandling! Du willst gewisse Exceptions gleich behandeln. Wenn du unterschiedliche Behandlung benötigst, füge eine neue Exceptionklasse ein, die in diesem Fall geworfen und gesondert behandelt werden kann. In diesem Fall müsstest du natürlich unterscheiden, was der Grund für eine Ausnahme ist (im Beispiel "Verbindungsparameter falsch" oder "Netzwerkverbindung gestört"). Faktisch ist es dir aber egal, ob du nicht an die Datenbank kommst, weil "Verbindungsparameter falsch" oder "Netzwerkverbindung gestört" ist - die Reaktion darauf ist die gleiche: gebe eine Meldung aus und beende den Vorgang (unerfolgreich).
                              Über 90% aller Gewaltverbrechen passieren innerhalb von 24 Stunden nach dem Konsum von Brot.

                              Kommentar

                              Lädt...
                              X