Felix Riesterer: wie __destruct sinnvoll nutzen?

Liebe PHPler,

wer hat mit PHPs Objektorientierung genügend Erfahrung und kann mir sagen, ob ich das Prinzip richtig verstanden habe? Hier meine Mutmaßungen, um deren Bestätigung bzw. Widerlegung ich bitte:

#1
In einem Objekt als Eigenschaften vorhandene Stream-Resourcen "sterben" in dem Moment, wo __destruct aufgerufen wird. Im Code (besser "scope") dieser speziellen Funktion (alias Methode) habe ich keine wirklich existierenden Referenzen auf Stream-Resourcen mehr. Anscheinend hat es nur dann einen Sinn diese Methode zu notieren, wenn man im Rahmen der Vererbung eventuelle Elternobjekte "sterben lassen" möchte.

#2
Stream-Resourcen werden beim Zerstören des Objektes automatisch von PHP geschlossen. Entweder durch das explizite Aufrufen von __destruct(), oder durch ein unset($obj).

Bisherige Erkenntnisse:
Ich habe mir eine Funktion gebastelt, die die Resource korrekt schließt, um anschließend $this->__destruct() aufzurufen. War das so im Sinne des Prinzips?

Debug-Ausgaben liefern auch innerhalb des Codes von __destruct Eigenschaften des Objekts, auch der "file handles" (konkret: "Resource id #7"). Trotzdem scheitert dort ein Aufruf von fclose() mit der Fehlermeldung, es handle sich um keine gülte Stream-Resource. Daher mein Umweg über eine "vorgeschaltete" Methode.

Vielleicht noch ein Hinweis: PHP5.5.1

Liebe Grüße,

Felix Riesterer.

--
ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
  1. Tach!

    wer hat mit PHPs Objektorientierung genügend Erfahrung und kann mir sagen, ob ich das Prinzip richtig verstanden habe? Hier meine Mutmaßungen, um deren Bestätigung bzw. Widerlegung ich bitte:

    Ich habe noch nie das Bedürfnis gehabt, in PHP einen Destruktor einzusetzen. Deswegen hab ich damit auch keine näheren Erfahrungen.

    Ich habe mir eine Funktion gebastelt, die die Resource korrekt schließt, um anschließend $this->__destruct() aufzurufen. War das so im Sinne des Prinzips?

    Warum tust du dir den Aufwand an, wenn PHP sowieso alles schließt, wenn es fertig ist und du durch das explizite Schließen keinen Vorteil hast? "Der Ordnung halber" ist für mich kein ausreichender Grund, wenn PHP problemlos selbst für Ordnung sorgt.

    dedlfix.

    1. Lieber dedlfix,

      Warum tust du dir den Aufwand an, wenn PHP sowieso alles schließt, wenn es fertig ist und du durch das explizite Schließen keinen Vorteil hast? "Der Ordnung halber" ist für mich kein ausreichender Grund, wenn PHP problemlos selbst für Ordnung sorgt.

      danke.

      Liebe Grüße,

      Felix Riesterer.

      --
      ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
  2. Bisherige Erkenntnisse:
    Ich habe mir eine Funktion gebastelt, die die Resource korrekt schließt, um anschließend $this->__destruct() aufzurufen. War das so im Sinne des Prinzips?

    Andersrum wird ein Schuh draus. Zumindest bei mir :) Ich benutze den destructor für Dinge, dich ich ausgeführt haben will _am Ende_ des Scriptes _ohne_ den destructor explizit aufzurufen (also destructor definieren, aber nicht aufrufen). Im Destructor meines Site-Objects zum Beispiel schreibe ich noch ein paar Sessionvariablen...

    Cheers,
    Baba

    1. Tach!

      Im Destructor meines Site-Objects zum Beispiel schreibe ich noch ein paar Sessionvariablen...

      Warum erst bei der Destruktion? Was sind das für Daten, die zu dem Zeitpunkt noch unfertig rumliegen?

      dedlfix.

      1. hi,

        Tach!

        Im Destructor meines Site-Objects zum Beispiel schreibe ich noch ein paar Sessionvariablen...

        Warum erst bei der Destruktion? Was sind das für Daten, die zu dem Zeitpunkt noch unfertig rumliegen?

        Das ist z.B. interessant für Fehlermeldungen, die beim beenden noch passiert sind und auf der nächsten Seite ggf. ausgegeben werden sollten. Sicher nicht die beste Art, wenn im beenden noch Fehler passieren können, die später relevant sind, aber dennoch ein Fall, der vor dem Beenden noch nicht zu erkennen war. (Zum Debuggen von Laufzeiten der Scripte ist das auch nicht ganz unwichtig. Zumindes für Leute die viel mit dem __destruct arbeiten).

        .
        Gruß Niklas

        --
        Man muss nicht alles wissen, man sollte aber wissen, wo das nicht gewusste zu finden ist.
  3. hi,

    ich habe mich sehr lange das selbe gefragt.
    Nachdem ich aber einige Zeit lang eine Anwendung mit ca. 2000 Usern pro Tag hatte, wurde mir der Sinn leider schmerzlicher bewusst.

    PHP räumt zwar in der Tat selber auf, braucht aber grade in Verbindung mit Datenbanken häufig etwas zu lange. Dadurch kann es dazu kommen, dass zuviele Verbindungen offen sind und eventuelle LOCK befehle den rest aufhalten könnten.

    Mit der Methode kannst du dann explizit immer sagen, dass die Verbindung beendet werden soll.

    ---

    Ein weiterer von mir eingesetzter Punkt ist die kommunikation mit diversen anderen Programmen.
    Bei diesen habe ich eine Verbindung offen, die mehrfache Anfragen/Antworten erlaubt. Wie etwa auch bei SMTP. Für mein Programm im Hintergrund wäre das etwas doof, wenn es nicht informiert wird, dass der "Client" fertig ist. So muss ich mich als Programmierer aber auch nicht mehr drum kümmern, dass wirklich am ende der befehl "close" noch kommt. Das ist über meine Class bereits fest vorgegeben.

    ---

    Für Logging müsste das ganze übrigens auch funktionieren, da man so z.b. noch einen finalen "bin durch gelaufen" Eintrag hinterlassen könnte! Die Dateistreams werden ja auch erst danach geschlossen und nicht schon davor!

    Einzig die Ausgabe funktioniert nicht mehr. Also Echo usw. wird nie erfolg haben.

    Gruß Niklas

    --
    Man muss nicht alles wissen, man sollte aber wissen, wo das nicht gewusste zu finden ist.
    1. Moin!

      ich habe mich sehr lange das selbe gefragt.
      Nachdem ich aber einige Zeit lang eine Anwendung mit ca. 2000 Usern pro Tag hatte, wurde mir der Sinn leider schmerzlicher bewusst.

      PHP räumt zwar in der Tat selber auf, braucht aber grade in Verbindung mit Datenbanken häufig etwas zu lange. Dadurch kann es dazu kommen, dass zuviele Verbindungen offen sind und eventuelle LOCK befehle den rest aufhalten könnten.

      Mit der Methode kannst du dann explizit immer sagen, dass die Verbindung beendet werden soll.

      Wenn das deine Lösung war, war's keine gute Lösung. Destruktoren werden ja nur implizit aufgerufen, und irgendwann nach dem Löschen der letzten Variablen, die auf das Objekt zeigt.

      Wenn du ein Connection-Problem mit der Datenbank hast, würde ich mich fragen, wieviele Prozesse/Threads/Scriptparallel-Läufe der Webserver erlaubt. So viele Connections muss die Datenbank abkönnen.

      Wenn's hingegen ein Problem mit dem Locking ist: Ja, Locks sollte man freigeben, wenn man sie nicht mehr braucht...

      Für Logging müsste das ganze übrigens auch funktionieren, da man so z.b. noch einen finalen "bin durch gelaufen" Eintrag hinterlassen könnte! Die Dateistreams werden ja auch erst danach geschlossen und nicht schon davor!

      Ich würde mich persönlich nicht drauf verlassen, dass man im Destruktor einer Klasse noch was loggen könnte. Da ich aber derzeit keinen einzigen Destruktor in meiner Codebasis habe, weil PHP alles korrekt selbst wegräumt, ist das ein unbedeutender Effekt.

      Einzig die Ausgabe funktioniert nicht mehr. Also Echo usw. wird nie erfolg haben.

      Das hängt eindeutig davon ab, wann ein Destruktor getriggert wird. Am Scriptende sicherlich nicht.

      - Sven Rautenberg

      1. hi,

        Moin!

        ich habe mich sehr lange das selbe gefragt.
        Nachdem ich aber einige Zeit lang eine Anwendung mit ca. 2000 Usern pro Tag hatte, wurde mir der Sinn leider schmerzlicher bewusst.

        PHP räumt zwar in der Tat selber auf, braucht aber grade in Verbindung mit Datenbanken häufig etwas zu lange. Dadurch kann es dazu kommen, dass zuviele Verbindungen offen sind und eventuelle LOCK befehle den rest aufhalten könnten.

        Mit der Methode kannst du dann explizit immer sagen, dass die Verbindung beendet werden soll.

        Wenn das deine Lösung war, war's keine gute Lösung. Destruktoren werden ja nur implizit aufgerufen, und irgendwann nach dem Löschen der letzten Variablen, die auf das Objekt zeigt.

        Wenn du ein Connection-Problem mit der Datenbank hast, würde ich mich fragen, wieviele Prozesse/Threads/Scriptparallel-Läufe der Webserver erlaubt. So viele Connections muss die Datenbank abkönnen.

        Nehmen wir mal kleine Zahlen um einfach zu rechnen:
        5 Prozesse gleichzeit, DB erlaubt z.b. 14 Connections ohne zu zucken.
        Nun schließen die ersten 5 Prozesse nicht schnell genug und die nachfolgenden 5 machen auf. Somit haben wir in dem Moment 10 Connections "offen".
        Nun machen wir das ganze noch mal und bemerken, dass die 15. Connection ein Problem haben dürfte.

        Bei 2000 Spielern die alle 5 Sekunden klicken kommst du auf 40.000 Anfragen.
        Dann nehme noch einen Server von vor einigen Jahren dazu und du wirst darauf stoßen.
        Dann noch bei Nachgeladenen Informationen und du bist schnell bei 80-100k Anfragen.
        Zugegeben: Man kann mir hier vorwerfen, das ganze unterschätzt zu haben. Aber wer erwartet schon so einen ansturm in kürzester Zeit?

        Sicher man könnte die DB-Verbindung auch dort schließen, wo man die ganze Ausgaben raus schmeißt. Es könnte aber dann auch mal vergessen worden sein und trotzdem wird es noch getriggert. (Soweit keiner im destructor von anderen klassen noch ein exit() verwendet.)

        Es gibt natürlich einige andere Möglichkeiten solche Probleme zu vermeiden. In der Not ist es aber die Lösung gewesen und innerhalb kürzester Zeit machbar. Ob es die schönste Lösung ist, war hier ja auch nicht gefragt, Sinnvoll war es aber allemal, denn das System lief dadurch wieder!

        Wenn's hingegen ein Problem mit dem Locking ist: Ja, Locks sollte man freigeben, wenn man sie nicht mehr braucht...

        Was man eigentlich direkt im Script auch tun sollte, wo man die Locks setzt. Selbst bei Fehlern sollte das durch try&catch ja möglich sein ...

        Für Logging müsste das ganze übrigens auch funktionieren, da man so z.b. noch einen finalen "bin durch gelaufen" Eintrag hinterlassen könnte! Die Dateistreams werden ja auch erst danach geschlossen und nicht schon davor!

        Ich würde mich persönlich nicht drauf verlassen, dass man im Destruktor einer Klasse noch was loggen könnte. Da ich aber derzeit keinen einzigen Destruktor in meiner Codebasis habe, weil PHP alles korrekt selbst wegräumt, ist das ein unbedeutender Effekt.

        Einzig die Ausgabe funktioniert nicht mehr. Also Echo usw. wird nie erfolg haben.

        Das hängt eindeutig davon ab, wann ein Destruktor getriggert wird. Am Scriptende sicherlich nicht.

        Wenn man die Objekte zur Laufzeit selber löscht (an diesen Fall habe ich bei meiner Aussage nicht gedacht), dann werfen diese natürlich sichtbare echos ...

        • Sven Rautenberg

        Gruß Niklas

        --
        Man muss nicht alles wissen, man sollte aber wissen, wo das nicht gewusste zu finden ist.
        1. Moin!

          Nehmen wir mal kleine Zahlen um einfach zu rechnen:
          5 Prozesse gleichzeit, DB erlaubt z.b. 14 Connections ohne zu zucken.
          Nun schließen die ersten 5 Prozesse nicht schnell genug und die nachfolgenden 5 machen auf. Somit haben wir in dem Moment 10 Connections "offen".
          Nun machen wir das ganze noch mal und bemerken, dass die 15. Connection ein Problem haben dürfte.

          Wenn der Webserver gleichzeitig 20 Prozesse erlaubt, die DB aber nur 14 Connections, ist klar, dass es zu einem Problem kommt.

          Wenn der Webserver auch nur 14 Prozesse erlaubt, tritt das Problem nicht auf. Oder die DB 20 Connections.

          - Sven Rautenberg

          1. hi,

            Moin!

            Nehmen wir mal kleine Zahlen um einfach zu rechnen:
            5 Prozesse gleichzeit, DB erlaubt z.b. 14 Connections ohne zu zucken.
            Nun schließen die ersten 5 Prozesse nicht schnell genug und die nachfolgenden 5 machen auf. Somit haben wir in dem Moment 10 Connections "offen".
            Nun machen wir das ganze noch mal und bemerken, dass die 15. Connection ein Problem haben dürfte.

            Wenn der Webserver gleichzeitig 20 Prozesse erlaubt, die DB aber nur 14 Connections, ist klar, dass es zu einem Problem kommt.

            Wenn der Webserver auch nur 14 Prozesse erlaubt, tritt das Problem nicht auf. Oder die DB 20 Connections.

            Du hast eindeutig nicht richtig mein Beispiel verfolgt.
            Der Webserver erlaubt 5 und somit weniger als die DB mit 14. Nach deiner Aussage sollte das ja glücklich passen. Da aber nicht alle Verbindungen geschlossen sind kommt er auf 3x5 und somit kann er ein Problem bekommen (15>14). Das ganze in dem Fall, weil der DB-Server kein close hatte und somit die Verbindung nicht frei gegeben hat. Der Timeout braucht dafür zu lange!

            • Sven Rautenberg

            Gruß Niklas

            --
            Man muss nicht alles wissen, man sollte aber wissen, wo das nicht gewusste zu finden ist.
  4. Moin!

    wer hat mit PHPs Objektorientierung genügend Erfahrung und kann mir sagen, ob ich das Prinzip richtig verstanden habe? Hier meine Mutmaßungen, um deren Bestätigung bzw. Widerlegung ich bitte:

    Da geht einiges durcheinander irgendwie.

    Der wichtigste Punkt aus meiner Sicht: Ressourcen sind KEINE Objekte. Sonst wären es Objekte.

    #1
    In einem Objekt als Eigenschaften vorhandene Stream-Resourcen "sterben" in dem Moment, wo __destruct aufgerufen wird. Im Code (besser "scope") dieser speziellen Funktion (alias Methode) habe ich keine wirklich existierenden Referenzen auf Stream-Resourcen mehr. Anscheinend hat es nur dann einen Sinn diese Methode zu notieren, wenn man im Rahmen der Vererbung eventuelle Elternobjekte "sterben lassen" möchte.

    Nein, die Ressourcen "sterben" in dem Moment, wo die Variable vom Garbage Collector weggeräumt wird. Das ist NACH dem Durchlaufen des Destructors, denn während der Code im Destruktor läuft, existiert das Objekt noch.

    Du hast allerdings nicht unbedingt eine Garantie, dass andere dir sonst in der Klasse zur Verfügung stehenden Objekte noch existieren. Vor allem bei zirkulären Referenzen muss man ja irgendwo mal anfangen mit dem Wegwerfen.

    #2
    Stream-Resourcen werden beim Zerstören des Objektes automatisch von PHP geschlossen. Entweder durch das explizite Aufrufen von __destruct(), oder durch ein unset($obj).

    Stream-Ressourcen sind auch nur Ressourcen. Seit wann haben die Methoden? Also können sie auch keinen Destruktor haben, den man aufrufen kann.

    Bisherige Erkenntnisse:
    Ich habe mir eine Funktion gebastelt, die die Resource korrekt schließt, um anschließend $this->__destruct() aufzurufen. War das so im Sinne des Prinzips?

    Nein. Das Schließen einer Ressource gehört IN den Destruktor. Man ruft Destruktoren auch nie explizit auf, sondern allerhöchstens implizit durch unset($object).

    Debug-Ausgaben liefern auch innerhalb des Codes von __destruct Eigenschaften des Objekts, auch der "file handles" (konkret: "Resource id #7"). Trotzdem scheitert dort ein Aufruf von fclose() mit der Fehlermeldung, es handle sich um keine gülte Stream-Resource. Daher mein Umweg über eine "vorgeschaltete" Methode.

    Du hast irgendwas gebastelt, was man ohne Code nicht beurteilen kann.

    - Sven Rautenberg

    1. Lieber Sven Rautenberg,

      ich habe eine Klasse in etwa dieser Form:

      class Beispiel {  
          private $f; // erhält später den Rückgabewert von fopen()  
        
          public  function __construct ($path) {  
              $this->f = fopen($path, 'r');  
          }  
        
          private function __destruct () {  
              fclose($this->f); // wirft einen Fehler "Not a valid stream resource"  
          }  
        
          public  function close () {  
              $this->__destruct();  
          }  
      }
      

      Wenn ich die close-Methode aufrufe, erhalte ich den oben angemerkten Fehler, der sich auf den fclose-Aufruf in __destruct() bezieht. Versetze ich den fclose()-Aufruf in die close-Methode selbst, erhalte ich den Fehler nicht. Den Hintergrund dahinter habe ich noch immer nicht verstanden.

      Du hast allerdings nicht unbedingt eine Garantie, dass andere dir sonst in der Klasse zur Verfügung stehenden Objekte noch existieren. Vor allem bei zirkulären Referenzen muss man ja irgendwo mal anfangen mit dem Wegwerfen.

      Das könnte bereits die auch von mir vermutete Erklärung dazu sein.

      Nein. Das Schließen einer Ressource gehört IN den Destruktor.

      Also doch so, wie oben beschrieben? Mit der passenden Fehlermeldung?

      Man ruft Destruktoren auch nie explizit auf, sondern allerhöchstens implizit durch unset($object).

      Das habe ich durch diesen Thread jetzt gelernt. Davor war mir der Sinn für diese __destruct-Methode überhaupt nicht klar.

      Liebe Grüße,

      Felix Riesterer.

      --
      ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
      1. Moin!

        ich habe eine Klasse in etwa dieser Form:

        class Beispiel {

        private $f; // erhält später den Rückgabewert von fopen()

        public  function __construct ($path) {
                $this->f = fopen($path, 'r');
            }

        private function __destruct () {
                fclose($this->f); // wirft einen Fehler "Not a valid stream resource"
            }

        public  function close () {
                $this->__destruct();
            }
        }

          
        Wie erwähnt: Man ruft den Destruktor nicht auf!  
          
        
        > Wenn ich die close-Methode aufrufe, erhalte ich den oben angemerkten Fehler, der sich auf den fclose-Aufruf in \_\_destruct() bezieht. Versetze ich den fclose()-Aufruf in die close-Methode selbst, erhalte ich den Fehler nicht. Den Hintergrund dahinter habe ich noch immer nicht verstanden.  
          
        Man ruft den Destruktor nicht auf... :)  
          
        Entweder schließt du die Ressource explizit mit einem Aufruf von "close()". Dann muss deine Klasse durch Code verhindern, dass man sie danach noch benutzt, oder die Datei dafür dann wieder öffnen. Oder du schließt sie implizit im Destruktor. Dann wird die Ressource geschlossen, wenn das Objekt gelöscht wird. Wann das ist, ist nicht garantiert.  
          
        
        > > Du hast allerdings nicht unbedingt eine Garantie, dass andere dir sonst in der Klasse zur Verfügung stehenden Objekte noch existieren. Vor allem bei zirkulären Referenzen muss man ja irgendwo mal anfangen mit dem Wegwerfen.  
        >   
        > Das könnte bereits die auch von mir vermutete Erklärung dazu sein.  
          
        Sehe ich nicht.  
          
        
        > > Nein. Das Schließen einer Ressource gehört IN den Destruktor.  
        >   
        > Also doch so, wie oben beschrieben? Mit der passenden Fehlermeldung?  
          
        Du hast im schlechtesten Fall keine normale Ausgabemöglichkeit mehr. Und dein Beispiel ist auch nicht wirklich geeignet für das Demonstrieren von Destruktoren.  
          
        Wenn der Destruktor die Datei schließt, aber der Zeitpunkt nicht garantiert nach dem "unset()" liegt, sondern auch irgendwann bis hin zum Skriptende passieren kann, und das für die Applikation auch keine Rolle spielt, kann man auf das explizite Schließen auch verzichten, und es die Aufräumroutine von PHP machen lassen.  
          
        Wenn man hingegen explizit die Datei schließen muss, kann man es nicht im Destruktor machen.  
          
        
        > > Man ruft Destruktoren auch nie explizit auf, sondern allerhöchstens implizit durch `unset($object)`{:.language-php}.  
        >   
        > Das habe ich durch diesen Thread jetzt gelernt. Davor war mir der Sinn für diese \_\_destruct-Methode überhaupt nicht klar.  
          
        Es gibt ziemlich wenig Grund, einen Destruktor in einer Klasse einzubauen. In PHP vor 5.3 war es zum Auflösen zirkulärer Referenzen lebensnotwendig, aber mit 5.3 ist der Garbage Collector dazu selbständig in der Lage. Sofern man also nicht explizit Codeoptimierungen vornimmt und die Garbage Collection deaktiviert, entfällt dieser Grund für Destruktoren schon mal.  
          
        Ebenfalls entfällt der Grund für das implizite Freigeben von Ressourcen. Braucht man es explizit, erstellt man dafür eine eigene Methode.  
          
         - Sven Rautenberg
        
        1. Lieber Sven Rautenberg,

          Oder du schließt sie implizit im Destruktor. Dann wird die Ressource geschlossen, wenn das Objekt gelöscht wird. Wann das ist, ist nicht garantiert.

          das bedeutet, wenn ich keine __destruct()-Methode notiere, dann wird die Ressource am Ende des PHP-Prozesses automatisch vom GC geschlossen, notiere ich hingegen die __destruct()-Methode und in ihr ein fclose($this->f), dann wird die Ressource _vielleicht_ schon früher geschlossen - vielleicht auch nicht?

          Wenn der Destruktor die Datei schließt, aber der Zeitpunkt nicht garantiert nach dem "unset()" liegt, sondern auch irgendwann bis hin zum Skriptende passieren kann, und das für die Applikation auch keine Rolle spielt, kann man auf das explizite Schließen auch verzichten, und es die Aufräumroutine von PHP machen lassen.

          Das scheint zu bestätigen, dass ich mit einem simplen unset($obj) nicht sicher sein kann, dass die Ressource _sofort_ geschlossen wird, auch nicht, wenn ich im Destruktor explizit fclose($this->f) notiere.

          Wenn man hingegen explizit die Datei schließen muss, kann man es nicht im Destruktor machen. [...]
          Braucht man es explizit, erstellt man dafür eine eigene Methode.

          Und dann muss man - wie schon erwähnt wurde - dafür sorgen, dass das Objekt anschließend nicht mehr "funktioniert", da ja die Ressource nicht mehr existiert.

          Ich glaube, jetzt habe ich die Zusammenhänge genügend verstanden. Ich fasse zusammen:

          1. Ressourcen werden irgendwann, insbesondere am Ende eines Scripts geschlossen, wenn man das Schließen PHP überlässt. Auch wenn man in einem Objekt eine Ressource abspeichert und im Destruktor deren Schließung explizit notiert, hat man keine Kontrolle darüber, wann die Ressource tatsächlich geschlossen wird.

          2. Will man eine Ressource zu einem definierten Zeitpunkt schließen, so muss man das explizit (bei in Objekten abgespeicherten Ressourcen außerhalb des Destruktors) tun. Ist ein Objekt auf das Vorhandensein einer in seinen Eigenschaften abgespeicherten Ressource angewiesen, so muss ich sicherstellen, dass nach dem expliziten Schließen einer solchen Ressource mein Objekt irgendwie trotzdem klarkommt.

          Liebe Grüße,

          Felix Riesterer.

          --
          ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
      2. class Beispiel {

        private $f; // erhält später den Rückgabewert von fopen()

        public  function __construct ($path) {
                $this->f = fopen($path, 'r');
            }

        private function __destruct () {
                fclose($this->f); // wirft einen Fehler "Not a valid stream resource"
            }

        public  function close () {
                $this->__destruct();
            }
        }

        
        >   
        > Wenn ich die close-Methode aufrufe, erhalte ich den oben angemerkten Fehler, der sich auf den fclose-Aufruf in \_\_destruct() bezieht. Versetze ich den fclose()-Aufruf in die close-Methode selbst, erhalte ich den Fehler nicht. Den Hintergrund dahinter habe ich noch immer nicht verstanden.  
        >   
        > > Du hast allerdings nicht unbedingt eine Garantie, dass andere dir sonst in der Klasse zur Verfügung stehenden Objekte noch existieren. Vor allem bei zirkulären Referenzen muss man ja irgendwo mal anfangen mit dem Wegwerfen.  
        >   
        > Das könnte bereits die auch von mir vermutete Erklärung dazu sein.  
          
        Ist es nicht so, dass der Destruktor 2x Aufgerufen wird?  
        IIRC, war es doch bei PHP so, dass der Destruktor an sich auch nur eine 'einfache' Funktion ist und wenn man sie aufruft, das nicht gleichbedeutend damit ist, dass das Objekt zerstört wurde.  
          
        Das würde dann bedeuten, dass der Destruktor 2x aufgerufen wird. Einmal durch $obj->close() und einmal wenn wenn der GC durch läuft, was wiederum bedeutet, dass beim Aufruf durch den GC die Resource schon freigegeben wurde.  
          
        Oder liege ich gerade komplett daneben?  
          
        MfG  
        bubble
        
        -- 
        If "god" had intended us to drink beer, he would have given us stomachs. - David Daye
        
        1. Tach!

          Ist es nicht so, dass der Destruktor 2x Aufgerufen wird?

          Kommt drauf an, ob man close() aufruft oder nicht.

          IIRC, war es doch bei PHP so, dass der Destruktor an sich auch nur eine 'einfache' Funktion ist und wenn man sie aufruft, das nicht gleichbedeutend damit ist, dass das Objekt zerstört wurde.

          Weder konstruiert eine Konstruktor-Funktion ein Objekt noch zerstört es die Destruktor-Funktion. Beide werden nur jeweils wärend eines solchen Prozesses aufgerufen, wenn vorhanden.

          Das würde dann bedeuten, dass der Destruktor 2x aufgerufen wird. Einmal durch $obj->close() und einmal wenn wenn der GC durch läuft, was wiederum bedeutet, dass beim Aufruf durch den GC die Resource schon freigegeben wurde.

          So ist es. Weiterhin wird das Ressourcen-Handle nicht gegen beispielsweise null oder false ausgetauscht. Es bleibt Ressourcen-Handle. Mit einem einfachen echo sieht man im geöffneten Zustand und nach fclose() immer nur "Resource id #3". Mit vardump hingegen wird aus "resource(3) of type (stream)" nach dem Schließen ein "unknown" aus dem "stream". Wenn man nur das echo anschaut, kann man leicht zu einem falschen Schluss bezüglich des Status der Ressource kommen.

          dedlfix.

          1. Lieber dedlfix,

            Weder konstruiert eine Konstruktor-Funktion ein Objekt noch zerstört es die Destruktor-Funktion. Beide werden nur jeweils wärend eines solchen Prozesses aufgerufen, wenn vorhanden.

            das ist sehr wesentlich für mein Verständnis! Danke!

            Weiterhin wird das Ressourcen-Handle nicht gegen beispielsweise null oder false ausgetauscht. Es bleibt Ressourcen-Handle. Mit einem einfachen echo sieht man im geöffneten Zustand und nach fclose() immer nur "Resource id #3". Mit vardump hingegen wird aus "resource(3) of type (stream)" nach dem Schließen ein "unknown" aus dem "stream". Wenn man nur das echo anschaut, kann man leicht zu einem falschen Schluss bezüglich des Status der Ressource kommen.

            OK... Und wie mache ich es "am besten" mit meiner geöffneten Datei? Lasse ich einfach $obj->f stehen und benutze ein unset($obj), womit das Dateihandle jetzt sofort geschlossen wird, oder muss ich vor dem unset() noch irgendwie ein fclose() ausführen lassen (z.B. innerhalb der close-Methode), weil der GC mit dem Schließen der Datei noch länger auf sich warten lassen könnte?

            Was ich noch überhaupt nicht verstanden habe, ist die Frage, on __construct() bzw. __destruct() eine "public" oder "private" Funktion sein sollte, und warum.

            Liebe Grüße,

            Felix Riesterer.

            --
            ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
            1. Tach!

              OK... Und wie mache ich es "am besten" mit meiner geöffneten Datei? Lasse ich einfach $obj->f stehen und benutze ein unset($obj), womit das Dateihandle jetzt sofort geschlossen wird, oder muss ich vor dem unset() noch irgendwie ein fclose() ausführen lassen (z.B. innerhalb der close-Methode), weil der GC mit dem Schließen der Datei noch länger auf sich warten lassen könnte?

              Es kommt drauf an, was man eigentlich will. Man muss auch noch stark unterscheiden zwischen einem langlaufenden Desktop-Programm (bei PHP eher selten) und einer kurzen Request-Abarbeitung. Es ist nur eingeschränkt sinnvoll und nützlich, das bei Desktop-Programmierung gelernte Verhalten, nach Gebrauch alles ordentlich aufzuräumen, auf PHP überzustülpen. Hat man etwas, das die gesamte Zeit des Requests über benötigt wird, dann kann man das Schließen sehr gut PHP überlassen. Hat man dagegen (und leidet vielleicht auch noch unter Ressourcenknappheit) einen abschließbaren Vorgang, dann gehört (für mich zumindest) das Öffnen und Schließen von Ressourcen in diesen Vorgang. Es ist integraler und nicht nebensächlicher Bestandteil dessen.

              Was ich noch überhaupt nicht verstanden habe, ist die Frage, on __construct() bzw. __destruct() eine "public" oder "private" Funktion sein sollte, und warum.

              Konstruktor-Funktionen können sowohl als auch sein, je nachdem, ob man Objekte öffentlich per new instantiieren will oder nicht. Der "Oder-Nicht"-Fall wäre gegeben, wenn man für das Erzeugen eines Objektes mehr als nur new(+parameter) erledigen muss (und man eine Factory-Methode verwenden soll, um ein Objekt zu bekommen).

              Von C# kenne ich es, dass man einem Destruktor keine Sichtbarkeit definieren kann. Es ist nicht vorgesehen oder möglich, ihn selbständig aufzurufen (außer vielleicht über Reflection - egal). Wenn du den Destruktor in PHP private deklarierst, beschwert sich PHP, wenn es aufräumen will. Am besten lässt du den Sichtbarkeitsvermerk weg, dann wird es public oder setzt ihn explizit public.

              dedlfix.

        2. Liebe(r) bubble,

          Ist es nicht so, dass der Destruktor 2x Aufgerufen wird?

          AHA!!! Danke!

          Liebe Grüße,

          Felix Riesterer.

          --
          ie:% br:> fl:| va:) ls:[ fo:) rl:| n4:? de:> ss:| ch:? js:) mo:} zu:)
  5. Hallihallo!

    Nur mal als Beispiel für einen Destruktor, den ich benutze:
    Ich habe mir eine Logging- Klasse gebaut, die (stark vereinfacht) ungefähr so aussieht:

      
    class log {  
       protected $messages;  // hier kommen die Meldungen rein  
      
       public function __construct() {  
          $this->messages = array[];  
          $this->messages[] = 'bin dann mal gestartet';  
       }  
      
       public function debug($message) {  
          // nur ein Beispiel. Wie gesagt, das hier ist stark vereinfacht.  
          // ausserdem geht es hier um den Destruktor... :-)  
          $this->messages[] = 'DEBUG :'.$message;  
       }  
      
       public function __destruct() {  
          // hier wird erst die Logdatei geschrieben.  
          // dadurch habe ich nicht die ganze Zeit das olle Handle offen.  
          // Das Tolle ist, dass ich mich nicht mehr darum kümmern  
          // muss, die Datei "manuell" zu schreiben  
      
          $logfile = fopen('/var/www/files/log.log',"w+");  
          $content = implode(PHP_EOL,$this->messages);  
          fwrite($logfile, $content);  
          fclose($logfile);  
       }  
    }  
      
      
    
    

    Beste Grüsse,
        Tobias Hahner

    1. Tach!

      Ich habe mir eine Logging- Klasse gebaut, die (stark vereinfacht) ungefähr so aussieht:

      Dann kommentiere ich mal ausgehend davon.

      public function __destruct() {
            // hier wird erst die Logdatei geschrieben.
            // dadurch habe ich nicht die ganze Zeit das olle Handle offen.

      Dafür hast du die ganze Zeit ein Objekt mit einem wachsenden Array im Speicher rumliegen.

      // Das Tolle ist, dass ich mich nicht mehr darum kümmern
            // muss, die Datei "manuell" zu schreiben

      Das Tolle ist, man bekommt das als Dreizeiler hin.

      function debug($message) {
        file_put_contents('/var/www/files/log.log', 'DEBUG :'.$message, FILE_APPEND);
      }

      Natürlich kann man das auch objektorientiert ausführen, zum Beispiel wenn man per Dependency Injection den anderen Akteuren das zu verwendende Log-Objekt vorgeben will, statt dass sie fest verdrahtet diese Funktion aufrufen. Aber auch dann braucht es keinen Destruktor. Und auf diese Weise hat man die Meldungen bis dahin auch dann in der Datei, wenn PHP meint, das Script mit einem fatalen Error abzubrechen.

      dedlfix.

      1. Moin!

        Ich habe mir eine Logging- Klasse gebaut, die (stark vereinfacht) ungefähr so aussieht:

        Dann kommentiere ich mal ausgehend davon.

        Das tolle am Destruktor ist auch, dass er vermutlich nicht mehr ausgeführt wird, wenn man einen fatalen PHP-Fehler kriegt. Und gerade Logging wäre dann eventuell sehr hilfreich gewesen...

        - Sven Rautenberg

        1. Tach!

          Das tolle am Destruktor ist auch, dass er vermutlich nicht mehr ausgeführt wird, wenn man einen fatalen PHP-Fehler kriegt. Und gerade Logging wäre dann eventuell sehr hilfreich gewesen...

          Ja, hab ich auch gesagt. Außerdem gibt es da noch die Möglichkeiten von error_log(), mit denen man auch (absturzsicher) einzeilig Fehler in Dateien und andere Stellen bringen kann.

          dedlfix.

          1. Hallihallo!

            Tach!

            Das tolle am Destruktor ist auch, dass er vermutlich nicht mehr ausgeführt wird, wenn man einen fatalen PHP-Fehler kriegt. Und gerade Logging wäre dann eventuell sehr hilfreich gewesen...

            Ja, hab ich auch gesagt. Außerdem gibt es da noch die Möglichkeiten von error_log(), mit denen man auch (absturzsicher) einzeilig Fehler in Dateien und andere Stellen bringen kann.

            Jaaajaaa, lästert Ihr mal ruhig :-)

            Wie ich ja schon geschrieben hatte, ist dieses Beispiel stark vereinfacht gewesen.

            Der eigentliche Sinn, den ich mit der Klasse verfolgt hatte, war nämlich, sowohl unterscheiden zu können, WIE die Nachrichten gespeichert werden sollen, als auch an die Nachrichten zum Zweck der späteren Ausgabe nochmal ranzukommen. Beispielsweise gebe ich sie in einer Antwort-Mail zurück, wenn ich meinem Skript "Befehle" per Mail geschickt habe.
            Geloggt werden so Sachen wie "INFO : Benutzer soundso will Methode soundso ausführen" und
            "WARNING : Benutzer soundso DARF das gar nicht! Wie konnte er die Option dafür überhaupt bekommen?"
            oder
            "ERROR : die Datenbank ist nicht auswählbar" oder
            "INFO: Benutzer soundso hat sich angemeldet"
            oder
            "INFO: Deine 20 Datensätze wurden gespeichert"
            Sowas eben...

            Um eine echte Fehlersuche geht es in dieser Klasse gar nicht. Dafür wird, wie schon erwähnt, das error log benutzt.
            Obwohl ich zugeben muss, dass meine Exception Klasse auch ein eigenes Log- Objekt benutzt, und zwar in einer Form, die dieser vereinfachten hier sehr nahe kommt. Sprich: erst im Destruktor werden die Dateien angelegt und geschrieben. Und hier ist es tatsächlich so: Die Datei WIRD geschrieben. Auch dann, wenn die Exception nicht gefangen wird, was in einem catchable fatal error endet. Aber darum geht es hier gar nicht...

            Halten wir also fest: DIESE MEINE log- Klasse ist NICHT zum Loggen von "echten" Programmfehlern gedacht, sondern von Bedienerfehlern oder Hinweisen für den Bediener. Und dafür hat sie sich bewährt.
            Während der Debug-Phase behelfe ich mir mit set_error_handler() und einer Funktion, die mir ein error log anlegt. Das ist per FTP dann leichter zu erreichen als das originale...
            Vielleicht sollte ich noch dazu sagen, dass ich meinen Entwicklungsserver zwar direkt neben mir stehen habe, aber er hat weder Monitor noch Tastatur (es ist ein RaspberryPi unter meinem Schreibtisch)

            Beste Grüsse,
                Tobias Hahner

            1. Tach!

              Wie ich ja schon geschrieben hatte, ist dieses Beispiel stark vereinfacht gewesen.
              Halten wir also fest: DIESE MEINE log- Klasse ist NICHT zum Loggen von "echten" Programmfehlern gedacht, sondern von Bedienerfehlern oder Hinweisen für den Bediener.

              Danke für die Klarstellung. Damit ergibt sich ein anderes Bild, das durch die Verkürzung so nicht zu sehen war. Was mich aber wundert, warum erst am Script-Ende feststeht, wohin diese Ausgabe geschickt werden soll. Wenn du den Text neben der Dateiausgabe am Ende auch noch ausgeben willst, sollte doch spätestens die allgemeine Programmlogik wissen, dass das notwendig ist und es von sich aus tun, kurz bevor sie selbst fertig ist und zum Aufrufer zurückkehrt. Du überträgst mit deiner Methode dem Destruktor Aufgaben, die nicht direkt ersichtlich sind. Wenn man das Programm nicht kennt und nach der Ursache der Ausgabe sucht, findet man das nicht im normalen Programmablauf, sondern in einem implizit aufgerufenen Teil. Ist das gute Architektur?

              dedlfix.

              1. Hallihallo!

                Danke für die Klarstellung. Damit ergibt sich ein anderes Bild, das durch die Verkürzung so nicht zu sehen war.

                Gerne doch, das war auch so beabsichtigt :-)
                (Ich hätte auch nicht erwartet, dass es zu einer Diskussion in dieser Richtung kommen könnte, da es meiner Meinung nach nur um die Frage "gibt es Anwendungsfälle, in denen man einen Destruktor brauchen kann?" ging.)

                Was mich aber wundert, warum erst am Script-Ende feststeht, wohin diese Ausgabe geschickt werden soll.

                Das ist so nicht die ganze Wahrheit. Natürlich weiss ich von Anfang an, wohin die Ausgabe gehen soll, zumindest in den meisten Fällen. Dazu etwas mehr weiter unten...

                Wenn du den Text neben der Dateiausgabe am Ende auch noch ausgeben willst, sollte doch spätestens die allgemeine Programmlogik wissen, dass das notwendig ist und es von sich aus tun, kurz bevor sie selbst fertig ist und zum Aufrufer zurückkehrt. Du überträgst mit deiner Methode dem Destruktor Aufgaben, die nicht direkt ersichtlich sind.

                Da laufen wir jetzt gerade Gefahr, aneinander vorbeizureden. Ist aber zum grössten mein Fehler, den ich weiter unten hoffentlich beheben kann.

                Wenn man das Programm nicht kennt und nach der Ursache der Ausgabe sucht, findet man das nicht im normalen Programmablauf, sondern in einem implizit aufgerufenen Teil. Ist das gute Architektur?

                Das will ich hoffen :-) Aber da ich Dich in diesem Forum für den Spezialisten in Sachen PHP halte, komme ich jetzt einfach mal zu dem "weiter unten", damit das Ganze hoffentlich zu einem stimmigen Gesamtbild kommt:

                Meine log-Klasse ist ein kleiner Teil eines relativ grossen Konstrukts, das über die letzten 3 Jahre immer weiter gewachsen ist. Teilweise fing das Alles geschwürartig an zu wuchern, es gab immer mehr "Frickel"- Ecken, und deshalb bin ich seit ca. 2 Wochen dabei, Alles von Grund auf neu zu bauen.
                Das "Konstrukt" ist im Prinzip nicht mehr und nicht weniger als eine Oberfläche für MySQL- Tabellen, plus einen "Arsch voll" Geschäftslogik. Es handelt sich momentan hauptsächlich um eine Lagerverwaltung mit angeschlossener Projektverwaltung, Tourenplanung, Zeiterfassung und Fakturierung usw.. Im Lager sind Artikel gelistet, die dann von der Projektleitung zu Packlisten "zusammengeklickt" werden können. Für die Dauer des Projekts werden dann die betreffenden Artikel (Mietgegenstände) aus dem Bestand gerechnet, die Mietpreise werden in Abhängigkeit zur Projektdauer in die Rechnung gerechnet, dazu dann noch die letzendlich geleisteten Arbeitsstunden, uswusf.
                Dabei musste ich auch von Anfang an verschiedene Benutzer und Benutzergruppen verwalten können, weil z.B. die Leute im Lager die hinterher gestellten Rechnungen nicht angehen, usw.
                Soweit die Vorgeschichte...

                Damit das Alles auch zukünftig erweiterbar und überhaupt wartbar ist, habe ich Alles in "Module" verpackt (quasi Plugins), was eigentlich das Schwierigste dabei war.
                Ausserdem kam jetzt vor Kurzem auch erst die Möglichkeit der Fernsteuerung per E-Mail hinzu. (Einige von uns haben Android Smartphones mit entsprechenden TimeTracker Apps, welche csv- Dateien versenden können. Diese werden dann von dem "Programm" empfangen, geparst und in MySQL übertragen, um nur ein Beispiel zu nennen)
                Es gab also plötzlich einen völlig neuen Kontext, ich nenne ihn intern einfach "mail", der vorher nicht vorgesehen war.

                Nun stand ich vor dem Problem des Testens und des Protokollierens von Fehlern oder Debug-Meldungen. Dabei ist es mir wichtig gewesen, auch jederzeit Kontrolle darüber zu haben, welche Meldungen überhaupt für wen interessant sind. Darüberhinaus brauchte ich auch noch die Möglichkeit, verschiedene Logs aus verschiedenen Modulen ein- und auszublenden, und und und...

                Es gibt also zum Beispiel folgendes Szenario:

                • Nutzer schickt eine csv- Datei mit seinen Arbeitsstunden an den Server
                • Programm "main" wird per cron gestartet und merkt, dass da eine neue Mail wartet.
                • Programm "main" legt ein log an und sagt "INFO: neue Mail entdeckt am xxxxxxx um xx:xx"
                • Programm reicht die E-Mail weiter an Klasse "email"
                • Klasse "email" legt ein log an und sagt "INFO: habe hier eine neue Email, ich parse die dann mal eben"
                • Klasse "email" meldet diverse "DEBUG"- Meldungen an sein log, bzgl. MessageId, Subject, From usw.
                • Klasse "email" sagt "DEBUG: fertig, email ist geparst"
                • Programm "main" fragt Klasse "email" nach einigen Header- Zeilen und fragt Klasse "user", ob man daraus einen user basteln kann
                • Klasse "user" legt ein log an und sagt "INFO: ich wurde nach einem user-Objekt gefragt. Folgende FROM und RECEIVED hab ich"
                • Klasse "user" stellet fest, dass da Alles klar geht (natürlich inklusive diverser DEBUG- Meldungen) und spuckt ein user Objekt aus
                • Programm "main" sagt: "INFO: Yupp, User ist angemeldet mit der ID xxx"
                • Programm "main" gibt die Email weiter an "executor"
                • Klasse "executor" legt ein log an und sagt "INFO: executor gestartet. Ich acker mich jetzt durch die Mail mit der MessageId xxxxx"
                • Klasse "executor" fräst sich also durch die Email und findet irgendwo einen Befehl.
                • Klasse "executor" sagt also "INFO: Befehl gefunden in Zeile soundso: blabla(foo);"
                • Klasse "executor" fragt Klasse "user", ob der User die Berechtigung für diesen Befehl hat
                • Klasse "user" sagt "WARNING: Darf der nicht!" und gibt false zurück...
                • Klasse "executor" sagt "INFO: Befehl wird nicht ausgeführt"

                Das war jetzt nur ein Beispiel...
                Jetzt möchte ich natürlich dem User nur die Mitteilung geben, dass Alles geklappt hat, oder eben nicht.
                Ich suche mir also im Programm "main" im Fehlerfall alle Meldungen die "WARNING" oder "ERROR" sind (muss ja nicht unbedingt an Berechtigungen scheitern) und schicke sie ihm. Aber nur die, die vom "executor" oder der angeforderten Routine stammen...
                Wenn es aber einen echten Fehler, oder z.B. einen Einbruchsversuch mittels gefälschter Emails gegeben hat, bekomme ich als admin eine Mail, in der ALLE Meldungen aus ALLEN Klassen, auch die DEBUG enthalten sind.

                Ich kann also effektiv unterscheiden, welche Meldungen aus welchem Programmteil ich sehen will. Unabhängig davon, auch wenn ich sie nicht sehen will, lasse ich sie, nach Klassen sortiert, aber auch abspeichern, damit ich Nicht-offensichtliche Fehler (z.B. Start- und Endzeit vertauscht, weil Jemand einen anderen TimeTracker benutzt, oder sonstwas) anhand der Debug- Meldungen nachvollziehen kann.

                Die programmatische Nutzung ist dabei absichtlich extrem simpel gehalten:
                Jede Klasse hat ihr eigenes Log- Objekt, das sie mit sich rumschleppt:
                protected $log = logs::getInstance("Zeiterfassung", 'ALL|~DEBUG'); // zum Beispiel. DEBUG werden zwar gemerkt für Ausgabe, erscheinen aber nicht in der Datei
                und im weiteren Verlauf der Klasse sind dann die Meldungen verteilt wie Kontrollausgaben:
                $this->log->debug('verarbeite jetzt Zeile Nr. xxx');
                oder
                $this->log->warning('Nutzer hat nicht die Berechtigung!');
                oder aber auch
                $this->log->error('Das jetzt aber blöd gelaufen! DB ist putt!');

                Zum Schluss kann ich von egal wo (normalerweise aus "main") die Einträge rausfischen, die ich anzeigen will:

                $entries = logs::getMessages("Zeiterfassung","WARNING"); // gibt mir nur die WARNINGS aus Zeiterfassung
                oder
                $entries = logs::getMessages('','ALL');  // gibt mir Alles...

                Um das Speichern muss ich mich nicht explizit kümmern. Will ich auch nicht. Ich bin faul, das macht der Destruktor des jeweiligen log-Objekts für mich. Wichtig ist mir vor Allem, dass ich hinterher mit den gefilterten Meldungen noch etwas anfangen kann. Dafür brauche ich das wachsende Array...

                Ich hoffe, damit konnte ich etwas Licht in das Dunkel bringen, nachdem ich vorher so brutal das Rollo runtergelassen und die Lampe ausgeknipst habe...

                Beste Grüsse,
                    Tobias Hahner

                1. Tach!

                  Ich nehme mal an, dass sich Sven dazu noch äußern wird, er kennt sich besser in Software-Architektur aus.

                  Um das Speichern muss ich mich nicht explizit kümmern. Will ich auch nicht. Ich bin faul, das macht der Destruktor des jeweiligen log-Objekts für mich.

                  Gut, aber ich als Neuer in dem Projekt weiß das nicht, dass da solch ein Mechanismus existiert und ich sehe ihn auch nicht einfach so. Ich sehe nur, dass da was geloggt wird und hab dann auf eine unersichtliche Weise Mail im Briefkasten. Erst wenn ich mir die Nebenklasse zum Logging anschaue, löst sich das Rätsel auf. Don't let me think, das muss ich schon noch zur Genüge bei den wirklichen Problemen der Geschäftslogik.

                  Wichtig ist mir vor Allem, dass ich hinterher mit den gefilterten Meldungen noch etwas anfangen kann. Dafür brauche ich das wachsende Array...

                  Also, ich würde es besser finden, das man sich dann explizit um diesen Datenberg kümmert. Notfalls müssen Referenzen zu den Log-Objekten eben noch in einem globalen Repository abgelegt werden, das man am Ende durchläuft und auswertet. Damit kommst du auch ans Ziel und der Prozess ist offensichtlicher. Es ist auch nicht so (soll jedenfalls nicht sein), dass man nun ständig selbst an das Aufräumen denken muss. Der Log-Endverwerter wird einfach irgendwo am Ende vom Front-Controller aufgerufen und tut sein Ding - nur eben explizit und nicht implizit durch Destruktoren.

                  dedlfix.

                  1. Hallihallo!

                    Don't let me think, das muss ich schon noch zur Genüge bei den wirklichen Problemen der Geschäftslogik.

                    Das war eigentlich der Grund dafür, dass ich es so gemacht habe...
                    Was kann einfacher sein, als einmal im Konstruktor ein log-Objekt anzulegen mit

                      
                    $this->log = logs::getInstance($modulname,$loglevel);  
                    /* Als dritten Parameter kann man sogar noch $paranoia=true setzen, dann wird jede Meldung sofort in die Datei geschrieben */  
                    
                    

                    und mit diesem Objekt dann machen zu können, was man will?
                    Bestimmte Designrichtlinien musste ich bisher auch immer beachten, wenn ich in irgendeinem Code ein Plugin einfügen wollte. Dafür gibt es dann ja auch eine entsprechende Dokumentation (ja, ich habe Eine, und nein, es interessierte bisher Niemanden :-) )

                    Wichtig ist mir vor Allem, dass ich hinterher mit den gefilterten Meldungen noch etwas anfangen kann. Dafür brauche ich das wachsende Array...

                    Also, ich würde es besser finden, das man sich dann explizit um diesen Datenberg kümmert. Notfalls müssen Referenzen zu den Log-Objekten eben noch in einem globalen Repository abgelegt werden, das man am Ende durchläuft und auswertet.

                    Vielleicht hatte ich das nicht richtig ausgedrückt, aber im Prinzip ist das ja so.
                    Wenn ich mit den Meldungen noch was anfangen will, hole ich sie mir einfach ab mit

                      
                    $entries = logs::getMessages($modulname,$level); // level kann hier anders sein als im Konstruktor, wenn man will  
                    
                    

                    und habe dann ein Array mit allen Meldungen des jeweiligen Levels. Damit kann ich als Programmierer arbeiten, sie analysieren, und irgendwann, nach dem Debuggen, vielleicht einfach das Abholen unterlassen und den Loglevel im Konstruktor auf WARNING|ERROR ändern. Dass die Log-Dateien alle an einer zentralen Stelle gespeichert sind, steht auch in der Doku. Und wenn mal Jemand Anders der Admin ist, und keine Lust auf Emails hat, dann kann er das in den Einstellungen des Programms im Admin-Panel einfach ausschalten...

                    Ich finde eigentlich, dass das für einen potentiellen Plugin- Entwickler eher eine enorme Arbeitserleichterung ist, wenn die komplette Infrastruktur für das Logging schon vorhanden ist. Zumindest, solange es eine ordentliche Doku dazu gibt.

                    Zumal ich nicht nach dem Debugging alle Kontrollausgaben aus dem Code entfernen muss. Ich muss lediglich das Abholen der Meldungen rauslöschen / anpassen.

                    Oder liege ich da so falsch?

                    Beste Grüsse,
                        Tobias Hahner

                    1. Tach!

                      Don't let me think, das muss ich schon noch zur Genüge bei den wirklichen Problemen der Geschäftslogik.
                      Das war eigentlich der Grund dafür, dass ich es so gemacht habe...

                      Ich kann dich ja verstehen, und aus deiner Sicht ist auch alles in Ordnung. Du kennst das Projekt und weißt, wie es arbeitet. Nur für den Außenstehenden ist es nicht direkt ersichtlich, wie es zum Ergebnis des Destruktor-Codes kommt. Man muss wissen, dass der Destruktor verantwortlich ist und auch noch, wann er aufgerufen wird.

                      Was kann einfacher sein, als einmal im Konstruktor ein log-Objekt anzulegen mit

                      Definiere "einfacher". Ist es wichtiger, Code einfacher zu schreiben oder Abläufe einfacher zu durchschauen?

                      Ich finde eigentlich, dass das für einen potentiellen Plugin- Entwickler eher eine enorme Arbeitserleichterung ist, wenn die komplette Infrastruktur für das Logging schon vorhanden ist. Zumindest, solange es eine ordentliche Doku dazu gibt.

                      Doku liest doch keiner. Da steht sowieso nie das drin, was man wissen will ...

                      Oder liege ich da so falsch?

                      Beim Programmieren kann man nicht immer eine bestimmte Vorgehensweise als definitiv falsch bezeichnen, zumal ja wie in deinem Fall offensichtlich auch das gewünschte Ergebnis erreicht wird. Ich möchte dir lediglich aufzeigen, das es manchmal bessere Orte für Code gibt, als den Destruktor. Mein Destruktor schließt am Ende des Arbeitstages lediglich das Zeug vom Schreibtisch in den Schrank ein. Deiner aber nimmt vorher auch noch den Stapel der erzeugten Geschäftsbriefe, tütet sie in Umschläge, frankiert diese und legt sie in den Postausgang. Mein Arbeitstag sieht diese Handlungen ebenfalls vor, aber das ist eine Aktion, die ich dann doch lieber extra und explizit anstoße. Es ist dann auch einfacher, sie mal an andere Stelle zu verlagern. Bei dir muss man Feierabend machen, damit es die Post versendet. Das kann man auch dokumentieren, aber "Zum Post versenden machen Sie bitte Feierabend!" ist schon etwas seltsam.

                      dedlfix.

                      1. Hallihallo!

                        Doku liest doch keiner. Da steht sowieso nie das drin, was man wissen will ...

                        Ist das wirklich Dein Ernst? Da hatte ich Dich vollkommen anders eingeschätzt. Ich habe bisher zumindest immer die Dokus überflogen, den Teil "Designrichtlinien" (oder ähnlich, heisst ja immer anders) habe ich sogar immer aufmerksam gelesen... Einmal hatte ich ein Projekt vor mir, das keine ordentliche Doku hatte, da wäre ich fast abgedreht...

                        Mein Destruktor schließt am Ende des Arbeitstages lediglich das Zeug vom Schreibtisch in den Schrank ein. Deiner aber nimmt vorher auch noch den Stapel der erzeugten Geschäftsbriefe, tütet sie in Umschläge, frankiert diese und legt sie in den Postausgang.

                        Nein, das tut er nicht :-) Ich stelle fest, dass ich anscheinend kein guter Erklärbär bin...

                        Bei dir muss man Feierabend machen, damit es die Post versendet. Das kann man auch dokumentieren, aber "Zum Post versenden machen Sie bitte Feierabend!" ist schon etwas seltsam.

                        Neinneinnein :-) (s.o.)

                        Da ich anscheinend arge Probleme habe, Programmtechnisches umgangssprachlich zu erklären, zeige ich Dir einfach mal den nicht ganz so stark vereinfachten Code. Vielleicht siehst Du dann, dass er im Endeffekt genau das tut, was Du , nur anders verpackt:

                        Die Klasse log: (bedenke bitte, sie hat noch die Versionsnummer 0.0.2, ist also durchaus verbesserungswürdig! Zum Beispiel sind konkurrierende Zugriffe auf logfiles noch nicht bedacht...

                        
                        class log {
                        		// severity codes, used for writing the log file. No effect on recording, though
                        		const NONE = 0;
                        		const DEBUG = 1;
                        		const INFO = 2;
                        		const NOTICE = 4;
                        		const WARNING = 8;
                        		const ERROR = 16;
                        		const ALL = 255;
                        
                        		/**
                        		 * @var resource the logfile
                        		 * @access protected
                        		 */
                        		protected $logfile = null;
                        		
                        		/**
                        		 * @var string Fully qualified name of the logfile.
                        		 * @access protected
                        		 */
                        		protected $logfilename = '';
                        		
                        		/**
                        		 * @var string the content to be written
                        		 * @access protected
                        		 */
                        		protected $logcontent = '';
                        		
                        		/**
                        		 * @var array
                        		 * @access protected
                        		 * the collected messages
                        		 */
                        		protected $messages;
                        		
                        		
                        		/**
                        		 * @var integer the contents to log (bitwise on or off)
                        		 */
                        		protected $loglevel = 0;
                        		
                        		/**
                        		 * @var bool
                        		 * flag to differ between paranoid (true) and lazy (false) mode
                        		 * should only be set for debugging, because its not threadsafe!
                        		 * True also uses more resources, as every message will be saved to the logfile immediately
                        		 */
                        		protected $paranoia;
                        		
                        		
                        		/**
                        		 * constructor
                        		 * sets the loglevel and, if not off, opens the logfile for write access
                        		 * @access public
                        		 * @param $logfile
                        		 * @param $loglevel
                        		 * @param $paranoia
                        		 */
                        		public function __construct($logfile, $loglevel, $paranoia=false) {
                        			$this->messages = array();
                        			if ( ($loglevel == 'NONE')||($loglevel === 0) ) {
                        				$this->loglevel = 0;
                        				$this->logfile = null;
                        				$this->logcontent = '';
                        				$this->paranoia = false;
                        				$this->logfilename = $logfile;
                        				return;
                        			}
                        			if (intval($loglevel) === $loglevel) {
                        				// ok, some nerd doesnt need words...
                        				$this->loglevel = $loglevel;
                        				if ($paranoia) {
                        					$this->logfile = @fopen($logfile, 'a+');
                        					if (FALSE === $this->logfile) $this->logfilename = '/var/www/files/logs/'.date("Y-m-d").'-generic.log';
                        					$this->logfile = fopen($this->logfilename, 'a+');
                        				}
                        				$this->logfilename = $logfile;
                        				$this->paranoia = $paranoia;
                        				$this->logcontent = date("Y-m-d H:i:s").' ---------- START LOGGING, INITIALIZED IN NERD MODE, LOGLEVEL='.$this->loglevel."\n";
                        				return;
                        			}
                        			$levs = array(
                        				'NONE'		=> 0,
                        				'DEBUG'		=> self::DEBUG,
                        				'INFO'		=> self::INFO,
                        				'NOTICE'	=> self::NOTICE,
                        				'WARNING'	=> self::WARNING,
                        				'ERROR'		=> self::ERROR,
                        				'ALL'		=> self::ALL
                        			);
                        			$this->loglevel = 0;
                        			$levels = explode('|', $loglevel);
                        			foreach ($levels as $part) {
                        				$part = trim($part);
                        				$cleanpart = str_replace('~', '', $part);
                        				$multiplikator = (FALSE === strpos($part, '~')) ? 1 : (-1);
                        				if (array_key_exists($cleanpart, $levs)) {
                        					$this->loglevel += $multiplikator*$levs[$cleanpart];
                        				}
                        			}
                        			if ($this->loglevel != 0) {
                        				$this->logfilename = $logfile;
                        				if ($paranoia) $this->logfile = @fopen($logfile, 'a+');
                        				if (FALSE === $this->logfile) $this->logfilename = '/var/www/files/logs/'.date("Y-m-d").'-generic.log';
                        				//$this->logfile = fopen($this->logfilename, 'a+');
                        				$this->paranoia = $paranoia;
                        				$this->logcontent = date("Y-m-d H:i:s").' ---------- START LOGGING, INITIALIZED IN NORMAL MODE, LOGLEVEL='.$this->loglevel.' ('.$loglevel.')'."\n";
                        			}
                        			return;
                        		}
                        
                        		/**
                        		 * destructor
                        		 * in lazy mode, opens the logfile, writes the content
                        		 * in paranoid and lazy mode, closes the logfile
                        		 * @access public
                        		 * @return void
                        		 */
                        		public function __destruct() {
                        			if ($this->logfilename == '') return;
                        			if (!$this->paranoia) {
                        				$this->logfile = fopen($this->logfilename, 'a+');
                        				fwrite($this->logfile, $this->logcontent);
                        			}
                        			if (!is_null($this->logfile)) {
                        				fclose($this->logfile);
                        			}
                        		}
                        
                        		/**
                        		 * savelog
                        		 * the main function of class log.
                        		 * receives the text and the loglevel. determines, whether the text has to be saved,
                        		 * and then eventually saves it.
                        		 * in lazy mode, the text will be in ram ($this->logcontent) until this object is destructed
                        		 * in paranoid mode, the text will immediately be stored in the logfile
                        		 * Regardless of paranoia and loglevel, all messages will be stored in ram for eventual later use
                        		 * @param string $text
                        		 * @param integer $level
                        		 * @return bool
                        		 * @version 0.0.1 initial implementation
                        		 * @version 0.0.2 added microtime recording
                        		 */
                        		public function savelog($text,$level) {
                        			// store the message anyway, no matter what the loglevel is:
                        			$this->messages[] = array('time'=>microtime(true),'level'=>$level,'message'=>$text);
                        			if ( ($level & $this->loglevel) == $level) {
                        				$this->logcontent .= date("Y-m-d H:i:s").' ';
                        				switch ($level) {
                        					case 1 :	$this->logcontent .= 'DEBUG     ';
                        								break;
                        					case 2 :	$this->logcontent .= 'INFO      ';
                        								break;
                        					case 4 :	$this->logcontent .= 'NOTICE    ';
                        								break;
                        					case 8 :	$this->logcontent .= 'WARNING   ';
                        								break;
                        					case 16:	$this->logcontent .= 'ERROR     ';
                        								break;
                        					default:	break;	
                        				}
                        				$this->logcontent .= $text."\n";
                        				if ($this->paranoia) {
                        					$success = fwrite($this->logfile, $this->logcontent);
                        					$this->logcontent = '';
                        					return $success;
                        				}
                        				return true;
                        			}
                        		}
                        
                        		/**
                        		 * debug
                        		 * a wrapper method for convenient logging
                        		 * @access public
                        		 * @param string $text
                        		 * @return bool
                        		 */
                        		public function debug($text){
                        			return $this->savelog($text, self::DEBUG);
                        		}
                        		
                        		/**
                        		 * info
                        		 * a wrapper method for convenient logging
                        		 * @access public
                        		 * @param string $text
                        		 * @return bool
                        		 */
                        		public function info($text) {
                        			return $this->savelog($text, self::INFO);
                        		}
                        		
                        		/**
                        		 * notice
                        		 * a wrapper method for convenient logging
                        		 * @access public
                        		 * @param string $text
                        		 * @return bool
                        		 */
                        		public function notice($text) {
                        			return $this->savelog($text, self::NOTICE);
                        		}
                        		
                        		/**
                        		 * warning
                        		 * a wrapper method for convenient logging
                        		 * @access public
                        		 * @param string $text
                        		 * @return bool
                        		 */
                        		public function warning($text) {
                        			return $this->savelog($text, self::WARNING);
                        		}
                        		
                        		/**
                        		 * error
                        		 * a wrapper method for convenient logging
                        		 * @access public
                        		 * @param string $text
                        		 * @return bool
                        		 */
                        		public function error($text) {
                        			return $this->savelog($text, self::ERROR);
                        		}
                        
                        

                        Die Klasse logs (Das ist quasi der globale Scope, den Du angesprochen hattest, um von überall Zugriff auf alle log- Objekte zu haben)

                        
                        	abstract class logs {
                        		protected static $logs = array();
                        		protected static $directory = null;
                        		protected static $time = '';
                        		protected static $sortcrit = 'time';
                        		protected static $filters = array();
                        		
                        		const DIR = '/var/www/files/logs/';
                        		
                        		/**
                        		 * init
                        		 * For internal use only
                        		 * Sets the log directory (creates if does not exist)
                        		 * @access protected
                        		 * @return void
                        		 * @since 0.0.1
                        		 */
                        		protected function init() {
                        			$date = date("Y-m-d");
                        			if (!is_dir(self::DIR.$date)) {
                        				mkdir(self::DIR.$date);
                        			}
                        			self::$time = date("H_i");
                        			self::$directory = self::DIR.$date.'/';
                        		}
                        		
                        		
                        		/**
                        		 * getInstance
                        		 * This is the function every other class should call for creating a log.
                        		 * Calls the log constructor and stores the log object in the static class var $logs
                        		 * @param string $name identifier for the log
                        		 * @param mixed $loglevel The loglevel. See log::__construct for details
                        		 * @return log the requested log object
                        		 */
                        		static public function getInstance($name,$loglevel,$paranoia=false) {
                        			if (is_null(self::$directory)) self::init();
                        			if (!array_key_exists($name, self::$logs)) {
                        				// needed for most identifiers as they must not contain characters confusing the file structure
                        				$filename = str_replace(array('\\','/',':'), '_', $name);
                        				$new = new log(self::$directory.self::$time.'-'.$filename.'.log', $loglevel,$paranoia);
                        				self::$logs[$name] = $new;
                        				self::$filters[$name] = 'ALL';		// required for getMessages()
                        			}
                        			return self::$logs[$name];
                        		}
                        
                        		/**
                        		 * getMessages
                        		 * returns the messages of all log objects created by this class.
                        		 * The messages can be specified by the parameter.
                        		 * The messages are returned in the same format as if called log::getMessages
                        		 * The returned messages will be sorted by the given second parameter.
                        		 * If the given parameter is not 'time', 'time' will be used as second criteria for sorting
                        		 * @see log::getMessages
                        		 * @since 0.0.1
                        		 * @param mixed $loglevel
                        		 * @param string $sortBy The key the messages shall be sorted. Can be any array key used in log::getMessages(). SHOULD be 'time' or 'identifier'
                        		 * @param bool $useFilters If set to true, messages will be collected using individual loglevels.
                        		 * @return array the requested messages
                        		 * @uses log::getMessages
                        		 */
                        		static public function getMessages($loglevel,$sortBy='time',$useFilters=true) {
                        			$ret = array();
                        			// collect data
                        			foreach (self::$logs as $identifier => $logobj) {
                        				$level = $loglevel;
                        				if ($useFilters) $level = self::$filters[$identifier];
                        				$mess = $logobj->getMessages($level);
                        				foreach ($mess as $entry) {
                        					// will produce
                        					// $ret[$counter] = array('identifier'=>..., 'time'=>..., 'level'=>..., 'leveltext'=>... , 'message'=>...)
                        					$tmp = $entry;
                        					$tmp['identifier'] = $identifier;
                        					$ret[] = $tmp;
                        				}
                        			}
                        			// sort data
                        			self::$sortcrit = $sortBy;
                        			$sorted = uasort($ret, array('self','sort'));
                        			if ($sorted) return $ret;
                        			return $sorted;
                        		}
                        
                        

                        Und zu guter letzt noch ein Beispiel, wie es benutzt wird:

                        
                        	class example {
                        		protected $log;
                        		protected $foo;
                        		
                        		public function __construct($bar) {
                        			$this->log = logs::getInstance('example', 'ALL|~DEBUG');
                        			$this->foo = $bar;
                        		}
                        		
                        		public function setFoo($baz) {
                        			$this->log->debug('setting foo to '.$baz);
                        			$this->foo = $baz;
                        		}
                        	}
                        
                        

                        Ich hoffe, durch den Code erkennt man a) dass wir anscheinend aneinander vorbeigeredet haben, denn b) der Destruktor nicht "die Briefe sortiert und eintütet" sondern lediglich "den Stapel vom    Schreibtisch in den Safe schliesst", und, zu guter letzt, dass c) Ich mir manchmal echt schwer tue beim Erklären. Worte sind noch weniger meine Stärke als    Quellcode...

                        Beste Grüsse,     Tobias Hahner

                        1. Tach!

                          Doku liest doch keiner. Da steht sowieso nie das drin, was man wissen will ...
                          Ist das wirklich Dein Ernst? Da hatte ich Dich vollkommen anders eingeschätzt.

                          Programmierer-Doku ist manchmal ausführlicher. Ich bin eher gebranntes Kind bei Anwender-Doku. Da findet man nicht selten solche unbrauchbaren Dinger wie:

                          Eingabefeld Telefonnummer:
                          Geben Sie in dieses Feld die Telefonnummer ein.

                          Ganz großes Kino. Das ist eine "Im-Pflichtenheft-stand-Doku-also-schreiben-wir-zu-jedem-Feld-was"-Alibi-Doku. Als ob man nicht auch bereits anhand der Feldbezeichnung darauf gekommen wäre, was da einzugeben wäre. Ungeklärt geblieben sind dabei aber Fragen wie die nach dem Format (0123-456789 oder +49-123-456789 oder was ganz anderes) und vielleicht noch, wozu die Angabe benötigt wird, wie sie mit anderen Werten zusammenspielt.

                          Mein Destruktor schließt am Ende des Arbeitstages lediglich das Zeug vom Schreibtisch in den Schrank ein. Deiner aber nimmt vorher auch noch den Stapel der erzeugten Geschäftsbriefe, tütet sie in Umschläge, frankiert diese und legt sie in den Postausgang.
                          Nein, das tut er nicht :-)
                          Ich stelle fest, dass ich anscheinend kein guter Erklärbär bin...

                          Vielleicht hast du auch mein Beispiel nicht so verstanden, wie ich es meinte.

                          Da ich anscheinend arge Probleme habe, Programmtechnisches umgangssprachlich zu erklären, zeige ich Dir einfach mal den nicht ganz so stark vereinfachten Code.

                          Dem entnehme ich, dass der Destruktor genau das macht, was ich meinte. Er räumt nicht einfach nur die Arbeitsmittel weg sondern tütet entstandene Daten ein und versendet sie.

                          Vielleicht siehst Du dann, dass er im Endeffekt genau das tut, was Du vorgeschlagen hattest, nur anders verpackt:

                          Was ich da vorgeschlagen hatte war Teil des Arbeitsvorgangs und nicht Teil des Aufräumens.

                          Die Klasse log: (bedenke bitte, sie hat noch die Versionsnummer 0.0.2, ist also durchaus verbesserungswürdig! Zum Beispiel sind konkurrierende Zugriffe auf logfiles noch nicht bedacht...

                          Das Logfile ist die ganze Zeit offen und müsste damit alle anderen Prozesse blockieren. Du öffnest also den Postausgang und blockierst ihn für alle anderen. Oder in deinem Beispiel, du öffnest den Safe und hast tagsüber auch noch ein Sicherheitsproblem geschaffen. (So schnell bekommen Beispiele Fußkrankheiten.) Vielleicht wirst du, um dieses Nebenläufigkeitsproblem zu lösen, wirklich auf meinen Vorschlag eingehen und jede Log-Zeile einzeln schreiben - was unter Umständen auch nicht so toll ist, und einen Dateihandlings-Overhead mit sich bringt, auch wenn der in der Funktion file_put_contents() versteckt ist.

                          Wir müssen das jetzt nicht weiter ausdehen, denn anscheinend kommen wir hier nicht auf einen grünen Zweig.

                          dedlfix.

                          1. Hallihallo!

                            Mein Destruktor schließt am Ende des Arbeitstages lediglich das Zeug vom Schreibtisch in den Schrank ein. Deiner aber nimmt vorher auch noch den Stapel der erzeugten Geschäftsbriefe, tütet sie in Umschläge, frankiert diese und legt sie in den Postausgang.
                            Nein, das tut er nicht :-)
                            Ich stelle fest, dass ich anscheinend kein guter Erklärbär bin...

                            Vielleicht hast du auch mein Beispiel nicht so verstanden, wie ich es meinte.

                            Dem entnehme ich, dass der Destruktor genau das macht, was ich meinte. Er räumt nicht einfach nur die Arbeitsmittel weg sondern tütet entstandene Daten ein und versendet sie.

                            Ich habe über genau diesen Satz nochmal gründlich nachgedacht, und dabei festgestellt, dass ich Dich tatsächlich falsch verstanden hatte. Ich dachte die ganze Zeit, Du meinst mit dem "Eintüten" das Sortieren der Logeinträge, und mit dem Versenden das Verteilen an die verschiedenen Empfänger (was in meinem Fall die Mail an den Nutzer, die Mail an den Admin und die Datei im Logverzeichnis war).
                            Jetzt, da ich Deinen Einwand verstanden habe, sieht die Sache anders aus

                            Vielleicht siehst Du dann, dass er im Endeffekt genau das tut, was Du vorgeschlagen hattest, nur anders verpackt:

                            Was ich da vorgeschlagen hatte war Teil des Arbeitsvorgangs und nicht Teil des Aufräumens.

                            Japp, das sehe ich jetzt auch. Ich halte das für verständlich, was Du da meinst, aber nicht für gut.

                            Die Klasse log: (bedenke bitte, sie hat noch die Versionsnummer 0.0.2, ist also durchaus verbesserungswürdig! Zum Beispiel sind konkurrierende Zugriffe auf logfiles noch nicht bedacht...

                            Das Logfile ist die ganze Zeit offen und müsste damit alle anderen Prozesse blockieren. Du öffnest also den Postausgang und blockierst ihn für alle anderen. Oder in deinem Beispiel, du öffnest den Safe und hast tagsüber auch noch ein Sicherheitsproblem geschaffen.

                            Genau dieser Einwand ist der Grund, weshalb ich es mir nicht verkneifen konnte, hier nochmal zu antworten.
                            Das Logfile ist nämlich NICHT die ganze Zeit offen, sondern nur während der Laufzeit des Destruktors! Der Fall, den Du vielleicht meinst, tritt nur dann auf, wenn beim Erstellen des Objekts ausdrücklich $paranoia gefordert ist. In den Kommentaren habe ich sogar extra geschrieben, dass man das eben NICHT tun soll (Kommentare sind doch keine Anwender-Dokus, oder? *SCNR*). Eingebaut ist diese Option lediglich für die Phase des Debuggens, damit ich auch im Falle eines fatalen Fehlers sehen konnte, was Phase war. Also nochmal: während der Lebenszeit des Log-Objekts werden nur Daten gesammelt und sortiert (auf seinen Schreibtisch geworfen), und wenn es Feierabend macht (__destruct), heftet es seinen bereits sortierten Papierkram in eine Akte und stellt sie in den Schrank (Datei speichern).

                            Vielleicht wirst du, um dieses Nebenläufigkeitsproblem zu lösen, wirklich auf meinen Vorschlag eingehen und jede Log-Zeile einzeln schreiben - was unter Umständen auch nicht so toll ist, und einen Dateihandlings-Overhead mit sich bringt, auch wenn der in der Funktion file_put_contents() versteckt ist.

                            Genau wegen dieses Overheads habe ich diesen "Aufwand" ja betrieben. Ich wollte ihn nicht, weil er das ganze System elend langsam macht, und ausserdem voneinander völlig unabhängige Requests in einer einzigen Datei wild durcheinander mixt. Und komm´ mir jetzt nicht mit "jeder Request seine eigene Datei" ;) Das war mein erster Versuch, und er war widerlich langsam und das Chaos war unbeschreiblich...
                            Besonders auf meinem Raspi kämpfe ich hier nicht um Millisekunden, sondern um Sekunden. Das Nebenläufigkeitsproblem habe ich übrigens flock für mich lösen lassen. Dafür wurde es schliesslich erfunden :-)

                            Wir müssen das jetzt nicht weiter ausdehen, denn anscheinend kommen wir hier nicht auf einen grünen Zweig.

                            Da stimme ich mit Dir überein. Ich wollte eigentlich auch die Klappe halten, aber Deine falsche Analyse meines Quelltextes wollte ich so nicht im Raum stehen lassen :-)

                            Beste Grüsse, und ein schönes Wochenende,
                                Tobias Hahner