php destructor behaviour

2019-02-07 03:54发布

问题:

im trying to understand php constructor and destructor behaviour. Everything goes as expected with the constructor but i am having trouble getting the destructor to fire implicitly. Ive done all the reading on php.net and related sites, but i cant find an answer to this question.

If i have a simple class, something like:

class test{

     public function __construct(){
          print "contructing<br>";
     }

     public function __destruct(){
          print "destroying<br>";
     }
}

and i call it with something like:

$t = new test;

it prints the constructor message. However, i'd expect that when the scripts ends and the page is rendered that the destructor should fire. Of course it doesnt.

If i call unset($t); when the scripts ends, of course the destructor fires, but is there a way to get it to fire implicitly?

thanks for any tips

回答1:

This is pretty easy to test.

<?php

class DestructTestDummy {
    protected $name;

    function __construct($name) {
        echo "Constructing $name\n";
        $this->name = $name;
    }

    function __destruct() {
        echo "Destructing $this->name\n";
        //exit;
    }
}

echo "Start script\n";

register_shutdown_function(function() {
    echo "Shutdown function\n";
    //exit
});

$a = new DestructTestDummy("Mr. Unset");
$b = new DestructTestDummy("Terminator 1");
$c = new DestructTestDummy("Terminator 2");

echo "Before unset\n";
unset($a);
echo "After unset\n";


echo "Before func\n";
call_user_func(function() {
    $c = new DestructTestDummy("Mrs. Scopee");
});
echo "After func\n";

$b->__destruct();

exit("Exiting\n");

In PHP 5.5.12 this prints:

Start script
Constructing Mr. Unset
Constructing Terminator 1
Constructing Terminator 2
Before unset
Destructing Mr. Unset
After unset
Before func
Constructing Mrs. Scopee
Destructing Mrs. Scopee
After func
Destructing Terminator 1
Exiting
Shutdown function
Destructing Terminator 2
Destructing Terminator 1

So we can see that the destructor is called when we explicitly unset the object, when it goes out of scope, and when the script ends.



回答2:

The __destruct() magic function is executed when the object is deleted/destroyed (using unset). It is not called during shutdown of a script. When a PHP script finishes executing, it cleans up the memory, but it doesn't 'delete' objects as such, thus the __destruct() methods aren't called.

You may be thinking of the register_shutdown_function(), which is fired when your PHP script finishes executing.

function shutdown()
{
    // code here
    echo 'this will be called last';
}

register_shutdown_function('shutdown');


回答3:

My understanding is that destructors are automatically called for any remaining objects when the script ends.

Looking though the manual page on constructors and destructors, it seems the only way to bypass destructors entirely is if you call exit() from the destructor of an object that is destroyed prior to the object in question.

Are you using exit() in any of your destructors? Are there even multiple objects in your script?

If it's not too much trouble, perhaps you could post the actual code in question rather than the sample code you have in your question now. Aside from the typo in your sample constructor, that code should call both the constuctor and destructor for your test object.



回答4:

The __destruct method of a class is called once all references to the object are unset.

For example

$dummy = (object) new Class();

The destructor is automatically called if the dummy object is set to null or the script is exited.

unset($dummy); // or $dummy = null;
//exit(); //also possible

However, there are three crucial memory notes to calling the destructor method:

Firstly, the desctructor method should be a public method, not protected or private.

Secondly, refrain from using internal and circular references. For example:

class NewDemo
{
     function __construct()
     {
          $this->foo = $this;
     } 
     function __destruct()
     {
          // this method will never be called 
          // and cause memory leaks
          // unset will not clear the internal reference
     }
}

The following will not work either:

$a = new Class();
$b = new Class();
$a->pointer = $b;
$b->pointer = $a;

unset($a); // will not call destructor
unset($b); // will not call destructor

Thirdly, deciding whether destructors are called after output is sent. Using

gc_collect_cycles() 

one can determine whether all destructors are called before sending data to user.

See http://php.net/manual/en/language.oop5.decon.php for the sources and elaborate explanations of magic destruct methods with examples.