Facebook
Twitter
Google+
Kommentare
25

Exceptions im Destruktor

Kater von gestern überstanden? Ja? Dann kann es ja weiter gehen … Man lernt nie aus. Diese These hatte ich ja glaube ich schon mal aufgestellt, aber heute wurde sie mal wieder bestätigt. Ich kam heute endlich mal wieder zum Programmieren und wie das nun mal ist, passieren da die komischsten Dinge. Heute hat mein Programm auf einmal gestreikt und ich hatte keine Ahnung warum. Viel geändert seit dem letzen F5 hatte ich nämlich nicht.

Die Fehlermeldung, die kam konnte ich auch nicht so recht interpretieren. Fatal error: Exception thrown without a stack frame in Unknown on line 0. Nachdem ich weiß, warum dieser Fehler auftrat, weiß ich auch, warum ich sie nicht interpretieren konnte. Sie macht keinen Sinn! Seid mal ehrlich, was würdet ihr denken, wenn auch euer Programm so um die Ohren fliegt? Ich hoffe mal ein großes Häääää?!? Zumindest ging es mir so. Was macht man in so einem Fall? Google fragen. Hat nicht lange gedauert, da wusste ich die Lösung.

PHP gibt diesen Laut von sich, falls man versucht eine Exception im Destruktor zu werfen. Ich war mir zu dem Zeitpunkt noch gar nicht wirklich bewusst, dass ich das mache, aber eine von den Methoden, die ich verwendet habe, hat dies leider getan und ich habe sie nicht rechtzeitig gefangen. Die Fehlermeldung ist ein klares WTF. Dass PHP so eine Exception nicht zulässt ist vielleicht gar nicht so verkehrt. Hier aber erstmal schnell ein Beispiel, damit ihr es zuhause auch probieren könnt.

  class MyClass {
    function __destruct(){
      throw new Exception('fail');
    }
  }
  $n = new MyClass();

Aber warum zum Teufel sollte ich keine Exceptions werfen können? Meine Theorie ist, es macht keinen Sinn. Der Destruktor wird vom Garbage Collector aufgerufen. Wann dies passiert, ist allein PHP überlassen (zumindest in den meisten Fällen). Ich kann zwar ein unset auf das Objekt machen, aber ob es dann in dem Augenblick auch vom GC in den Müll geworfen wird ist fraglich. Wie soll ich also diese Exception fangen, wenn ich nicht weiß, wo sie geworfen wird. Ein Handling dieser Ausnahme ist also nicht möglich. Ich muss dazusagen, dass ich mir nicht sicher bin, ob ich die PHP Internals genau genug kenne, um diese Theorie zu halten, aber vielleicht kennt ja jemand von euch die wahren Gründe. Könnte mir auch vorstellen, dass PHP da einfach beim Ausführen abschmiert und deswegen diese fiese Fehlermeldung kommt, da es aber nicht verkehrt ist, dass man mit dem Programm aufhört, hat man es gelassen.

Ich bin mir auch gar nicht so sicher, ob PHP 5.3 mit dem gleichen Fehler rausknallt. Irgendwo habe ich was gelesen, dass sich da was verändert hat.

Über den Autor

Nils Langner

Nils Langner ist der Gründer von "the web hates me" und auch der Hauptautor. Im wahren Leben leitet er das Qualitätsmanagementteam im Gruner+Jahr-Digitalbereich und ist somit für Seiten wie stern.de, eltern.de und gala.de aus Qualitätssicht verantwortlich. Nils schreibt seit den Anfängen von phphatesme, welches er ebenfalls gegründet hat, nicht nur für diverse Blogs, sondern auch für Fachmagazine, wie das PHP Magazin, die t3n, die c't oder die iX. Nebenbei ist er noch ein gern gesehener Sprecher auf Konferenzen. Herr Langner schreibt die Texte über sich gerne in der dritten Form.
Kommentare

25 Comments

  1. Wirklich interessante Frage.
    Meine Argumenten sind eher allgemeiner Natur und lassen sich gut in folgendem Zitat aus dem C-Umfeld zusammenfassen:
    „Beware of throwing exceptions from a destructor
    The most plausible way to report a failure during object construction is by throwing an exception. However, this is not recommended for destructors. The problem is that a destructor may be invoked automatically when an uncaught exception is thrown in its scope. If the called destructor invoked due to an exception also throws an exception, the result is an infinite recursion.“
    (http://www.devx.com/tips/Tip/12464)

    „Infinite recursion“ klingt jedenfalls suboptimal. Ob das bei php genauso ist, werde ich ja sicher gleich erfahren 😉

    Reply
  2. „von Nils Langner am 4. August 2009 “

    „Ich kam heute endlich mal wieder zum Programmieren“

    Wenn ich fragen darf, um wieviel Uhr begann der Porgrammiertag? 🙂
    Also mich würds morgends so früh nerven ne on Line 0 Meldung zu bekommen 🙂

    Reply
  3. Gerade getestet auf PHP 5.3 dem Final Preview Package für Debian Lenny von Dotdeb.org.

    „Fatal error: Exception thrown without a stack frame in Unknown on line 0“

    Scheint sich dahingehend also nichts geändert zu haben.

    Reply
  4. @Christian: Da muss ich dich leider enttäuschen. Den Artikel hatte ich vorgeschrieben 🙂 Wenn ich jeden morgen um 6 Uhr anfangen würde zu programmieren, würde mich meine Freundin (verständlicherweise) töten.

    Reply
  5. Update:
    Der Destruktor wird wohl beim unset( ) direkt aufgerufen. Also nicht vom GC oder der GC wird sofort drübergejagt.

    Destructors are functions that are called automatically when an object is destroyed, either with unset() or by simply going out of scope.

    class A {
      public function __destruct() {
        throw new Exception('Destruction!');
      }
    }
    
    function test() {
      new A();
    }
    
    try {
      test();
    }
    catch(Exception $ex) {
      echo 'Caught '.$ex->getMessage();
    }
    Reply
  6. Das hatte ich vor ein paar wochen auch, und sogar länger recherchiert warum das so ist. hab auch ne relative gute und (zur damaligen zeit) einleuchtende erklärung gefunden. aber typisch, ich find den bookmark dazu wieder nicht *grmbl*

    AFAIR war es so, das werfen der Exception ist nicht das problem, aber das fangen (es ist möglich einen try/catch im destruct zu haben), da ja der GC ausserhalb deines Scopes läuft. (jemand mit mehr php internal erfahrung kann das sicher besser erklären)

    (aber nils, das hat dir doch phpDesaster eh im phpforum erklärt 🙂
    http://www.phpforum.de/forum/showthread.php?t=243078

    my2c
    ludwig

    Reply
  7. Hmm, Nils, hattest Du die Meldung nicht schon vor ein paar Tagen? Ich kann mich daran erinnern, das was dazu durch Twitter gerauscht ist. 🙂

    Reply
  8. @Ralf: Glaubst du wirklich, dass die Artikel immer an den Tagen geschrieben werden an denen sie live gehen? Oft schreibe ich an ’nem Samstag 2 oder 3, dann hab ich die Woche über weniger Stress. Aber ich hab gehört, dass man mit mehr Co-Autoren da was dran ändern könnte 🙂 Wobei die jetzigen Co-Autoren echt gut sind!

    Reply
  9. Die selbe Fehlermeldung tritt übrigens auch auf, wenn im Exception Handler eine Exception geworfen wird.

    function exceptionHandler(Exception $e) {
        throw $e;
    }
    set_exception_handler('exceptionHandler');
    
    // Fatal error: Exception thrown without a stack frame in Unknown on line 0
    throw new Exception('Test');
    Reply
  10. Zitat:
    „Aber warum zum Teufel sollte ich keine Exceptions werden können?“

    Hehehe, ja warum nur? Frag Deine Freundin, die hat Dich ja schon gefangen 😉

    Reply
  11. @Nils: Was hälst du von einem kleinen „Print“-Symbol für eine Druckansicht über jedem Artikel? Ich entdecke in meinem Urlaub gerade den Charme von ausgedruckten phphatesme-Artikeln 🙂

    Reply
  12. @Nils: Zugegeben, war ein bescheidenes Wortspiel … aber manchmal springt einem so ein Unsinn einfach ins Auge bzw. in den Kopf 😉

    @Christian+Nils: Ein einfaches Print Stylesheet würde ja schon reichen, wo z.B. das Menü auf display:none gesetzt wird.

    Reply
  13. @Kevin + Christian: Da sprecht ihr meiner Freundin aus der Seele 🙂 Ihre Idee war aber ein schönes PDF und ich muss ehrlich sagen, dass mir das auch besser gefällt 🙂 Vielleicht kennt jemand von euch da ja ein schönes WordPress Plugin.

    Reply
  14. @Nils + Kevin: Das mit dem PDF hat wirklich mehr Charme als die einfache Lösung… Wenn mir ein Plugin über den Weg läuft oder ich eines Nachts eins geschrieben habe, komme ich darauf zurück…

    Vielleicht auch ein schöner, zukünftiger Artikel….

    Reply
  15. Die gleiche Fehlermeldung hatte ich neulich auch. Bin durch Googlen auf die Stelle gekommen. Ich habe mir eine eigene Klasse rund um PDO gestrickt und diese für einen einfacheren Zugriff in die Session legen wollen. Und immer beim Zuweisen des Objektes an $_SESSION[‚db‘] kam diese Exception. Der Tipp war, dass Klassen, die in die Session gelegt werden sollen, eine Funktion __sleep() definieren müssen. Hat bei mir aber auch nicht wirklich geholfen, ich habe dann auf die Klassenhaltung in der Session verzichtet. Aber auch bei mir war diese Meldung ein klassischer WTF-Moment …

    Reply
  16. das Problem tritt meines Wissens nur auf, wenn noch eine weitere Referenz auf das betreffende Objekt besteht, wie z.B. in self::$instance. Ein unset() im globalen Kontext löscht dann nur die Referenz, da der Destruktor ja erst dann aufgerufen wird, wenn die letzte Referenz auf das Objekt gelöscht wird, was in diesem Fall dann nach dem Skriptablauf vom GC erfolgt. Ist im Destruktor dann ein Aufruf an ein referenziertes Objekt enthalten (z.B. speichern via Db-Objekt) und wurde dieses Objekt bereits entsorgt, gibt es dann diesen Fehler.
    Grundsätzlich kann man diese Ausgabe unterdrücken, in dem man im Destruktor Exceptions abfängt und mit einem leeren catch-Block einfach ignoriert, allerdings kann man sich den Code im Destruktor dann auch gleich sparen, wenn man sich nicht darauf verlassen kann, dass er auch wirklich ausgeführt wird.
    Als Ergebnis könnte man festhalten, dass diese Meldung in der Regel einen Designfehler offenbart. Um diesen zu vermeiden, sollte man entweder im Destruktor keinen Objektkontext verwenden oder die Instanzen (inklusive aller Referenzen darauf) aktiv löschen.

    Reply

Leave a Comment.

Link erfolgreich vorgeschlagen.

Vielen Dank, dass du einen Link vorgeschlagen hast. Wir werden ihn sobald wie möglich prüfen. Schließen