When will __destruct not be called in PHP? - php

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

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.

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

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)

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.

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.

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.

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

Related

PHPUnit expect exception failing due to foreach warning

I want to test that a method called foo() throws an exception. The problem is that I can't get PHPUnit expectException() to catch the exception.
foo() looks something like this:
public function foo()
{
$params = $this->readAndFormatConfig();
// exception actually gets thrown in this method
$this->method->throws->exception($params);
}
If I catch the exception manually it works fine, like this:
public function testFoo()
{
$badConfig = new Config([]);
$driver = new bar($badConfig);
$exceptionThrown = false;
try {
$driver->foo();
} catch (Exception $e) {
$exceptionThrown = true;
}
$this->assertTrue($exceptionThrown);
}
If I catch it using expectException, like this:
public function testFoo()
{
$badConfig = new Config([]);
$driver = new bar($badConfig);
$this->expectException(Exception::class);
$driver->foo();
}
the test fails and I get this exception:
MyTestClass::testFoo Invalid argument supplied for foreach()
The output of get_class($e) is PHPUnit_Framework_Error_Warning which surprised me, but explains why the first test works but the second doesn't.
I'd like to either ignore the warning and wait until a real exception is thrown, or get the original warning, not PHPUnit_Framework_Error_Warning.
I'm using php 5.6.32 and PHPUnit 5.7.15
Adding the following to boostrap.php converts warnings to exceptions.
function warningToException($errno, $errstr, $errfile, $errline)
{
throw new Exception($errstr . " on line " . $errline . " in file " . $errfile);
}
set_error_handler("warningToException", E_WARNING);
This allowed the following test pass.
public function testFoo()
{
$badConfig = new Config([]);
$driver = new bar($badConfig);
$this->expectException(Exception::class);
$driver->foo();
}
I think a better method would be to expect PHPUnit_Framework_Error_Warning in the test as ishegg suggested.
What I ended up actually doing was making sure the object I was passing to foreach was traversable before entering the loop.
public function foo()
{
if (is_array($x) || $x instanceof \Traversable) {
// do stuff
} else {
// return false;
}
}
I think it makes more sense to refactor the application code in this case. What I'm really trying to test is that the function foo returns false if the application is misconfigured, so handling the misconfiguration properly seems like the right path in my opinion.
If you stumble upon this question for some reason and really need to convert warnings to an exception class other than PHPUnit_Framework_Error_Warning this is how I did it.
Thanks to ishegg for pointing me in the right direction.

Identifying between PHP Internal Classes and User-defined Classes

I've been trawling through the PHP docs trying to identify if there is any method that will allow me to differentiate between instances of a built-in class (such as DateTime or PDO) and User-defined Classes, but without any success.
The only approach that I have found so far is to try and bind a closure to the instance. When doing this with a built-in class, it displays a warning (yuk) and returns a null.
$targetObject = new DateTime();
$closure = function();
$test = #$closure->bindTo($targetObject, get_class($targetObject));
if ($test === false) {
throw new Exception('General failure');
} elseif ($test === null) {
throw new Exception('Unable to bind to internal class');
}
Is anybody aware of a cleaner approach to this problem?
Take a look at the reflection API. Manual
<?php
$d = new DateTime();
$r = new ReflectionClass($d);
echo ($r->isInternal());

PHP Unit Tests: Is it possible to test for a Fatal Error?

FWIW I'm using SimpleTest 1.1alpha.
I have a singleton class, and I want to write a unit test that guarantees that the class is a singleton by attempting to instantiate the class (it has a private constructor).
This obviously causes a Fatal Error:
Fatal error: Call to private FrontController::__construct()
Is there any way to "catch" that Fatal Error and report a passed test?
No. Fatal error stops the execution of the script.
And it's not really necessary to test a singleton in that way. If you insist on checking if constructor is private, you can use ReflectionClass:getConstructor()
public function testCannotInstantiateExternally()
{
$reflection = new \ReflectionClass('\My\Namespace\MyClassName');
$constructor = $reflection->getConstructor();
$this->assertFalse($constructor->isPublic());
}
Another thing to consider is that Singleton classes/objects are an obstacle in TTD since they're difficult to mock.
Here's a complete code snippet of Mchl's answer so people don't have to go through the docs...
public function testCannotInstantiateExternally()
{
$reflection = new \ReflectionClass('\My\Namespace\MyClassName');
$constructor = $reflection->getConstructor();
$this->assertFalse($constructor->isPublic());
}
You can use a concept like PHPUnit's process-isolation.
This means the test code will be executed in a sub process of php. This example shows how this could work.
<?php
// get the test code as string
$testcode = '<?php new '; // will cause a syntax error
// put it in a temporary file
$testfile = tmpfile();
file_put_contents($testfile, $testcode);
exec("php $tempfile", $output, $return_value);
// now you can process the scripts return value and output
// in case of an syntax error the return value is 255
switch($return_value) {
case 0 :
echo 'PASSED';
break;
default :
echo 'FAILED ' . $output;
}
// clean up
unlink($testfile);

PHP: 'or' statement on instruction fail: how to throw a new exception?

Everyone here should know the 'or' statemens, usually glued to an die() command:
$foo = bar() or die('Error: bar function return false.');
The most of the times we see something like:
mysql_query('SELECT ...') or die('Error in during the query');
However, i cant understand how exactly that 'or' statement works.
I would like to throw a new exception instead of die(), but:
try{
$foo = bar() or throw new Exception('We have a problem here');
Doesnt work, and neither
$foo = bar() or function(){ throw new Exception('We have a problem here'); }
The only way i found to do that is this horrible thought:
function ThrowMe($mess, $code){
throw new Exception($mess, $code);
}
try{
$foo = bar() or ThrowMe('We have a problem in here', 666);
}catch(Exception $e){
echo $e->getMessage();
}
But there is a way to throw a new exception directly after the 'or' statement?
Or this kind of structure is mandatory (i dont liek the ThrowMe function at all):
try{
$foo = bar();
if(!$foo){
throw new Exception('We have a problem in here');
}
}catch(Exception $e){
echo $e->getMessage();
}
Edit: what i want is really to avoid the use of an if() check every potential dangerous operation i do, for example:
#The echo $e->getMessage(); is just an example, in real life this have no sense!
try{
$foo = bar();
if(!$foo){
throw new Exception('Problems with bar()');
}
$aa = bb($foo);
if(!$aa){
throw new Exception('Problems with bb()');
}
//...and so on!
}catch(Exception $e){
echo $e->getMessage();
}
#But i relly prefer to use something like:
try{
$foo = bar() or throw new Exception('Problems with bar()');
$aa = bb($foo) or throw new Exception('Problems with bb()');
//...and so on!
}catch(Exception $e){
echo $e->getMessage();
}
#Actually, the only way i figured out is:
try{
$foo = bar() or throw new ThrowMe('Problems with bar()', 1);
$aa = bb($foo) or throw new ThrowMe('Problems with bb()', 2);
//...and so on!
}catch(Exception $e){
echo $e->getMessage();
}
#But i'll love to thro the exception directly instead of trick it with ThrowMe function.
or is just a logical operator, and it's analogous to ||.
The common trick of
mysql_query() or die();
could just as well be written
mysql_query() || die();
What happens here is the "logical or" operator (whichever you choose) is trying to determine if either operand evaluates to TRUE. This means the operands must be expressions that can be cast as a boolean.
So, the reason
bar() or throw new Exception();
is illegal, is because
(boolean)throw new Exception();
is also illegal. In essence, the process of throwing an exception doesn't generate a return value for the operator to check.
But calling a function does generate a return value for the operator to check (no explicit return value will result int the function returning NULL which casts as FALSE) which is why it works for you when you wrap exception throwing in a function.
I've simply defined the function toss for this.
function toss(Exception $exception): void
{
throw $exception;
}
Because the file/line/stack information is captured when the exception is constructed (new) not thrown (throw) this doesn't interfere with the call stack.
So you can just do this.
something() or toss(new Exception('something failed'));
Why don't bar() and bb() throw the exceptions? In PHP Exceptions bubble up, so there's no need to throw the exception in the function/method where you call bar()/bb(). These exceptions may be thrown by bar()/bb(). In case you want to throw another exception, you can simply do:
function foo() {
try {
$bar = bar();
} catch (BarException) {
throw new FooException;
}
}
A solution I found that can replace "or die()" everywhere is to wrap the throw with a anonymous function that gets called immediately by call_user_func:
call_user_func(function(){
throw new Exception("ERROR");
});
You can see that it works by executing this on a dummy script:
false or call_user_func(function(){throw new Exception("ERROR");});
I think you want to use something like the last structure, although there's really no point in using exceptions for that:
$foo = bar();
if(!$foo){
echo 'We have a problem in here';
}
Per comment - I don't think you can do that in a single line (i.e. without the if(!$foo) check), and I agree that the exception throwing method is pretty horrible. Personally, I prefer the explicitness of:
$foo = bar();
if(!$foo){
throw new Exception('We have a problem in here');
}
but that's a personal preference. If you want the single-line thing, I think you'll have to go with your exception-throwing function option.
I guess this limitation is probably down to PHP's dynamic-typing stuff, it can cast the results of a function call to a conditional, but not the results of a throw.
Here's a single-line solution without the extra function:
if (!($foo = bar())) throw new Exception('We have a problem here');
You can also create a custom exception class and use it's static constructor method instead of throw new Exception() construction.
Exception class:
class CustomException extends Exception {
static public function doThrow($message = "", $code = 0, Exception $previous = null) {
throw new Exception($message, $code, $previous);
}
}
Usage:
try {
$foo = bar() or CustomException::doThrow('Problems with bar()');
$aa = bb($foo) or CustomException::doThrow('Problems with bb()');
} catch(Exception $e){
echo $e->getMessage();
}
Note
If you are using PHP 7 and higher - you can rename static method doThrow() to simply throw(), since in PHP 7 and higher it's allowed to use reserved keywords as method names.

SimpleTest: How to assert that a PHP error is thrown?

If I am correct, SimpleTest will allow you to assert a PHP error is thrown. However, I can't figure out how to use it, based on the documentation. I want to assert that the object I pass into my constructor is an instance of MyOtherObject
class Object {
public function __construct(MyOtherObject $object) {
//do something with $object
}
}
//...and in my test I have...
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
$object = new Object($notAnObject);
$this->expectError($object);
}
Where am I going wrong?
Type hinting throws E_RECOVERABLE_ERROR which can be caught by SimpleTest since PHP version 5.2. The following will catch any error containing the text "must be an instance of". The constructor of PatternExpectation takes a perl regex.
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
$this->expectError(new PatternExpectation("/must be an instance of/i"));
$object = new Object($notAnObject);
}
PHP has both errors and exceptions, which work slightly different. Passing a wrong type to a typehinted function will raise an exception. You have to catch that in your test case. Eg.:
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$notAnObject = 'foobar';
try {
$object = new Object($notAnObject);
$this->fail("Expected exception");
} catch (Exception $ex) {
$this->pass();
}
}
or simply:
public function testConstruct_ExpectsAnInstanceOfMyOtherObject() {
$this->expectException();
$notAnObject = 'foobar';
$object = new Object($notAnObject);
}
But note that this will halt the test after the line where the exception occurs.
Turns out, SimpleTest doesn't actually support this. You can't catch Fatal PHP errors in SimpleTest. Type hinting is great, except you can't test it. Type hinting throws fatal PHP errors.
you have to expect the error before it happens, then SimpleTest will swallow it and count a pass, if the test gets to the end and there is no error then it will fail. (there's expectError and expectException that act in the same way, for PHP (non-fatal) errors and Exceptions, respectively.)

Categories