Ankündigung

Einklappen
Keine Ankündigung bisher.

[Erledigt] Singleton + Destruktor

Einklappen

Neue Werbung 2019

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

  • [Erledigt] Singleton + Destruktor

    Hallo,

    zu logging Zwecken habe ich in meinem aktuellen Projekt eine Log Klasse implementiert:
    PHP-Code:
    <?php
    class Vp_Log {
        private static 
    $instance;
        private 
    $startTime;
        private 
    $fileHandle null;
        private 
    $logLevel;

        public static function 
    getInstance() {
            
    var_dump(self::$instance);
            if (
    self::$instance === null) {
                
    self::$instance = new self;
                
    self::$instance->Init(5);
            }
            return 
    self::$instance;
        }

        private function 
    __construct() { }

        private function 
    __clone() { }

        public function 
    Init($logLevel) {
            
    $this->setLogLevel($logLevel);
            if (
    $this->getLogLevel() > 0) {
                
    $this->startTime microtime(true);
                
    $this->setFileHandle(fopen(BASE_PATH '/' 'log''ab'));
                if (
    $this->getFileHandle() == false) {
                    throw new 
    Vp_Exception('Couldn´t open logfile');
                }
                
    $strToFile date('d-m-Y_H:i:s') . ' site request from ip ' $_SERVER['REMOTE_ADDR'] . "\n";
                
    fwrite($this->getFileHandle(), $strToFile);
            }
        }

        public function 
    __destruct() {
            if (
    $this->getLogLevel() > 0) {
                
    $neededTime microtime(true) - $this->getStartTime();
                
    $strToFile date('d-m-Y_H:i:s') . ' needed ' $neededTime ' seconds for execution' "\n";
                
    fwrite($this->getFileHandle(), $strToFile);
                
    fclose($this->getFileHandle());
            }
            echo 
    'Destroying Vp_Log' "\n";
        }

        public function 
    setStartTime($time) {
            
    $this->startTime $time;
        }

        public function 
    getStartTime() {
            return 
    $this->startTime;
        }

        public function 
    setFileHandle($fileHandle) {
            
    $this->fileHandle $fileHandle;
        }

        public function 
    getFileHandle() {
            return 
    $this->fileHandle;
        }

        public function 
    setLogLevel($level) {
            
    $this->logLevel $level;
        }

        public function 
    getLogLevel() {
            return 
    $this->logLevel;
        }

        public function 
    write($message$level) {
            if (
    $level <= $this->getLogLevel()) {
                
    fwrite($this->getFileHandle(), date('d-m-Y_H:i:s') . ' ' $message "\n");
            }
        }

        public static function 
    w($message$level) {
            
    self::getInstance()->write($message$level);
        }
    }
    das passt auch soweit alles. Problem ist nur das zu Ende des Skriptes der Destruktor aufgerufen wird, das eigentliche Objekt aber immer noch im Speicher vorhanden zu sein scheint. Siehe an folgender Ausgabe:
    Code:
    NULL
    object(Vp_Log)#1 (3) {
      ["startTime":"Vp_Log":private]=>
      float(1271229645.0748)
      ["fileHandle":"Vp_Log":private]=>
      resource(4) of type (stream)
      ["logLevel":"Vp_Log":private]=>
      int(5)
    }
    
    --- weitere Aufrufe der Logklasse und danach der HTML-Code der Seite ---
    
    Destroying Vp_Log // ab jetzt sollte Vp_Log::$instance wieder null sein, ist aber nicht der Fall
    object(Vp_Log)#1 (3) {
      ["startTime":"Vp_Log":private]=>
      float(1271229645.0748)
      ["fileHandle":"Vp_Log":private]=>
      resource(4) of type (Unknown)
      ["logLevel":"Vp_Log":private]=>
      int(5)
    }
     
    Warning: fwrite(): 4 is not a valid stream resource in C:\workspace\web\netboot\abc\Vp\Log.php on line 70
    Der GC räumt also nach und nach den Speicher auf und "löscht" das Objekt der Vp_Log Klasse, der Destruktor wird aufgerufen und das FileHandle wird geschlossen. Die Objekte der anderen Klassen werden auch gelöscht und deren Destruktoren werden aufgerufen, in diesen wird jeweils ein Logeintrag gemacht, was nicht funktioniert, da der Destruktor der Vp_Log Klasse schon aufgerufen wurde.

    Das seltsame daran ist, dass die statische Variable $instance aus der Vp_Log Klasse eben nicht leer ist nachdem der Destruktor aufgerufen wurde.

    Jemand eine Idee wieso das so ist?


  • #2
    Der Destruktor wird doch von PHP automatisch am ende des Skripts aufgerufen, also wenn das Skript durchgelaufen ist, wenn ich mich nicht irre. Wenn du also deine Log - Klasse ein weiteres mal aufrufst, hast du auch ein weiteres mal zur Laufzeit Zugriff auf dieses Objekt und die Methoden werden auch ausgeführt, da das Objekt ja noch besteht. Der Destruktor gibt den Speicher doch nur zum Ende des Skripts wieder frei, wenn das Objekt nicht mehr gebraucht wird. Aber du rufst es ja zwei mal auf, also wird das Objekt auch zwei mal verwendet.

    Kommentar


    • #3
      Mir gehts darum, dass der Destruktor aufgerufen wird von PHP, das Objekt aber nicht gelöscht wird und somit auch der FileHandle im Konstruktor kein weiteres mal geöffnet wird, weil das Objekt ja noch da ist.

      Würde das Objekt gelöscht werden, nachdem der Destruktor aufgerufen wurde, wäre ja alles wie erwartet und das Logfile würde beim nächsten Aufruf des Log Objekts wieder geöffnet.

      Kommentar


      • #4
        Poste mal den Code, der die obige Ausgabe macht...

        Kommentar


        • #5
          Ich kann jetzt hier wohl schlecht den ganzen Projektordner posten, aber rein vom Aufbau her wird bei jedem Seitenaufruf
          PHP-Code:
          Vp_Log::getInstance(); 
          aufgerufen danach wird der FrontController gestartet, der dann die zuständigen ActionController aufruft. Diese Controller und die meisten anderen Klassen haben folgendes im Destruktor stehen
          PHP-Code:
          Vp_Log::w('destroying ' get_called_class(), 1); 
          Auf das Objekt wird während des Skriptablaufs immer mal wieder zugegriffen, wie das beim Logging eben so ist.

          Kommentar


          • #6
            Zitat von peaceman Beitrag anzeigen
            Die Objekte der anderen Klassen werden auch gelöscht und deren Destruktoren werden aufgerufen, in diesen wird jeweils ein Logeintrag gemacht
            Das ist m.E. die Unsauberkeit, die es zu beseitigen gilt.

            Destruktoren haben nichts mehr irgendwo hin zu schreiben, sondern nur das Objekt aufzuräumen und zum „Wegwerfen“ vorzubereiten.

            Kommentar


            • #7
              Die Objekte der anderen Klassen werden auch gelöscht und deren Destruktoren werden aufgerufen, in diesen wird jeweils ein Logeintrag gemacht
              Destruktoren haben nichts mehr irgendwo hin zu schreiben, sondern nur das Objekt aufzuräumen und zum „Wegwerfen“ vorzubereiten.
              oder um es anders zu sagen: Du kannst nicht kontrollieren, wann der GC ein Objekt entsorgt. Wenn Du also alle abhängigen Objekte nicht vorher löschst und damit den Destruktor antriggerst, bleibt wohl nur die Aussage, dass Du keine solche Abhängigkeiten erzeugen darfst. So weit wie ChrisB würde ich nicht gehen, IMHO sollte es aber nur 1 Objekt geben, das ggf. am Ende der Applikation die Logging-Finalisierung vornimmt.
              --

              „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
              Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


              --

              Kommentar


              • #8
                Ich will das Vorgehen des GC auch garnicht kontrollieren, nur macht er in diesem Fall etwas für mich nicht nachvollziehbares.

                Bisher bin ich immer davon ausgegangen, dass der Speicher den ein Objekt belegt unmittelbar nach dem Aufruf des Destruktors geleert wird. Scheint aber eben nicht so zu sein und darin liegt das Problem meines Skriptes.

                Kommentar


                • #9
                  dass der Speicher den ein Objekt belegt unmittelbar nach dem Aufruf des Destruktors geleert wird.
                  Was hat das mit dem Speicher zu tun?
                  Zudem würde ich vermuten, dass Log neu instanziiert wird, nachdem das Objekt zerstört wurde. Da v ja statisch ist und getInstance aufruft, ist das durchaus möglich. Die Frage ist dann, warum keine Fileressource mehr erstellt werden kann, allerdings ist das am Rande der Laufzeit vielleicht auch nicht wirklich mehr die Frage.
                  --

                  „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
                  Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


                  --

                  Kommentar


                  • #10
                    Mit Speicher meinte ich die statische Variable.

                    Log wird eben nicht neu instanziiert weil die statische Variable nicht null ist, wie man im ersten Post bei den Ausgaben sehen kann.

                    Kommentar


                    • #11
                      Häh?
                      Code:
                      Log Destruct
                      Objekt Xy Destruct
                        > Log::v
                            > Log::getInstance()
                          Log->write
                      Wenn Du was anderes meinst, hastr Du zuwenig Informationen gepostet.
                      --

                      „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
                      Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


                      --

                      Kommentar


                      • #12
                        Ne das meinte ich schon, nur das nach dem Log Destruct die statische Variable, die das Objekt der Log Klasse beinhaltet nicht null ist sondern noch das alte Objekt inne hat, aber eben mit dem fehlendem FileHandle, da dieses ja vom Destruktor geschlossen wurde.

                        Kommentar


                        • #13
                          Die einzige Lösung die mir aktuell dazu einfällt wäre, dass ich jedem Objekt, das Einträge im Log machen soll, eine Referenz des Log Objekts übergebe. Damit wäre sichergestellt, dass bis zum letzten Logrelevanten Objekt der Destruktor des Log Objektes nicht aufgerufen wurde.

                          Jetzt ist nur noch herauszufinden ob das Verhalten von PHP so gewollt ist oder es sich um einen Bug handelt.

                          Kommentar


                          • #14
                            Die einzige Lösung die mir aktuell dazu einfällt wäre, dass ich jedem Objekt, das Einträge im Log machen soll, eine Referenz des Log Objekts übergebe.
                            OOP-technisch ist das sowieso die bessere Variante. Es ist ja auch durchaus möglich, dass man mal meherere Log-Objekte parallel benutzen möchte. Zudem wird das Logfile damit austauschbar, testbar etc.

                            Wenn Du irgendeine Art von Applikationsobjekt hast, könntest Du auch darüber nachdenken, eine Art finalize() Methode zu implementieren, für die sich komponenten mit einem Callback registrieren können. Log-Objekte dann bspw. mit einem Callback auf write() and close(). Ob Du finalize() dann manualle aufrufst, oder über den Destruktor ist dann Geschmacksfrage.
                            --

                            „Emoticons machen einen Beitrag etwas freundlicher. Deine wirken zwar fachlich richtig sein, aber meist ziemlich uninteressant.
                            Wenn man nur Text sieht, haben viele junge Entwickler keine interesse, diese stumpfen Texte zu lesen.“


                            --

                            Kommentar


                            • #15
                              Ne das meinte ich schon, nur das nach dem Log Destruct die statische Variable, die das Objekt der Log Klasse beinhaltet nicht null ist sondern noch das alte Objekt inne hat, aber eben mit dem fehlendem FileHandle, da dieses ja vom Destruktor geschlossen wurde.
                              Du könntest sie im Destruktor manuell auf null setzen.

                              Ich tippe mal, PHP unterscheidet dort zwischen der Instanz und der Klasse. self::$instance ist ja eine Klassenvariable, gehört also nicht zur Instanz.

                              Aber auch das wird im Zweifel nicht verhindern, dass Vp_Log immer wieder neu initialisiert wird, wenn die Reihenfolge der Destruktoren zufällig ist. (Edit: So verstehe ich das zumindest...)

                              Aus dem Handbuch:

                              The destructor method will be called as soon as all references to a particular object are removed or when the object is explicitly destroyed or in any order in shutdown sequence.
                              Eigentlich doch recht eindeutig.

                              Das folgende Beispiel geht bei mir, wobei ich nicht weiß, ob das besonders viel aussagt.

                              PHP-Code:
                              <?php

                              class Vp_Log
                              {
                                  private static 
                              $instance;
                                  private 
                              $fileHandle null;

                                  public static function 
                              getInstance() {        
                                      if (
                              self::$instance === null) {
                                          
                              self::$instance = new self();
                                          
                              self::$instance->Init(5);
                                      }
                                      return 
                              self::$instance;
                                  }

                                  private function 
                              __construct() { }

                                  private function 
                              __clone() { }

                                  protected function 
                              Init($level) {
                                      echo 
                              __METHOD__ "\n";
                                      
                              $this->fileHandle 'something';
                                  }

                                  public function 
                              log($s) {
                                      echo 
                              $s "\n";
                                  }

                                  public function  
                              __destruct() {
                                      echo 
                              __METHOD__ "\n";
                                      
                              $this->fileHandle null;
                                  }
                              }

                              class 
                              A
                              {
                                  public function 
                              __destruct() {
                                      
                              Vp_Log::getInstance()->log(__METHOD__);
                                  }
                              }

                              $a = new A();
                              unset(
                              $a);

                              echo 
                              "Programmende\n";
                              Edit: Es ist in Sprachen mit automatischer Speicherverwaltung (bzw. Garbage Collector) immer so eine Sache, sich darauf zu verlassen, dass Objekte dann zerstört werden, wenn es "logisch" wäre. Ich weiß nicht, ob unset in PHP mit einem Befehl wie free oder destroy in anderen Sprachen gleichzusetzen ist. (Ist es nicht, es löst lediglich eine Referenz auf.)

                              Kommentar

                              Lädt...
                              X