php destructor behaviour - php

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?

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.

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');

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.

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.

Related

Reused object will not call `__destruct`?

I was trying to make a "pool" like structure in a CLI program, which includes lots of "borrowing" and "recycling". And while testing things, I encountered something quite unexpected:
<?php
class FOO
{
public static $pool=[];
public static function get()
{
if(empty(self::$pool))
{
self::$pool[]=new self(mt_rand(1000,9999));
}
return array_shift(self::$pool);
}
protected static function recycle(FOO $foo)
{
echo "Recycling {$foo->num}\n";
self::$pool[]=$foo;
}
public $num;
protected function __construct(int $num)
{
$this->num=$num;
}
public function __destruct()
{
static::recycle($this);
}
}
function Bar()
{
$foo=FOO::get();
echo "Got {$foo->num}\n";
}
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
print_r(FOO::$pool);
echo "End.\n";
The output is:
Bar
Got 2911
Recycling 2911
Bar
Got 2911
Bar
Got 1038
Recycling 1038
Bar
Got 1038
Array
(
)
End.
If I limit the Bar() call to 3 times instead of 4, I got the following output:
Bar
Got 7278
Recycling 7278
Bar
Got 7278
Bar
Got 6703
Recycling 6703
Array
(
[0] => FOO Object
(
[num] => 6703
)
)
End.
Here you can see, when an object is "reused" (see 2911 and 1038 in example 1, 7278 in example 2), its __destruct() will not be called; instead, it will simply disappear.
I'm confused. Why would this happen? Why wouldn't FOO->__destruct got called every time? And is it possible to make instances of FOO auto recycle itself?
I'm testing this in PHP-7.2, behavior observed both in Windows and WSL-Ubuntu.
As far as I know destructors will not be called twice for the same object. It is generally a bad practice to reuse objects from the destructor. An object whose destructor was called should be destroyed, not reused. In your test script it doesn't cause any serious issues, but in real life, such usage could lead to unexpected behaviours if the developer is not careful.
At first when I read your question, I was worried that you have created a memory leak, but that is not the case. The destructor is called as soon as you leave the scope of Bar() only if it hasn't been called yet on this object. However, because you save the reference and increase the ref_count of this object inside of the __destruct() the garbage collector cannot collect the object yet. It must wait until the next time when you decrease the ref_count of this object.
The process can be explained as follows (simplified):
You call Bar() function which creates an instance of FOO. An object is created and assigned to $foo. (ref_count = 1)
Upon the end of Bar() the only reference to this object is lost (ref_count = 0), which means PHP starts the process of destroying the object.
2.1. The destructor is called. Inside of the destructor you increase the ref_count to 1.
2.2. The next step would be for the GC to collect the object, but the ref_count is not zero. This could mean a cycle or like in your example, a new reference has been created in the destructor. GC must wait until there are no uncyclical references to collect the object.
You call Bar() again. You shift the same object from static array, which means that the reference inside of FOO::$pool is gone, but you immediately assign it to $foo. (ref_count = 1)
Upon the end of Bar() the only reference to this object is lost (ref_count = 0), which means GC can finally collect this object. The destructor has already been called, so there is no other action to be taken here.
You didn't recycle the object, you merely prolonged its existance in the memory. You could access it for a little longer, but for PHP that object was already in the process of being destroyed.

MySQLi close in destructor fires immediately

I've recently been redesigning an archaic PHP application, which had most of it's code in 1000+ line long PHP files for each page. I first started by refactoring much of the code into classes. I recently have been working on database connections, and began writing a class for it. I decided to throw $mysqli->close() into the destructor (I'm using the OO approach).
Unfortunately I've almost immediately ran in to problems with it. Instead of the MySQLi connection closing when the page finishes rendering (or when there are no more references to the DB object), it just immediately closes. I've tested this through writing a simple test:
$db = new DBConnect(); //My abstraction class
$db->getSQL()->query("SELECT 1"); //Query fails. Error message states connection closed.
My destructor looks like this:
public function __destruct() {
$this->mysqli->close();
}
My constructor looks like this:
public function __construct() {
$this->mysqli=new mysqli(\MainConfig::$database['host'], \MainConfig::$database['user'], \MainConfig::$database['pass'], \MainConfig::$database['db']);
if($this->mysqli->connect_error) {
die('Connect error (' . $this->mysqli->connect_errno . ') ' . $this->mysqli->connect_error); //This is not ever fired.
}
}
I know it's not some other bit of code closing the connection, since if I comment out the $this->mysqli->close() line, then the code works as expected. It seems to me that the destructor fires immediately which is not the desired behavior. Am I misunderstanding how they are intended to work?
I was able to reproduce the behavior you described with the code:
<?php
class DestructorTest {
public function getter() {
print "DestructorTest::getter() called\n";
return new CallableTest();
}
public function __destruct() {
print "DestructorTest destructor called\n";
}
}
class CallableTest {
public function method() {
print "CallableTest::method() called\n";
}
}
(new DestructorTest())->getter()->method();
which prints:
DestructorTest::getter() called
DestructorTest destructor called
CallableTest::method() called
The long and the short of it is: A destructor may be called as soon as there are no references to an object. This can even happen in the middle of a line -- in this case, for instance, after DestructorTest:getter() is called, the DestructorTest object is no longer reachable, so it is destroyed.
My advice:
You don't need to close MySQLi handles. They already have an internal destructor which will close them when they are garbage-collected.
In fact: avoid writing destructor methods in general. (This applies to many programming languages, not just PHP.) They are frequently not called when you expect them to be, and have a tendency to cause strange, hard-to-debug behavior.
If you're going to ignore this and write a destructor anyways, then you need to make sure that you don't inadvertently destroy something which you've "leaked" a reference to outside your class. In the case of your code, you've allowed code outside the class to get a reference to your MySQLi handle, but you're destroying that handle as soon as your object goes away -- even if the handle is still being used outside.

Will this PHP script leak memory?

I have a PHP script that runs in the background for a while (usually a few minutes, but this could be up to an hour or so). It contains a loop in which I need to create an object. I'm currently using the same name every time like this:
while (!$job_finished) {
$x = new MyClass();
$x->doStuff();
$x->doMoreStuff();
unset ($x);
// more code here
}
Since I'm creating $x repeatedly with the same name, will garbage collection properly clean up memory? Or should I use an array over $x such as
$x[$i] = new MyClass();
Actually I don't need to use the array. The unset() command will destroy the object and therefore I shouldn't worry. This is in the PHP documentation where it states:
The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.
I tested it with the following code and it shows the destruct method being called before the end of the script indeed.
<?php
class A {
function __destruct() {
echo "cYa later!!\n";
}
}
$a = new A();
unset($a);
echo "hello";
sleep(10);
Yes, if you are keeping object references to an array, it will not release memory & will fail in last.
However, your code example shown will not issue memory problems as you are not keeping object's reference & overriding it always in loop.

PHP Destructor is not called

I'm Using PHP 5.4.12
I have two class
<?php
class MySessionHandler implements SessionHandlerInterface {
//..
}
$handler = new MySessionHandler();
session_set_save_handler($handler,true);
?>
And
<?php
class MySession {
//..
function __destruct() {
session_write_close();
echo 'called';
}
}
And with this code, MySession's destructor is never called.
<?php
require_once 'MySessionHandler.php';
include_once 'MySession.php';
$test = new MySession();
But with this code, "MySession" destructor is called ok
<?php
require_once 'MySessionHandler.php';
include_once 'MySession.php';
class Test {
function __construct() {
$test = new MySession();
}
}
$obj = new Test();
I have tested session_set_save_handler($handler,true/false).
I need MySession's destructor to be called 'cause I have issues with session_write_close(); I have to call it explicitly or the session never will be written to the server.
any workaround about this? I have simplified the code to their roots, but the __destruct method is not called when should be.
Dont depend on __destruct... From the manual:
When using objects as session save handlers, it is important to register the shutdown function with PHP to avoid unexpected side-effects from the way PHP internally destroys objects on shutdown and may prevent the write and close from being called. Typically you should register 'session_write_close' using the register_shutdown_function() function.
In your case above, you need to instantiate the object:
$test = new MySession();
by calling the __destruct() function itself
in my case __destruct() is not called when working in child file came from different file location.
I think __destruct() can only work on a single file page.

How reliable is __destruct?

Are there situations in which this method would not be called?
I'm thinking to store a important variable into a persistent cache just before the cache object gets destroyed. This variable is used many times in the page so I wouldn't like to update the cache with it every time the variable changes...
Let's have a class:
class A {
public function __construct(){
echo "Construct\n";
}
public function __destruct(){
echo "Destruct\n";
}
}
And test code:
$test = new A();
die( "Dead\n"); // Will output Construct; dead; Destruct
$test = new A();
throw new Exception("Blah\n"); // Construct, Fatal error (no destruct)
$test = new A();
require_once( 'invalid_file.php'); // Construct, Fatal error (no destruct)
So basically: there are situations (fatal errors) when destructor won't be called.
Ah and this question has the same answer as this one: When will __destruct not be called in PHP? (+/-)
It is called as soon as there are no more references to that particular object, or during the shutdown sequence. The manual also states destructors are called when scripts are terminated with exit().
Aside from the issue pointed out by TimWolla, I am not aware of any problems with PHP destructors.
It seems there at least was a problem using Windows: https://github.com/WoltLab/WCF/blob/ff7e6ed381f2ccab7f51220f97087921133b2237/wcfsetup/install/files/lib/system/WCF.class.php#L122
I don't know whether this is still relevant.

Categories