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

  • peaceman
    hat ein Thema erstellt [Erledigt] Singleton + Destruktor.

    [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?

  • nikosch
    antwortet
    Ja.

    Einen Kommentar schreiben:


  • mermshaus
    antwortet
    Zitat von nikosch
    Objekte sind immer Referenzen
    Puuuh. Ich würde eher sagen, Objekte (oder besser: Klasseninstanzen) sind immer Referenten (= das, auf das referiert wird).

    Mein letzter Absatz war allerdings arg schwammig, ich gebe es zu. Wer's genauer wissen will, kann den Teil über Garbage Collection und zyklische Referenzen (in PHP < 5.3) und so lesen.

    Vielleicht lässt sich sagen: Es gibt in PHP (meines Wissens) keine sichere Möglichkeit, ein Objekt manuell zu zerstören (im Sinne einer Deallokation des Speichers bzw. im Sinne eines Auslösens des Destruktors). Es kann an anderer Stelle immer noch eine Referenz bestehen, was die Zerstörung verhindert.

    Einen Kommentar schreiben:


  • nikosch
    antwortet
    Objekte sind immer Referenzen

    Kleiner Tipp: Benutz gleich zusätzliche eine Registry, sonst bekommst Du schnell das Problem, Objekte von Konstruktor zu Konstruktor durchreichen zu müssen.

    Einen Kommentar schreiben:


  • peaceman
    antwortet
    Wer hätte es gedacht
    PHP-Code:
    self::$instance null
    funktioniert im Destruktor. Werde mir aber dennoch die Idee mit der Referenzübergabe nochmal genauer anschauen.

    Einen Kommentar schreiben:


  • mermshaus
    antwortet
    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.)

    Einen Kommentar schreiben:


  • nikosch
    antwortet
    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.

    Einen Kommentar schreiben:


  • peaceman
    antwortet
    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.

    Einen Kommentar schreiben:


  • peaceman
    antwortet
    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.

    Einen Kommentar schreiben:


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

    Einen Kommentar schreiben:


  • peaceman
    antwortet
    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.

    Einen Kommentar schreiben:


  • nikosch
    antwortet
    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.

    Einen Kommentar schreiben:


  • peaceman
    antwortet
    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.

    Einen Kommentar schreiben:


  • nikosch
    antwortet
    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.

    Einen Kommentar schreiben:


  • ChrisB
    antwortet
    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.

    Einen Kommentar schreiben:

Lädt...
X