Dred: OOP php: verschachtelte Klassen und ein Zugriff auf eine Eigensc

Ich habe ähnliche Situation vorliegen:

  
  
class Site{  
  
  var $Exception = array();  
  
  public function __construct(){  
  
    $db = new DB();  
  
  }  
  
}  
  
class DB{  
  
  
  //so sieht der exception handler aus  
  //wenn einer fehler auftritt, wird die Exception an die Seite übergeben  
  private function ExceptionHandler($e){  
  
    Global $Site;  
    $Site->Exception[] = $e;  
  
  }  
  
  
}  
  
// instantiierung einer Seite  
$Site = new Site();  
  

Einen solchen ExceptionHandler() habe ich in vielen Klassen eingebaut und normalerweise klappt das mit dem Global und dem Anhängen der Exception an den Array. Nur nicht, wenn es im Konstruktor der Site geschieht. Logisch: denn dann ist das Objekt $Site noch nicht vorhanden.

Wie kann ich das lösen. Vielleicht ist der Ansatz schlecht mit dem Global aber ich möchte alle Exception an die Seite delegieren.

Könnt ihr mir helfen. Ich programmiere noch nicht lange OO.

  1. Moin!

    Könnt ihr mir helfen. Ich programmiere noch nicht lange OO.

    Merkt man. Dein OOP-Konzept ist ziemlich grausig!

    WENN du OOP machen willst, dann wende unbedingt folgende Regeln an:

    1. Der Befehl "global" ist VERBOTEN!
    2. Es gilt das Verbot von "global".
    3. Weiterhin ist die Verwendung von "global" UNTERSAGT!

    4. Die Verwendung des Keyworts "new" ist möglichst zu vermeiden.

    Nachdem das also geklärt ist, wende ich mich zunächst dem auffälligsten Konstrukt deines Codes zu: Die Exceptions.

    Was verstehst DU ganz persönlich unter einer Exception? Denn dein Verständnis hat sich ja irgendwie in deinem Code manifestiert, wenngleich auch nicht besonders schön. Offensichtlich verwendest du keine Exceptions im PHP-Sinn, denn wenn das der Fall wäre, würdest du sie werfen (throw new Exception('Erklärtext');) und fangen (try { /* Code */ } catch (Exception $e) { /* Code für Fail */ }).

    Alles, was du glaubst tun zu müssen, um mit Exceptions in deinem Verständnis umzugehen, ist vermutlich falsch, weil du es nicht besser kennst - aber da werden wir sicher diverse Hinweise liefern, um das zu ändern.

    Ich habe ähnliche Situation vorliegen:

    class Site{

    var $Exception = array();

    public function __construct(){

    $db = new DB();

    }

    }

    class DB{

    //so sieht der exception handler aus
      //wenn einer fehler auftritt, wird die Exception an die Seite übergeben
      private function ExceptionHandler($e){

    Global $Site;
        $Site->Exception[] = $e;

    }

    }

    // instantiierung einer Seite
    $Site = new Site();

      
    An deinem Code ist, wie du dir denken kannst, einiges grausam schlecht designt. Die Tatsache, dass die Site ihren DB-Handler selbst instanziiert, ist problematisch; würde ich nicht so machen, sondern als Parameter dem Konstruktor übergeben, also außerhalb der Site in dem Code, der Site ins Leben ruft.  
      
    Das hat auch den Vorteil, dass du dir auf dieser einfachen Stufe direkt das GLOBAL in der DB sparen kannst, wenn du dir eine Methode `public function setSiteObject(Site $site) { $this->_site = $site }`{:.language-php} baust, dir eine private Eigenschaft $\_site für dein Site-Objekt in die DB tust, und dort dein Site-Objekt übergibst:  
      
    ~~~php
    $db = new DB();  
    $site = new Site($db);  
    $db->setSiteObject($site);
    

    In der DB hast du dann statt $Site immer $this->_site verfügbar.

    Allerdings: Dein Exception-Handling ist, wie erwähnt, sehr fragwürdig.

    Einen solchen ExceptionHandler() habe ich in vielen Klassen eingebaut und normalerweise klappt das mit dem Global und dem Anhängen der Exception an den Array. Nur nicht, wenn es im Konstruktor der Site geschieht. Logisch: denn dann ist das Objekt $Site noch nicht vorhanden.

    Wie kann ich das lösen. Vielleicht ist der Ansatz schlecht mit dem Global aber ich möchte alle Exception an die Seite delegieren.

    Ich kann dir schwerlich was raten, wenn ich nicht weiß, was du warum gemacht hast. Wirfst du Exceptions? Wo? Und was soll das Ergebnis sein, wenn eine geworfen wird.

    Exceptions sind, wenn man es böse betrachtet, eine moderne Form von GOTO. Der normale Programmablauf wird verlassen, und im catch-Teil weitergemacht. Deshalb sollte man dieses Mittel eher sparsam einsetzen, insbesondere nicht zur Steuerung des Programmflusses mißbrauchen.

    Im Prinzip gibt es nur zwei Möglichkeiten, mit Exceptions umzugehen: Entweder, der aufrufende Code einer exceptionwerfenden Funktion kann die Exception fangen, weil er auch mit dem Fehlerfall umzugehen weiß. Das kann beispielsweise in der Bereitstellung eines passenden Ersatzes münden (z.B.: getArrayOfDatabases() -> DbUnavailableException -> catch: return array()).

    Alternativ fängt man die Exception eben gerade nicht, sondern lässt sie ganz bis nach "oben" durchschlagen - was dann natürlich nicht ganz so elegant zu einer Fehlermeldung führen würde, weshalb es eine gute Idee ist, auf oberster Codeebene ein pauschales Catch für alle Exceptions einzufügen, welcher den HTTP-Status auf 500 "Internal server error" setzt und dann noch eine hübsche Fehlerseite ausgibt.

    - Sven Rautenberg

    1. Könnt ihr mir helfen. Ich programmiere noch nicht lange OO.

      Merkt man. Dein OOP-Konzept ist ziemlich grausig!

      DAnke für diesen Spruch, Du hochnäsiger Mensch. Ich hätte 100€ verwettet, dass so ein Spruch auf meinen Satz folgt. Du hast ja auch echt ne Menge von meinem Code gesehen, um das sagen zu können.

      Nachdem das gesagt ist, wende ich mich nun Deiner Antwort zu.

      Vielen Dank für deine Antwort.

      Es ist so, das der Beispielcode nur ein Bruchstück darstellt. Und im Kontext macht es mehr Sinn, die DB im Constructor der Site zu instantiieren. Denke ich. Es ist auch nicht nur eine DB, daher mag ich das mit dem Parameter nicht.

      Generell gilt bei meinem Projekt: alle Klassen sollen annähernd stand-alone-fähig sein. Das heisst, jede Klasse kümmert sich erstmal selbst um seine Exceptions. Natürlich verwende ich throw, try u. catch. Ich wende es, denke ich, ziemlich sauber an. Warum hängst Du Dich so an dem Beispielcode auf? In annähernd jedem catch steht dann der Code:

        
      catch (Exception $e) {  
        
        $this->ExceptionHandler($e);  
        	  
      }
      

      Damit landen also alle Exceptions beim eigenen ExceptionHandler(). Dort soll dann jeder, der später mit den Klassen arbeitet, machen, was er möchte. Ich möchte, dass sie dann an das $Site - Object gegeben werden, was ich momentan mit Global $Site, etc.... mache.

      Die Frage, die ich gestern bestimmt mißverständlich oder schlecht formuliert habe -

      Ja hast Du, häng' den Programmierjob an den Nagel, Junge!

      ... ja danke, ich weiß - könnte vielleicht besser lauten: Wie kann ich aus einer Klasse heraus die Eigenschaft einer anderen Klasse verändern (Ansätze mit extend würde hier glaube ich nicht so gut passen). Wie kann ich die Exceptions einer Klasse an eine andere delegieren. So, klappt es ja bereits. Global verwende ich tatsächlich nur in diesen ExceptionHandler()'n, sozusagen ganz provokativ.

      Alles, was du glaubst tun zu müssen, um mit Exceptions in deinem Verständnis umzugehen, ist vermutlich falsch, weil du es nicht besser kennst - aber da werden wir sicher diverse Hinweise liefern, um das zu ändern.

      Vielen Dank. Ich hoffe/glaube ich verwende es nicht völlig falsch. Ich habe zum Beispiel häufig im Constructor ein try-Block, der sich um mehrere Funktionen spannt, in denen ich dann exceptions werfe, die sich dann bis zum Constructor durchschlagen.

      An deinem Code ist, wie du dir denken kannst, einiges grausam schlecht designt. Die Tatsache, dass die Site ihren DB-Handler selbst instanziiert, ist problematisch; würde ich nicht so machen, sondern als Parameter dem Konstruktor übergeben, also außerhalb der Site in dem Code, der Site ins Leben ruft.

      Würde ich nicht, da es mehrere Instantiierungen sind (würden dann zu viele Parameter). Außerdem finde ich Parameter zu "statisch" in Bezug auf eine Veränderung des Projektes.

      Ich kann dir schwerlich was raten, wenn ich nicht weiß, was du warum gemacht hast. Wirfst du Exceptions? Wo? Und was soll das Ergebnis sein, wenn eine geworfen wird.

      Ich werfe sie (bspw. in Methoden der Klasse DB) und sie landen beim ExceptionHandler der Klasse DB. Im Ende sollen sie zur Klasse Site, da ich sie dort aus der Eigenschaft Exception (Array) auslese und darstelle.

      Exceptions sind, wenn man es böse betrachtet, eine moderne Form von GOTO. Der normale Programmablauf wird verlassen, und im catch-Teil weitergemacht. Deshalb sollte man dieses Mittel eher sparsam einsetzen, insbesondere nicht zur Steuerung des Programmflusses mißbrauchen.

      Zugegeben, ich verwende sie zur Programmsteurung. Finde ich aber auch nicht so merkwürdig. Wenn ich eine Funktion habe, die mit einer Datei allerhand Dinge anstellt und ich als erstes im try-Block mal habe if(!file_exists($file)) throw new Exception("File not found.", 1); und damit der weitere Ablauf der Funktion verhindert wird, ist das doch mehr als gewünscht. Oder soll ich jedesmal tolle if-Blöcke bauen um bis zur 14-ten Verschachtelung zu kommen?

      Im Prinzip gibt es nur zwei Möglichkeiten, mit Exceptions umzugehen: Entweder, der aufrufende Code einer exceptionwerfenden Funktion kann die Exception fangen, weil er auch mit dem Fehlerfall umzugehen weiß. Das kann beispielsweise in der Bereitstellung eines passenden Ersatzes münden (z.B.: getArrayOfDatabases() -> DbUnavailableException -> catch: return array()).

      Alternativ fängt man die Exception eben gerade nicht, sondern lässt sie ganz bis nach "oben" durchschlagen - was dann natürlich nicht ganz so elegant zu einer Fehlermeldung führen würde, weshalb es eine gute Idee ist, auf oberster Codeebene ein pauschales Catch für alle Exceptions einzufügen, welcher den HTTP-Status auf 500 "Internal server error" setzt und dann noch eine hübsche Fehlerseite ausgibt.

      • Sven Rautenberg

      Je nach Lage fange ich sie entweder unmittelbar oder lasse sie nach oben durchschlagen.

      Vielen Dank für Deine kompetente Hilfe. Bitte verstehe aber, dass ich nicht verstehen kann, wieso das immer so schnippisch ablaufen muss. Ich bin in der Situation des Unwissenden nur auf der Suche nach Informationen. Ich bin nicht faul oder dumm. Ich schätze Deine Hilfe, aber ich schätze nicht diese demotivierenden Kommentare "grausig", "grausame schlecht",...

      Liebe Grüße

      1. Hallo,

        Könnt ihr mir helfen. Ich programmiere noch nicht lange OO.

        Merkt man. Dein OOP-Konzept ist ziemlich grausig!

        DAnke für diesen Spruch, Du hochnäsiger Mensch.

        Unterscheide zwischen:

        "Der Code ist grausig!" - Es wird eine Sache beschrieben und kein Mensch.

        Und:

        "Du hochnäsiger ..." - Es wird ein Mensch persönlich angegriffen.

        Das Forum ist sehr beliebt. Sven Rautenberg zeichnet glaube ich verantwortlich für das Forum und ist Vereinsmitglied (oder Vorstand?). Zudem äußerst kompetent. Sei froh, dass Du eine Antwort von ihm hast. Und frag Dich doch, warum so ein erfahrener Kerl Deinen Code grausig findet. Das bringt Dich weiter.

        Gruß

        jobo

      2. Moin!

        Könnt ihr mir helfen. Ich programmiere noch nicht lange OO.

        Merkt man. Dein OOP-Konzept ist ziemlich grausig!

        DAnke für diesen Spruch, Du hochnäsiger Mensch. Ich hätte 100€ verwettet, dass so ein Spruch auf meinen Satz folgt. Du hast ja auch echt ne Menge von meinem Code gesehen, um das sagen zu können.

        Wenn du meine Antwort liest mit dem Hintergedanken im Kopf, ich würde dich ganz persönlich angreifen wollen, dann lies sie bitte noch mal ohne diesen Hintergedanken.

        Bedenke dabei: ICH habe DICH nicht persönlich angegriffen, ich habe das fachlich kritisiert, was du uns in deinem Posting gezeigt hast. Ich habe die Dinge aufgegriffen, die mir in deinem Ausschnitt sofort ins Auge gefallen sind, die Problematik DEINER Vorgehensweise erklärt und begründet, und Alternativen aufgezeigt.

        Wenn das alles in deiner Wahrnehmung mündet in "hochnäsiger Mensch", ist das nicht sachgerecht.

        Problematisch in diesem Zusammenhang finde ich auch, dass du in deiner Antwort Texte als Zitatteile einfügst, die ich so nicht geschrieben habe. Sowas gehört sich nicht, wenn man offen und an einer sachlichen Lösung interessiert miteinander umgeht.

        Es ist so, das der Beispielcode nur ein Bruchstück darstellt. Und im Kontext macht es mehr Sinn, die DB im Constructor der Site zu instantiieren. Denke ich. Es ist auch nicht nur eine DB, daher mag ich das mit dem Parameter nicht.

        Da sind wir genau beim Kern: Ich kritisiere, dass dein Code tut, was er tut. Ich biete Verbesserungsvorschläge an. Du ignorierst diese Vorschläge, weil du selbst glaubst, dass das richtig ist, was du tust. Und quittierst mit "hochnäsiger Mensch".

        Es steht dir frei, beliebige Dinge so zu programmieren, wie du willst. Solange das Resultat so funktioniert, wie du willst, ist alles prima.

        Erwarte jedoch nicht, dass erfahrene Programmierer das genauso sehen. Wenn du nach Verbesserungsvorschlägen fragst, kriegst du die selbstverständlich. Wenn du aufgrund deines noch nicht so ausgeprägten Erfahrungsschatzes nicht einleuchten will, warum der andere Ansatz besser ist, mach einfach so, wie du denkst - solange, bis deine Erfahrungen dir sagen, dass der andere Ansatz doch besser ist.

        Generell gilt bei meinem Projekt: alle Klassen sollen annähernd stand-alone-fähig sein.

        Naja, das Problem ist ja nun aber, dass z.B. die Site-Klasse eben NICHT standalone-fähig ist. Sie benötigt ZWINGEND eine Klasse namens "DB" in der verfügbaren Codebasis.

        Gibst du der Site im Konstruktor oder in einer separaten Methode ein Datenbankobjekt zur Verwendung, dann muss die Datenbankklasse nicht mehr zwingend "DB" heißen, sie muss nur noch sämtliche Methoden implementieren, die Site aufruft. Und um das sicherzustellen, wird man sehr wahrscheinlich ein Interface definieren, welche als Kontrollinstanz dafür sorgt, dass sämtliche Methoden, die zur Verfügung gestellt werden müssen, auch implementiert sind. Umgekehrt dokumentiert das Interface, welche Methoden man überhaupt benutzen darf, bzw. auf welche Methoden man sich verlassen kann.

        Das heisst, jede Klasse kümmert sich erstmal selbst um seine Exceptions.

        Das ist halt schon genau das, was ich an deinem Konzept nicht mag. Eine Exception wird geworfen, weil ein nicht umgehbares Problem aufgetreten ist, und der Code an dieser Stelle nicht sinnvoll im vorgesehenen Fahrplan weitermachen kann.

        Das wiederum bedeutet, dass die aufgerufene Methode eben gerade NICHT das gewünschte Ergebnis zu liefern imstande ist. Der Codeteil, der diese Methode aufrief, kann also ebenfalls nicht wie geplant weitermachen.

        Jetzt gibts eben genau zwei Möglichkeiten: Entweder an dieser Stelle ist im catch-Teil das Notfallprogramm enthalten, welches als Alternative zum Einsatz kommt. Das sollte dann allerdings höchst individuell gestaltet sein, von einem pauschalen Exception-Handling halte ich absolut nichts.

        Oder man kann an dieser Stelle kein sinnvolles Notfallprogramm anbieten - dann wird die Exception grundsätzlich nicht gefangen, folglich gibts auch kein try, und wenn sich nicht auf oberster Ebene ein pauschales try/catch doch noch erbarmt, diese Exception etwas userfreundlich in eine nette Browserfehlermeldung mit Status 500 zu wandeln, dann wird PHP mit einer eher hässlichen Fehlermeldung die Programmausführung abbrechen.

        Natürlich verwende ich throw, try u. catch. Ich wende es, denke ich, ziemlich sauber an.

        Da bin ich anderer Meinung. Ich würde es SO nicht tun. Leider äußerst du dich zu deiner Motivation, warum du das so machst, wie du es machst, bislang nicht.

        Warum hängst Du Dich so an dem Beispielcode auf?

        Weil das das einzige ist, was ich von deinem Code kenne bislang. Und du zu diesem Beispielcode, insbesondere im Hinblick auf das Exception-Handling, um Rat gefragt hast.

        Bedenke: Dir selbst ist dein gesamter Code, die gesamte Vorgehensweise, natürlich sonnenklar. Du kennst ihn ja schließlich seit mehreren Wochen. Ich kenne nur deinen Beispielcode und dein geschildertes Problem.

        In annähernd jedem catch steht dann der Code:

        
        
        > catch (Exception $e) {
        > 
        >   $this->ExceptionHandler($e);
        >   	
        > }
        
        

        Damit landen also alle Exceptions beim eigenen ExceptionHandler().

        Das bedeutet, dass du es für möglich erachtest, dass sich beim Aufrufen deines Skriptes mehrere Exceptions nacheinander ansammeln. Ist das sinnvoll? Tritt sowas in der Realität tatsächlich auf?

        Vermutlich wirfst du an zuvielen Stellen Exceptions.

        Dort soll dann jeder, der später mit den Klassen arbeitet, machen, was er möchte. Ich möchte, dass sie dann an das $Site - Object gegeben werden, was ich momentan mit Global $Site, etc.... mache.

        Die Frage ist halt, was man im Problemfall an einer so späten Stelle mit den Exceptions noch anfangen kann. Ich würde sagen: Außer die gesammelten Exceptions als Fehlermeldungen auszugeben und dem User etwas anzuzeigen, an deren Problembehebung er nicht mithelfen kann (wenn z.B. der DB-Server unerreichbar ist, kann kein Webbrowser der Welt daran was ändern).

        Gewiß: Die Exception ist für dich als Entwickler natürlich interessant. Aber für das Logging gibt es zahlreiche fertige Lösungen, zu deren Einsatz ich raten würde, beispielsweise "Log4PHP". Ein vernünftiges Logging-Konzept kann sehr viel zu einem wartbaren und auch gut debuggbaren Code beitragen.

        Letztendlich hängt hier eben alles an der Frage: Was soll deine Softwarelösung dir bringen, und wärst du bereit, deine Voreingenommenheit zugunsten deiner aktuellen Lösung zu überwinden?

        Die Frage, die ich gestern bestimmt mißverständlich oder schlecht formuliert habe -

        Ja hast Du, häng' den Programmierjob an den Nagel, Junge!

        Wie gesagt: Mir Zitate unterzuschieben, die ich nicht geschrieben habe, ist schlechter Stil.

        ... ja danke, ich weiß - könnte vielleicht besser lauten: Wie kann ich aus einer Klasse heraus die Eigenschaft einer anderen Klasse verändern (Ansätze mit extend würde hier glaube ich nicht so gut passen). Wie kann ich die Exceptions einer Klasse an eine andere delegieren. So, klappt es ja bereits. Global verwende ich tatsächlich nur in diesen ExceptionHandler()'n, sozusagen ganz provokativ.

        Die Gestaltung von Exception-Klassen und deren Vererbung ist ein gar nicht mal simples Thema. Im Gegensatz zu anderen Klassen müssen alle Exceptions, die man selbst gestaltet, von der in PHP integrierten Klasse "Exception" erben.

        Es gibt Leute, die hiervon ausgehend Exception-Typen basierend auf der Art des aufgetretenen Problems werfen, beispielsweise die InvalidArgumentException für fehlerhafte Parameter (als Kind der "LogicException"), und die UnexpectedValueException für das Auftreten unerwarteter Werte (als Kind der "RuntimeException"). Dieser Ansatz versucht, die auftretenden Probleme nach Typen zu gruppieren (die genannten Exceptions sind in PHP über die Standard PHP Library "SPL" verfügbar), so dass man in der Lage ist, je nach Fehlertyp die eine oder andere Fehlerbehebung zu versuchen, oder dem User eine entsprechende Fehlermeldung zu übermitteln.

        Mein persönlicher Favorit hingegen ist, dass Exceptions nach den Klassen gruppiert vererbt werden, die thematisch als Module in einer Software auftreten: Eine Datenbankklasse "Db" wirft eine Db_Exception. Das darin enthaltene Modul Db_Connector wirft eine Db_Connector_Exception. Der Db_Connector_Mysql wirft eine Db_Connector_Mysql_Exception. Und die Exceptions erben in einer Kette jeweils voneinander. Damit wäre man in der Lage, auf einer sehr hohen Programmebene beim Aufruf der Methoden der Klasse "Db" mit try/catch alle Db_Exceptions zu fangen (man weiß dann, dass irgendwas in der Datenbankabfrage schief gelaufen ist und man kein Ergebnis bekommt), inklusive aller Db_Connector_Exception (weil z.B. der konfigurierte Datenbanktreiber nicht geladen werden konnte) und aller Db_Connector_Mysql_Exception (z.B. für fehlerhafte SQL-Querys).

        Innerhalb der Db-Klasse würde man mit try/catch alle Db_Connector_Exception fangen können, in Db_Connector wären das Db_Connector_Mysql_Exception, etc... Wobei das Fangen nur Sinn ergibt, wenn man ein passendes Alternativprogramm anbieten kann.

        Alles, was du glaubst tun zu müssen, um mit Exceptions in deinem Verständnis umzugehen, ist vermutlich falsch, weil du es nicht besser kennst - aber da werden wir sicher diverse Hinweise liefern, um das zu ändern.

        Vielen Dank. Ich hoffe/glaube ich verwende es nicht völlig falsch. Ich habe zum Beispiel häufig im Constructor ein try-Block, der sich um mehrere Funktionen spannt, in denen ich dann exceptions werfe, die sich dann bis zum Constructor durchschlagen.

        Hast du ein Codebeispiel 1:1? Denn sowas ist mit hoher Wahrscheinlichkeit ein Mißbrauch von Exceptions zur Programmsteuerung, und sollte vermieden werden.

        An deinem Code ist, wie du dir denken kannst, einiges grausam schlecht designt. Die Tatsache, dass die Site ihren DB-Handler selbst instanziiert, ist problematisch; würde ich nicht so machen, sondern als Parameter dem Konstruktor übergeben, also außerhalb der Site in dem Code, der Site ins Leben ruft.

        Würde ich nicht, da es mehrere Instantiierungen sind (würden dann zu viele Parameter). Außerdem finde ich Parameter zu "statisch" in Bezug auf eine Veränderung des Projektes.

        Wenn du einer Klasse mehr als vier, fünf Parameter im Konstruktor mitteilen musst, hast du auch wieder ein Designproblem. Deine Klasse muss dann zuviel unter einen Hut bringen, muss zuviel parallel kennen und können.

        Ich kann dir schwerlich was raten, wenn ich nicht weiß, was du warum gemacht hast. Wirfst du Exceptions? Wo? Und was soll das Ergebnis sein, wenn eine geworfen wird.

        Ich werfe sie (bspw. in Methoden der Klasse DB) und sie landen beim ExceptionHandler der Klasse DB. Im Ende sollen sie zur Klasse Site, da ich sie dort aus der Eigenschaft Exception (Array) auslese und darstelle.

        Es geht also ausschließlich um die Darstellung der Exceptions.

        Wieviele Exceptions erwartest du pro Skriptaufruf? Wieviele Exceptions sind Folgefehler vorher aufgetretener Exceptions?

        Nur mal so als Gedankenspiel: Wenn deine Datenbank down ist, wird vermutlich jeder Zugriff eine Exception werfen. Am Ende hast du also vermutlich zehn bis hundert Exceptions gesammelt, die dir alle nur die eine Information geben: Deine Datenbank ist unerreichbar. Inwiefern macht es Sinn, bei einem so handfesten Fehler wie einer unerreichbaren Datenbank überhaupt noch den Versuch zu unternehmen, irgendwie weiterzumachen?

        Auf der anderen Seite: Wenn die Abfrage der Datenbank absolut optional ist, weil damit nur irgendein dekoratives Widget in die Seite eingebaut wird, warum ist das Werfen der Exception dann überhaupt für die Seitendarstellung relevant? DB-Abfrage versuchen, eventuelle Exception fangen, das definierte Ersatzergebnis zurückgeben (leeres Array, NULL, Leerstring), und an der Stelle, wo es zur Ausgabe kommen soll, wird dieses Ergebnis zu irgendeiner alternativen Reaktion führen: Entweder wird garnichts angezeigt, oder ein Text wie "Keine Ergebnisse gefunden" oder "Datenbank im Moment nicht erreichbar".

        [...]

        1. Exceptions sind, wenn man es böse betrachtet, eine moderne Form von GOTO. Der normale Programmablauf wird verlassen, und im catch-Teil weitergemacht. Deshalb sollte man dieses Mittel eher sparsam einsetzen, insbesondere nicht zur Steuerung des Programmflusses mißbrauchen.

          Zugegeben, ich verwende sie zur Programmsteurung. Finde ich aber auch nicht so merkwürdig. Wenn ich eine Funktion habe, die mit einer Datei allerhand Dinge anstellt und ich als erstes im try-Block mal habe if(!file_exists($file)) throw new Exception("File not found.", 1); und damit der weitere Ablauf der Funktion verhindert wird, ist das doch mehr als gewünscht. Oder soll ich jedesmal tolle if-Blöcke bauen um bis zur 14-ten Verschachtelung zu kommen?

          Weil du es gerade fragst: Nein, Verschachtelungen über Ebene 5 hinaus sind auch böse.

          Und zum Zweiten: Warum hast du in der Funktion überhaupt ein try/catch? Das finde ich schon falsch. Entweder Code ruft innerhalb von try diverse Funktionen auf, die IHRERSEITS eventuell Exceptions werfen und damit den Sprung in den (evtl. passenden, vgl. Exception-Typen oben) catch-Block veranlassen, oder es wird Code ausgeführt, der seinerseits Exceptions wirft - dann steckt der aber eben gerade nicht in einem try/catch.

          In der Summe kommt es natürlich aufs Gleiche raus: In der äußeren Funktion wird der try-Block betreten, darin eine Funktion aufgerufen, die eine Exception wirft, und schon ist man direkt im catch-Block - warum ist dann der Funktionsaufruf dazwischen nötig, man könnte doch auch alles in eins zusammenfassen?

          Der Unterschied ist der Scope: Wenn die äußere Funktion try/catch macht, dann erwartet sie eventuell auftretende Exceptions, weiß aber nicht genau, ob und wo innerhalb der aufgerufenen Funktionen diese auftreten könnten. "Irgendwo" ist keine sehr präzise Angabe, und auch gar nicht nötig, denn unter dem Strich bedeutet eine Exception immer: "Die aufgerufene Funktion wird das vereinbarte Ergebnis nicht liefern."

          Umgekehrt bedeutet das Werfen in der inneren Funktion: "Ich bin auf ein unlösbares Problem gestoßen, habe aber keine Ahnung, wie wichtig das im Moment gerade für meinen aufrufenden Code ist - ich informiere ihn mit einer Exception".

          Wenn du aber innerhalb von try eine Exception wirfst, um in den catch-Block derselben Funktion "weiter unten" zu gelangen, ist das erstmal ein Mißbrauch von Exceptions zur Programmflusssteuerung. Für sowas gibt es ehrlichere Methoden.
          [...]

          Im Prinzip gibt es nur zwei Möglichkeiten, mit Exceptions umzugehen: Entweder, der aufrufende Code einer exceptionwerfenden Funktion kann die Exception fangen, weil er auch mit dem Fehlerfall umzugehen weiß. Das kann beispielsweise in der Bereitstellung eines passenden Ersatzes münden (z.B.: getArrayOfDatabases() -> DbUnavailableException -> catch: return array()).

          Alternativ fängt man die Exception eben gerade nicht, sondern lässt sie ganz bis nach "oben" durchschlagen - was dann natürlich nicht ganz so elegant zu einer Fehlermeldung führen würde, weshalb es eine gute Idee ist, auf oberster Codeebene ein pauschales Catch für alle Exceptions einzufügen, welcher den HTTP-Status auf 500 "Internal server error" setzt und dann noch eine hübsche Fehlerseite ausgibt.

          • Sven Rautenberg

          Je nach Lage fange ich sie entweder unmittelbar oder lasse sie nach oben durchschlagen.

          Vielen Dank für Deine kompetente Hilfe. Bitte verstehe aber, dass ich nicht verstehen kann, wieso das immer so schnippisch ablaufen muss. Ich bin in der Situation des Unwissenden nur auf der Suche nach Informationen. Ich bin nicht faul oder dumm. Ich schätze Deine Hilfe, aber ich schätze nicht diese demotivierenden Kommentare "grausig", "grausame schlecht",...

          Ich kritisiere deinen Code. Ich kritisiere nicht dich. Insbesondere greife ich dich nicht persönlich an. Den Einsatz von starken Worten zur Dramatisierung der Beschreibung mögest du bitte entschuldigen, oder großzügig darüber hinwegsehen, aber es geht leider nicht immer im Weichspülmodus ab.

          Wenn du mir Adjektive mit ähnlich prägnanter Bedeutung und ebengleicher Kürze nennst, die freundlichere Wirkung im Text entfalten, werd' ich's damit gern beim nächsten Mal probieren. ;)

          - Sven Rautenberg

        2. Hallo hallo,

          zunächst möchte ich mich für mein gestriges Posting entschuldigen und Dir für Deine heutige Antwort sehr danken.

          Entschuldigung auch für das falsche Zitat, war als Witz gedacht :)

          Nun zu meinem Problem. Ich sortier mal die Antwort etwas um. Bitte nicht als Manipulationsversuch werten, auch wenn deine Intention eine andere war.

          Letztendlich hängt hier eben alles an der Frage: Was soll deine Softwarelösung dir bringen, und wärst du bereit, deine Voreingenommenheit zugunsten deiner aktuellen Lösung zu überwinden?

          JAAAAa, ich bin bereit meine Voreingenommenheit zu überwinden. Sonst wäre ich nicht bis hier gekommen (Stichwort OOP  :)

          Bei den Exceptions ist es auch so, dass man erst sagt: "Kram, brauch ich nicht" und sie dann vermutl. etwas inflationär einsetzt. Vielleicht hast Du damit recht:

          Vermutlich wirfst du an zuvielen Stellen Exceptions.

          Ok. Was soll meine Softwarelösung bringen? Recht hast Du, dass die Klassen ja nicht unabhängig voneinander sind, wenn ich im Site-Constructor die DB-Klasse instantiiere.

          Anders Beispiel:
          ich habe eine Klasse gebaut, die aus einer ASCII-Datei ein Objekt erstellt. Diese Ascii-Datei ist eine Art Steuerdatei um Einstellungen (Klimamodell) über unterschiedliche Plattformen und Programmiersprachen auszutauschen. Sie ist untergliedert durch Steuerzeichen, wie "#", "##", "&", EOL,...
          Es ist ein Austauschformat, was wir definiert haben.

          Die Klasse liest die Datei und erstellt ein php-Objekt daraus. Ebenso ist es fähig, diese Datei wieder zu schreiben. Manipulieren einer Steuerdatei würde es treffen. DIESE Klasse soll standalone fähig sein, da die spätere Verwendung in meinem CMS nicht zwingend ist. Sie liest also die ASCII Datei und prüft auf um die 40 Konsistenzfehler (neben Datei vorhanden auch: Anzahl Blöcke wie erwartet?, Anzahl Spalten in jeder Zeile gleich?, Zelle enthält Wert/Array?). Wenn eine(!) Exception anspringt wird die Eigenschaft des Objektes mit den Daten gelöscht und die Exception landet im ExceptionHandler(). Von dort delegiere ich sie an meine Site. Ich schreibe in den ExceptionHandler() groß rein:

          "Achtung: hier bitte persönliche Einstellungen vornehmen, blabla..."

          Ich habe das so gemacht, wie es da stand mit GLOBAL $Site;

          Der Vorteil: die Klasse kann jeder verwenden und muss nur an einer einzigen Stelle (im ExceptionHandler()) Änderungen vornehmen. Notfalls liefere ich die Klasse aus mit echo $e; im ExceptionHandler().

          Das bedeutet, dass du es für möglich erachtest, dass sich beim Aufrufen deines Skriptes mehrere Exceptions nacheinander ansammeln. Ist das sinnvoll? Tritt sowas in der Realität tatsächlich auf?

          Mehrere Exceptions dieser Klasse sind nicht zu erwarten. Mehrere Exceptions für einen Request aber schon, wenn andere Module/Klassen auch Exceptions an die Site delegieren.

          Diese Methode funktioniert nun nicht im Falle der DB, die bereits im Constructor der Site instantiiert wird, da wie gesagt das Objekt $Site erst nach dem Konstruktor zu existieren scheint. Logisch. Nach meinem bisherigen Fahrplan komme ich also nicht weiter.

          Momentan (nach dem gestrigen Posting) löse ich es nun so, dass die DB die Exception wirft und sie vom Konstruktor der Site gefangen wird.

          Das verstehe ich nicht ganz:

          Gibst du der Site im Konstruktor oder in einer separaten Methode ein Datenbankobjekt zur Verwendung, dann muss die Datenbankklasse nicht mehr zwingend "DB" heißen, sie muss nur noch sämtliche Methoden implementieren, die Site aufruft. Und um das sicherzustellen, wird man sehr wahrscheinlich ein Interface definieren, welche als Kontrollinstanz dafür sorgt, dass sämtliche Methoden, die zur Verfügung gestellt werden müssen, auch implementiert sind. Umgekehrt dokumentiert das Interface, welche Methoden man überhaupt benutzen darf, bzw. auf welche Methoden man sich verlassen kann.

          AUS DEINEM ANDEREN POSTING:

          Und zum Zweiten: Warum hast du in der Funktion überhaupt ein try/catch? Das finde ich schon falsch. Entweder Code ruft innerhalb von try diverse Funktionen auf, die IHRERSEITS eventuell Exceptions werfen und damit den Sprung in den (evtl. passenden, vgl. Exception-Typen oben) catch-Block veranlassen, oder es wird Code ausgeführt, der seinerseits Exceptions wirft - dann steckt der aber eben gerade nicht in einem try/catch.

          Ich habe in public deklarierten Methoden einen eigenen try-catch-Block. Ansonsten stets darauf geachtet, dass in der aufrufenden Methode ein solcher steht.

          Wenn du aber innerhalb von try eine Exception wirfst, um in den catch-Block derselben Funktion "weiter unten" zu gelangen, ist das erstmal ein Mißbrauch von Exceptions zur Programmflusssteuerung. Für sowas gibt es ehrlichere Methoden.

          Also zu meiner Verteidigung: ich verwende die Exceptions nie, um in den catch Block weitere Anweisungen zu verarbeiten. Dort steht stets nur
          $this->ExceptionHandler($e);. :)

          Nun noch ein paar weitere Zitate..............................

          Das widerspricht sich meiner Meinung nicht: Meine Aussage{

          Das heisst, jede Klasse kümmert sich erstmal selbst um seine Exceptions.

          } Deine Aussage {

          Das ist halt schon genau das, was ich an deinem Konzept nicht mag. Eine Exception wird geworfen, weil ein nicht umgehbares Problem aufgetreten ist, und der Code an dieser Stelle nicht sinnvoll im vorgesehenen Fahrplan weitermachen kann.

          Das wiederum bedeutet, dass die aufgerufene Methode eben gerade NICHT das gewünschte Ergebnis zu liefern imstande ist. Der Codeteil, der diese Methode aufrief, kann also ebenfalls nicht wie geplant weitermachen.

          }

          Die Gestaltung von Exception-Klassen und deren Vererbung ist ein gar nicht mal simples Thema. Im Gegensatz zu anderen Klassen müssen alle Exceptions, die man selbst gestaltet, von der in PHP integrierten Klasse "Exception" erben.

          Ich habe keine Exception-Klasse definiert und möchte das auch nicht. Ich nutze von den Exceptions nur die Message und den Errorcode.

          Sehr interessant. Aber leider noch zu hoch für mich...

          Es gibt Leute, die hiervon ausgehend Exception-Typen basierend auf der Art des aufgetretenen Problems werfen, beispielsweise die InvalidArgumentException für fehlerhafte Parameter (als Kind der "LogicException"), und die UnexpectedValueException für das Auftreten unerwarteter Werte (als Kind der "RuntimeException"). Dieser Ansatz versucht, die auftretenden Probleme nach Typen zu gruppieren (die genannten Exceptions sind in PHP über die Standard PHP Library "SPL" verfügbar), so dass man in der Lage ist, je nach Fehlertyp die eine oder andere Fehlerbehebung zu versuchen, oder dem User eine entsprechende Fehlermeldung zu übermitteln.

          Das klingt äußerst interessant. Kennst Du einen Beitrag im Internet (Tutorial, Ähnliches) wo dieses Prinzip vorgestellt wird. Ich verstehe es näcmlich leider nicht ganz:

          Mein persönlicher Favorit hingegen ist, dass Exceptions nach den Klassen gruppiert vererbt werden, die thematisch als Module in einer Software auftreten: Eine Datenbankklasse "Db" wirft eine Db_Exception. Das darin enthaltene Modul Db_Connector wirft eine Db_Connector_Exception. Der Db_Connector_Mysql wirft eine Db_Connector_Mysql_Exception. Und die Exceptions erben in einer Kette jeweils voneinander. Damit wäre man in der Lage, auf einer sehr hohen Programmebene beim Aufruf der Methoden der Klasse "Db" mit try/catch alle Db_Exceptions zu fangen (man weiß dann, dass irgendwas in der Datenbankabfrage schief gelaufen ist und man kein Ergebnis bekommt), inklusive aller Db_Connector_Exception (weil z.B. der konfigurierte Datenbanktreiber nicht geladen werden konnte) und aller Db_Connector_Mysql_Exception (z.B. für fehlerhafte SQL-Querys).

          Innerhalb der Db-Klasse würde man mit try/catch alle Db_Connector_Exception fangen können, in Db_Connector wären das Db_Connector_Mysql_Exception, etc... Wobei das Fangen nur Sinn ergibt, wenn man ein passendes Alternativprogramm anbieten kann.

          1. Moin!

            Anders Beispiel:
            ich habe eine Klasse gebaut, die aus einer ASCII-Datei ein Objekt erstellt. Diese Ascii-Datei ist eine Art Steuerdatei um Einstellungen (Klimamodell) über unterschiedliche Plattformen und Programmiersprachen auszutauschen. Sie ist untergliedert durch Steuerzeichen, wie "#", "##", "&", EOL,...
            Es ist ein Austauschformat, was wir definiert haben.

            Die Klasse liest die Datei und erstellt ein php-Objekt daraus. Ebenso ist es fähig, diese Datei wieder zu schreiben. Manipulieren einer Steuerdatei würde es treffen. DIESE Klasse soll standalone fähig sein, da die spätere Verwendung in meinem CMS nicht zwingend ist. Sie liest also die ASCII Datei und prüft auf um die 40 Konsistenzfehler (neben Datei vorhanden auch: Anzahl Blöcke wie erwartet?, Anzahl Spalten in jeder Zeile gleich?, Zelle enthält Wert/Array?). Wenn eine(!) Exception anspringt wird die Eigenschaft des Objektes mit den Daten gelöscht und die Exception landet im ExceptionHandler(). Von dort delegiere ich sie an meine Site. Ich schreibe in den ExceptionHandler() groß rein:

            "Achtung: hier bitte persönliche Einstellungen vornehmen, blabla..."

            Ich habe das so gemacht, wie es da stand mit GLOBAL $Site;

            Der Vorteil: die Klasse kann jeder verwenden und muss nur an einer einzigen Stelle (im ExceptionHandler()) Änderungen vornehmen. Notfalls liefere ich die Klasse aus mit echo $e; im ExceptionHandler().

            Das ist genau der Punkt. Wenn deine Klasse wirklich gut designt wäre, müsste man sie nicht im Code an die eigenen Erfordernisse anpassen. Du zwingst den Anwender deiner Klasse, deren Code so zu ändern, dass das Behandeln von Problemfällen du der Infrastruktur der jeweiligen Anwendung passt.

            Und das Grundsatzproblem bleibt: Innerhalb deiner Klasse wirfst du mit Exceptions, aber nach außen dringt davon nichts. Dabei kann diese Klasse gar nicht wissen, wie ihr Umfeld mit einem Problem arbeiten will.

            Und aufgrund des in der Klasse global gültigen ExceptionHandlers gibt es auch keine Möglichkeit, das Behandeln von Problemen individuell für jeden Funktionsaufruf (derselben Funktion!) zu handhaben.

            Das mag dir im Moment alles nicht so ganz bewußt sein, weil du selbst ja dein eigener Anwender der Klasse bist, und das Ändern des Codes das Leichteste von der Welt ist. Das ändert aber nichts an der Designproblematik. :)

            So wär's besser:

            class IrgendwasKlima {  
              
              public function einlesenOderSo() {  
                // fail:  
                throw new Exception('Das ist jetzt blöd...');  
              }  
            }  
              
            class Site {  
              
              private function gefangeneExceptionEinlagern(Exception $exception) {  
                $this->_exceptionLager[] = $exception;  
              }  
              
              
              public function foobar() {  
                // Irgendwoanders im Code deiner Site:  
                try {  
                  $klima = new IrgendwasKlima;  
                  $klima->einlesenOderSo();  
                }  
                catch (Exception $e) {  
                  $this->gefangeneExceptionEinlagern($e);  
                }  
              }  
            }
            

            Diese Art hat diverse Vorteile:

            1. Deine Klimaklasse kommuniziert mit ihrer Umwelt über ein eindeutiges, verlässliches Medium: Das Werfen einer Exception ist die standardisierte Methode, um "Game Over" zu signalisieren.

            2. Der Verwender deiner Klimaklasse kann unabhängig von dieser bestimmen, was mit einer Exception passieren soll. Einlagern und Ansammeln, oder direkt im Einzelfall behandeln - vollkommen egal.

            3. Die Klimaklasse braucht keine Referenz auf das globale Site-Objekt.

            4. Das Fangen von Exceptions funktioniert auch, wenn das im Konstruktor der Klimaklasse passiert.

            5. Die Klimaklasse muss nicht verändert werden, wenn sich die Anforderungen für das Behandeln von Exceptions in der Site-Klasse verändert oder in einer anderen Klasse ganz andere Anforderungen bestehen.

            Wenn du keine Lust hast, die ganzen try/catch-Blöcke aus den bisherigen Klassen zu entfernen, dann änderst du deinen globalen ExceptionHandler auf:

            private function ExceptionHandler($e){  
              throw $e  
            }
            

            Das Setzen eines Exception-Handlers für nicht gefangene Exceptions wäre zu überlegen. Ich bin halt skeptisch, dass es sinnvoll ist, mehr als eine Exception pro Aufruf ansammeln zu lassen, um die man sich nicht sinnvoll im Code kümmern konnte.

            Das verstehe ich nicht ganz:

            Gibst du der Site im Konstruktor oder in einer separaten Methode ein Datenbankobjekt zur Verwendung, dann muss die Datenbankklasse nicht mehr zwingend "DB" heißen, sie muss nur noch sämtliche Methoden implementieren, die Site aufruft. Und um das sicherzustellen, wird man sehr wahrscheinlich ein Interface definieren, welche als Kontrollinstanz dafür sorgt, dass sämtliche Methoden, die zur Verfügung gestellt werden müssen, auch implementiert sind. Umgekehrt dokumentiert das Interface, welche Methoden man überhaupt benutzen darf, bzw. auf welche Methoden man sich verlassen kann.

            Vielleicht geht es mit Code deutlicher:

            // Das Interface definiert, welche Funktionen alle Klassen anbieten MÜSSEN, wenn sie das Interface implementieren:  
              
            interface Db_Queryfunktionen_Interface {  
              public function connect();  
              public function query($sql);  
            }  
              
            // Beispielhaft eine Mysql-Klasse  
            class Db_Mysql implements Db_Queryfunktionen_Interface {  
              public function connect() {  
                $this->db = new mysqli(...);  
              }  
              public function query($sql) {  
                return $this->db->query($sql);  
              }  
            }  
              
            // Beispielhaft eine Oracle-Klasse  
            class Db_Oracle implements Db_Queryfunktionen_Interface {  
              public function connect() {  
                $this->oracle = oci_connect(...);  
              }  
              public function query($sql) {  
                $stmt = oci_parse($this->oracle, $sql);  
                return oci_execute($stmt);  
                // Code hier nicht auf die Goldwaage legen von wegen Korrektheit eines Oracle-Querys. ;)  
              }  
            }  
              
            // Und nun die große Magie:  
            class IrgendwasMitDatenbank {  
              // Der Konstruktor fordert eine Klasse, die das Interface implementiert.  
              public function __construct(Db_Queryfunktionen_Interface $db) {  
                $this->_db = $db;  
              }  
              public function getIrgendwasFromDb() {  
                // Weil das DB-Objekt garantiert die zwei Methoden "connect" und "query" implementiert,  
                // kann man diese Methoden garantiert aufrufen, egal welche Datenbank man wirklich nutzt.  
                $this->_db->connect();  
                $this->_db->query("SELECT * FROM table");  
              }  
            }  
              
            // Objekt mit Oracle-Datenbank verbinden  
            $obj = new IrgendwasMitDatenbank(new Db_Oracle);  
            // Funzt ;)  
            
            

            Wenn du aber innerhalb von try eine Exception wirfst, um in den catch-Block derselben Funktion "weiter unten" zu gelangen, ist das erstmal ein Mißbrauch von Exceptions zur Programmflusssteuerung. Für sowas gibt es ehrlichere Methoden.
            Also zu meiner Verteidigung: ich verwende die Exceptions nie, um in den catch Block weitere Anweisungen zu verarbeiten. Dort steht stets nur
            $this->ExceptionHandler($e);. :)

            Nicht sehr kreativ. ;) Exceptions immer gleich zu behandeln, nämlich irgendwo anzulagern, erlaubt ja nicht, den Fehlerfall bestmöglich abzumildern, wenn das möglich ist.

            Die Gestaltung von Exception-Klassen und deren Vererbung ist ein gar nicht mal simples Thema. Im Gegensatz zu anderen Klassen müssen alle Exceptions, die man selbst gestaltet, von der in PHP integrierten Klasse "Exception" erben.
            Ich habe keine Exception-Klasse definiert und möchte das auch nicht. Ich nutze von den Exceptions nur die Message und den Errorcode.

            Ist in Ordnung. Aber es bleibt die Frage, ob "nur" Errorcode nicht zu unbeschreibend ist. Im Prinzip kannst du beim Werfen der Exception ja beliebige Codes eingeben, die mit der Message nichts zu tun haben müssten.

            Wenn du dir eigene Exception-Klassen definierst, kannst du den Code in jeder eigenen Exception fest definieren, erhältst zusätzlich noch einen netten, beschreibenden Namen, könntest die Message um beschreibende Standardtextkomponenten anreichern etc.

            Und erst damit kriegst du auch die Möglichkeit, auf unterschiedliche Exceptions mit unterschiedlichen catch-Blöcken zu reagieren. Das mag überdimensioniert klingen, aber ich finde es ziemlich gut, wenn man das einsetzen kann, wenn man es braucht.

            Es gibt Leute, die hiervon ausgehend Exception-Typen basierend auf der Art des aufgetretenen Problems werfen, beispielsweise die InvalidArgumentException für fehlerhafte Parameter (als Kind der "LogicException"), und die UnexpectedValueException für das Auftreten unerwarteter Werte (als Kind der "RuntimeException"). Dieser Ansatz versucht, die auftretenden Probleme nach Typen zu gruppieren (die genannten Exceptions sind in PHP über die Standard PHP Library "SPL" verfügbar), so dass man in der Lage ist, je nach Fehlertyp die eine oder andere Fehlerbehebung zu versuchen, oder dem User eine entsprechende Fehlermeldung zu übermitteln.

            Das klingt äußerst interessant. Kennst Du einen Beitrag im Internet (Tutorial, Ähnliches) wo dieses Prinzip vorgestellt wird. Ich verstehe es näcmlich leider nicht ganz:

            Mein persönlicher Favorit hingegen ist, dass Exceptions nach den Klassen gruppiert vererbt werden, die thematisch als Module in einer Software auftreten: Eine Datenbankklasse "Db" wirft eine Db_Exception. Das darin enthaltene Modul Db_Connector wirft eine Db_Connector_Exception. Der Db_Connector_Mysql wirft eine Db_Connector_Mysql_Exception. Und die Exceptions erben in einer Kette jeweils voneinander. Damit wäre man in der Lage, auf einer sehr hohen Programmebene beim Aufruf der Methoden der Klasse "Db" mit try/catch alle Db_Exceptions zu fangen (man weiß dann, dass irgendwas in der Datenbankabfrage schief gelaufen ist und man kein Ergebnis bekommt), inklusive aller Db_Connector_Exception (weil z.B. der konfigurierte Datenbanktreiber nicht geladen werden konnte) und aller Db_Connector_Mysql_Exception (z.B. für fehlerhafte SQL-Querys).

            Innerhalb der Db-Klasse würde man mit try/catch alle Db_Connector_Exception fangen können, in Db_Connector wären das Db_Connector_Mysql_Exception, etc... Wobei das Fangen nur Sinn ergibt, wenn man ein passendes Alternativprogramm anbieten kann.

            Ich fand ganz spontan das hier: http://www.php.de/tutorials/45124-php-exceptions-teil-1-a.html

            - Sven Rautenberg

            1. Vielen Dank für die vielen Tipps. Es war sehr lehrreich und ich habe es zum größten Teil bereits umgesetzt.

              Eine Frage noch, zu einer evtl. Exceptionklasse.

              Wenn du dir eigene Exception-Klassen definierst, kannst du den Code in jeder eigenen Exception fest definieren, erhältst zusätzlich noch einen netten, beschreibenden Namen, könntest die Message um beschreibende Standardtextkomponenten anreichern etc.

              Und erst damit kriegst du auch die Möglichkeit, auf unterschiedliche Exceptions mit unterschiedlichen catch-Blöcken zu reagieren. Das mag überdimensioniert klingen, aber ich finde es ziemlich gut, wenn man das einsetzen kann, wenn man es braucht.

              Ich habe auch schon mal kurz mit dem Gedanken gespielt, die Codes (nr.) zu verwenden. Ich kam aber darauf, dass es dann sehr wichtig ist, dass jede Nummer nur exakt einmal in dem ganzen Projekt vorkommt, sonst gibt es ja richtige Falschmeldungen. Ist das nicht wahnsinnig schwer zur warten? Ich bin sicher, dass es da nochwas gibt, was ich nicht weiß.

              Grüße
              Dred

              1. Moin!

                Eine Frage noch, zu einer evtl. Exceptionklasse.

                Wenn du dir eigene Exception-Klassen definierst, kannst du den Code in jeder eigenen Exception fest definieren, erhältst zusätzlich noch einen netten, beschreibenden Namen, könntest die Message um beschreibende Standardtextkomponenten anreichern etc.

                Und erst damit kriegst du auch die Möglichkeit, auf unterschiedliche Exceptions mit unterschiedlichen catch-Blöcken zu reagieren. Das mag überdimensioniert klingen, aber ich finde es ziemlich gut, wenn man das einsetzen kann, wenn man es braucht.

                Ich habe auch schon mal kurz mit dem Gedanken gespielt, die Codes (nr.) zu verwenden. Ich kam aber darauf, dass es dann sehr wichtig ist, dass jede Nummer nur exakt einmal in dem ganzen Projekt vorkommt, sonst gibt es ja richtige Falschmeldungen. Ist das nicht wahnsinnig schwer zur warten? Ich bin sicher, dass es da nochwas gibt, was ich nicht weiß.

                Wenn es darum geht, festzustellen, woher eine Exception kommt: Die Exception bringt den gesamten Stacktrace sowie die Datei und Zeilennummer mit, in der sie geworfen wurde.

                Einen EIGENEN Error-Code habe ich in meinen Exceptions noch nie verwendet. Wohl aber habe ich den Error-Code von MySQL dort schon reingetan: Mysql-Funktionsaufruf liefert einen Fehler, mein DB-Layer wirft eine Exception (und zwar eine Db_Exception) mit Error-Message und Error-Code der MySQL-Fehlermeldung.

                Bonus-Gimmik: Weil das Erkennen von "Duplicate Key Violation" in der DB als Besonderheit und "ist gar kein böser Fehler" behandelt werden musste, hat die Db_Exception eine neue Methode "isDuplicateKey()" bekommen, die den gesetzten Fehlercode mit dem für diesen Duplicate-Key-Fehler vergleicht und ggf. true zurückgibt. Dadurch kann ich erstmal ein INSERT versuchen und ohne viel Brimborium im catch mit if ($e->isDuplicateKey()) ein nachträgliches UPDATE hinterherschieben.

                - Sven Rautenberg

  2. hi,

    Einen solchen ExceptionHandler() habe ich in vielen Klassen eingebaut und normalerweise klappt das mit dem Global und dem Anhängen der Exception an den Array. Nur nicht, wenn es im Konstruktor der Site geschieht. Logisch: denn dann ist das Objekt $Site noch nicht vorhanden.

    Für meinen Teil mache ich sowas mit Perl, aber die Sachlage ist ähnlich, so sorge ich dafür, dass das $Site-Objekt in jedem Fall erstellt wird um die Ausgabe einer Response sicherzustellen. Wenn bereits im Konstruktor eine Exception auftritt, beispielweise ein Fehlschlagen der DB-Verbindung, halte ich das ein einer Eigenschaft des $Site-Objekts fest, bei mir in {ERR}, was bei mir ebenfalls ein Array ist.

    Treten im Verlauf der Anwendung weitere Exceptions auf, erfolgt ein Push auf das Error-Attribut (Array: Exceptions). Mein 'Trick' besteht nun darin, am Ende, wenn die Response ausgegeben werden soll, nicht einfach den Body auszugeben, sondern eine spezielle Methode (getbody) aufzurufen: Diese Methode getbody wirft einen Blick in das Exception-Array, liegen darin Einträge vor, wird der Body komplett ausgetauscht, auch wenn dieser bis dahin einen anderen Inhalt hat.

    Hotti

  3. Hallo,

    Könnt ihr mir helfen. Ich programmiere noch nicht lange OO.

    Also ich habe bisher nur gesehen: myExceptionClass extends Exception

    Beschäftige Dich doch mal mit einem der PHP-Frameworks und schaue Dir an, wie die das machen. Zend, Yii, Cake, Symfony. Alles OO, alles MVC.

    Gruß

    jobo