When will __destruct not be called in PHP?

PhpDestructor

Php Problem Overview


class MyDestructableClass {
   function __construct() {
       print "\nIn constructor\n";
       $this->name = "MyDestructableClass";
   }

   function __destruct() {
       print "\nDestroying " . $this->name . "\n";
   }
}

$obj = new MyDestructableClass();

When the above script is in a complex environment,the __destruct won't get called when exit,but I can't reproduce it easily.Have someone ever noticed this ?

EDIT

I'll post the whole stuff here,it's the testing environment of symfony,which means you can easily reproduce it if you are familar with the framework:

require_once dirname(__FILE__).'/../bootstrap/Doctrine.php';
 

$profiler = new Doctrine_Connection_Profiler();

$conn = Doctrine_Manager::connection();
$conn->setListener($profiler);

$t = new lime_test(0, new lime_output_color());

class MyDestructableClass {
   function __construct() {
       print "\nIn constructor\n";
       $this->name = "MyDestructableClass";
   }

   function __destruct() {
       print "\nDestroying " . $this->name . "\n";
   }
}

$obj = new MyDestructableClass();
$news = new News();

$news->setUrl('http://test');
$news->setHash('http://test');
$news->setTitle('http://test');
$news->setSummarize('http://test');
$news->setAccountId(1);
$news->setCategoryId(1);
$news->setThumbnail('http://test');
$news->setCreatedAt(date('Y-m-d H:i:s',time()));
$news->setUpdatedAt(date('Y-m-d H:i:s',time()));
$news->save();
exit();

Php Solutions


Solution 1 - Php

The __destruct will not be called:

  • If exit is called in another destructor
  • Depending on the PHP Version: if exit is called in a shutdown function registered with register_shutdown_function
  • If there is a fatal error somewhere in the code
  • If another destructor throws an exception
  • If you try to handle an exception in a destructor (PHP >= 5.3.0)

Guess that's all I can think of right now

& What Pascal MARTIN said. That's the first step of debugging that.

Solution 2 - Php

The __destruct method will also not be called if script is running on CLI and receives a SIGTERM (Ctrl+C)

Solution 3 - Php

Not having an output on the screen doesn't mean the destructor is not called : the ouptut could be captured using output_buffering (maybe lime does that, to be able to work on it ? ), and not echoed when the script ends, for instance.

For testing purposes, you could try writing to a file, in your __destruct method, instead of just echoing some text.
(Just make sure your application / PHP has the required privileges to write to your destination file)

(I've already run into situations where I would not see the output made in a destructor -- but it was actually called)

Solution 4 - Php

As the PHP documentation says:

> The destructor will be called even if script execution is stopped using exit(). Calling exit() in a destructor will prevent the remaining shutdown routines from executing.

Solution 5 - Php

I know I'am a little late to the party but for people who are also looking to get __destruct to be executed when CTRL+C and/or Fatal errors occur, you can try this (below is a test case):

Index.php

<?php

// Setup CTRL+C and System kill message handler
// The only signal that cannot be caught is the SIGKILL (very hard kill)
declare(ticks = 1); // Required else it won't work.
pcntl_signal(SIGTERM, 'close'); // System kill (Unhappy Termination)
pcntl_signal(SIGINT, 'close'); // CTRL+C (Happy Termination)

// Shutdown functions will be executed even on fatal errors
register_shutdown_function('close');

function close($signal = null) // only pcntl_signal fills $signal so null is required
{
    // Check if there was an fatal error (else code below isn't needed)
    $err = error_get_last();
    if(is_array($err))
    {
        foreach(array_keys($GLOBALS) as $key)
        {
            if(in_array($key, ['_GET', '_POST', '_COOKIE', '_FILES', '_SERVER', '_REQUEST', '_ENV', 'GLOBALS']))
                continue;

            // This will automatically call __destruct
            unset($GLOBALS[$key]);
        }
    }
}

// Example
class blah
{
    private $id = '';

    public function __construct()
    {
        $this->id = uniqid();
        // note this piece of code, doesn't work on windows!
        exec('mkdir /tmp/test_'.$this->id);
    }

    public function __destruct()
    {
        // note this piece of code, doesn't work on windows!
        exec('rm /tmp/test_'.$this->id.' -R');
    }
}

// Test
$a = new blah();
$b = new blah();
$c = new blah();
$d = new blah();
$e = new blah();
$f = new blah();
$g = new blah();
$h = new blah();
$i = new blah();
$j = new blah();
$k = new blah();
$l = new blah();
$m = new blah();
$n = new blah();
$o = new blah();
$p = new blah();
$q = new blah();
$r = new blah();
$s = new blah();
$t = new blah();
$u = new blah();
$v = new blah();
$w = new blah();
$x = new blah();
$y = new blah();
$z = new blah();

// The script that causes an fatal error
require_once(__DIR__.'/test.php');

Test.php

<?php

// this will create a parse (E_PARSE) error.
asdsaddsaadsasd

Note: calling exit or throwing exceptions in destructors or shutdown functions will cause the script to terminate immediately.

Solution 6 - Php

If you pass a reference of an instance of a class to another class and call exit outside of that class, the __destruct of the class will not be called. It looks like a bug to me.

$a = new classa();
$b = new classb($a);  // where classb constructor is __construct(&a) {}
exit;                 

neither destructors will be called, and there is no guarantee of order of destruction. I have tried many ways to just echo a message in the destructor and it never prints unless I explicitly say:

unset($b);     // properly calls classb::__destruct
unset($a);     // properly calls classa::__destruct
exit;

Since I cannot get any results, I cannot tell if it is a race condition of destructors or just the expected results; Anyway, unset() always calls the destructor properly. I know it is a pain, but better than living with this bug. They need to handle reference counts to classes and dependency order properly when exit is called and call the destructors in the proper order.

Solution 7 - Php

Don't familiar with the Doctrine, but check one point: check for possible exceptions in __construct()/__destruct() they can produce fatal errors.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser198729View Question on Stackoverflow
Solution 1 - PhpedorianView Answer on Stackoverflow
Solution 2 - Phpnickel715View Answer on Stackoverflow
Solution 3 - PhpPascal MARTINView Answer on Stackoverflow
Solution 4 - PhpArek - Krakiewicz.plView Answer on Stackoverflow
Solution 5 - PhpWhiteFangView Answer on Stackoverflow
Solution 6 - PhpSamView Answer on Stackoverflow
Solution 7 - PhpAlexey SheinView Answer on Stackoverflow