Consider the following two PHP (5.4) scripts. Why is the callback passed to register_shutdown_function only invoked when script A is executed, but not when script B is executed?
Script A
set_error_handler(function() {
throw new Exception();
});
register_shutdown_function(function() {
echo "shutdown handler invoked\n";
});
undefined();
// "shutdown handler invoked" IS displayed
Script B
set_error_handler(function() {
throw new Exception();
});
register_shutdown_function(function() {
echo "shutdown handler invoked\n";
});
$undefined->undefined();
// "shutdown handler invoked" IS NOT displayed
It's a bug—if the callable registered with set_error_handler throws an exception, the shutdown function will not be invoked.
In this particular case, the following chain of events happens:
Non-fatal error is triggered (Undefined variable: undefined)
User error handler is invoked
Exception is thrown
Fatal error is triggered (Call to a member function undefined() on a non-object)
Shutdown function is not invoked, due to existing exception
The existing bug reports at https://bugs.php.net/61767 (with patch!) and https://bugs.php.net/60909 have additional details.
Here is what I found out.
register_shutdown_function is called upon exit.
$undefined->undefined(); triggers E_NOTICE error because $undefined is undefined as a variable which goes through set_error_handler. if you call throw new Exception, php for some reason doesn't trigger register_shutdown_function handler when exiting just crashes, if you want to throw an exception maybe you can call your exception function directly.
If you make a dummy class and define $undefined, but doesn't make a member function called undefined, it will be behave in the same way as calling undefined() as below
undefined(); triggers E_ERROR which immediately exits triggering shutdown_function handler
You could do this in the shutdown function beside using error handler to catch different types of errors:
function shutdown_function_handler(){
if ($error = error_get_last()){
switch ($error['type']){
case E_ERROR:
case E_USER_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
echo " ERRROR:".$error['message'];
}
}
}
Related
I want to get an errors object from a callback function. here is what i have done so far.
$errors = null;
set_exception_handler(function($exception) use (&$errors) {
$errors = $exception;
var_dump($exception); // works fine
});
var_dump($errors); // give null ????
throw new Exception("blablblablablabla", 0);
I am sure with an anonymous function and a variable would work very fine !
But in this case with a callback and object I am not sure what's going on ? i can't get my head a round. any help explanation would be really appreciated.
set_exception_handler does not stop execution. It just defines what will happen if/when an exception occurs, so you can var_dump($errors); errors after setting it. $errors will still be null at that point because you have set it to null, and nothing has happened yet to change that.
The thing that does stop execution is when an exception actually occurs and the anonymous function defined in set_exception_handler is called. At that point, the var_dump($exception); within that function is called, but any other code in the main program after the point where the exception occurred will not be executed.
Storing the exception in a global variable from within the exception handler doesn't make really sense, because if the exception handler is called, no more code in the main program will be executed, so you won't be able to use that variable anyway.
You are assigning the anonymous function as an exception handler with set_exception_handler().
Yet you do not throw an exception in your code. Therefore the anonymous function is never called, because there was no event to handle, which means that $error was never set.
If you add a try catch block your function will also not be called. This can be seen by the fact, that the line var_dump($exception) from within your handler is not called either.
If you want to store the last called exception in your code, you may need to use a wrapper function around that code, that uses try catch to overwrite a static resource with the last exception encountered.
Note, that the exception within the given function is never fully thrown, because it was catched by the wrapper!
class Catcher {
private static $_EXCEPTION = null;
public static function GET_EXCEPTION () {
return self::$_EXCEPTION;
}
public static function run($func) {
try {
return $func();
} catch (Exception $e) {
self::$_EXCEPTION = $e;
}
}
}
var_dump(Catcher::GET_EXCEPTION()); //dumps NULL
Catcher::run(function(){
throw new Exception('I was called');
});
var_dump(Catcher::GET_EXCEPTION()); //dumps the last thrown exception
I cannot find a reliable source to ensure that I am able to throw an exception inside __destruct().
This is what php documentation says
Attempting to throw an exception from a destructor (called in the time of script termination) causes a fatal error.
But when I test it
class A
{
public function __destruct()
{
throw new \Exception();
}
}
try {
$a = new A();
} catch(\Exception $x) {
var_dump($x);
}
it seems that this is not true. I throw and catch exceptions normally. So, why has the doc got this line?
edited after Mark Baker's answer:
it actually works, the exception can be thrown and caught from destructor. I am still uncertain why the documentation lies then.
The destructor isn't being called anywhere in your script, so your try/catch block won't catch anything. If you unset($x) inside the try/catch block, then an exception will be thrown, and duly caught.
class A {
public function __destruct() {
throw new \Exception();
}
}
try {
$a = new A();
unset($a);
} catch(\Exception $x) {
var_dump($x);
}
Otherwise, PHP throws an exception when the script terminates, but that is outside of your try/catch block
EDIT
The exception thrown on script termination if your object hasn't been manually destroyed can't be caught because you can't wrap try/catch around the PHP's internal termination handling that destroys the object; and this is what results in a fatal error
Fatal Error: Uncaught Exception
So this is what the documentation is warning you about
An exception is thrown in my shutdown function and not caught within a try/catch block, for example:
<?php
set_exception_handler(function($e){
echo "exception handled"; // not echoed
});
register_shutdown_function(function(){
throw new Exception("test"); // this should be caught by the exception handler above, but it doesn't
});
Live run.
Running the above code gives:
Fatal error: Uncaught exception 'Exception' with message 'test'
However PHP Manual claims:
set_exception_handler sets the default exception handler if an exception is not caught within a try/catch block. Execution will stop after the exception_handler is called.
Why does exception_handler not catch the exception thrown?
Because it is beyond the scope...
http://www.php.net/manual/en/function.register-shutdown-function.php
Registers a callback to be executed after script execution finishes or exit() is called.
It must be stripping down your handlers as the execution of your script has finished and is in fact shutting down.
I've written error handling class which divided all errors into the normal ones (notices, warnings, ...), and the critical ones.
Now I've found out that it's a good practice to convert all errors into exceptions. It would also shorten my code.
However, I'm not sure how to handle this...
Are there exceptions that don't stop scripts execution, and exceptions that do? If there aren't...how to differ converted errors?
Converting errors into exception is done by calling set_error_handler() and throw new ErrorException() in there...What's next? set_exception_handler() is called automagically?
Caught exceptions do not stop your script, all uncaught ones do.
No, set_exception_handler() is not called automatically, you can do that if you like.
The exception handler you set with set_exception_handler() gets called after an exception has gone uncaught, it is the last piece of code that gets called before the script terminates. Make sure it doesn't cause an error/exception, or it will end badly.
Are there exceptions that don't stop scripts execution, and exceptions that do? If there aren't...how to differ converted errors?
Exceptions don't stop script execution if they're caught. To recognize a converted error:
try {
// ...
} catch (ErrorException $e) {
// converted error (probably)
} catch (Exception $e) {
// another kind of exception; this basically catches all
}
Or:
function handle_exception(Exception $e)
{
if ($e instanceof ErrorException) {
// converted error (probably)
} else {
// another kind of exception
}
}
set_exception_handler('handle_exception');
Note that ErrorException can be thrown by any piece of code, but it was meant to convert regular errors in set_error_handler() registered functions only.
Converting errors into exception is done by calling set_error_handler() and throw new ErrorException() in there...What's next? set_exception_handler() is called automagically?
If the thrown ErrorException from your error handler function is not caught anywhere else in your code, the registered exception handler (set using set_exception_handler()) will be called.
Any uncaught exception will stop execution of your script.
When an exception is thrown, code following the statement will not be
executed, and PHP will attempt to find the first matching catch block.
If an exception is not caught, a PHP Fatal Error will be issued with
an "Uncaught Exception ..." message, unless a handler has been defined
with set_exception_handler().
See docs about this
As for set_exception_handler() - it is not called automatically, but it is your last resort to react to the problem that occured
Sets the default exception handler if an exception is not caught
within a try/catch block. Execution will stop after the
exception_handler is called.
Keep in mind that you can only convert Warnings into Exceptions, errors cannot be converted to Exceptions because the error handler doesn't run.
set_error_handler(function ($severity, $message, $file, $line) {
echo 'You will never see this.'
});
// Provoke an error
function_that_does_not_exist();
It is possible to "catch" them using a shutdown function, but that is out of the scope of the question.
My app has a registered shutdown function and it seems there's some issues with that and my method of using an exception with a try/catch to exit the application (instead of using the exit() method due to FastCGI not liking this).
My problem is that if another exception is thrown in the try/catch block that isn't the ExitApp exception, it causes some unexpected results and the end result is the ExitApp exception isn't caught.
I'm seeing this on PHP 5.3.6, going to test it on another version now, but I'm curious if anyone can immediately point out what's wrong here.
<?php
// Define dummy exception class
class ExitApp extends Exception {}
try {
// Define shutdown function
function shutdown() {
echo "Shutting down...";
throw new ExitApp;
}
register_shutdown_function("shutdown");
// Throw exception!
throw new Exception("EXCEPTION!");
} catch(ExitApp $e) {
echo "Catching the exit exception!";
}
/**
* Expected Result: Uncaught Exception Error and then "Catching the exit exception!" is printed.
* Actual Result: Uncaught Exception Error for "Exception" and then Uncaught Exception Error for "ExitApp" even though it's being caught.
*/
You have wrong expectations from your code. Firstly, if you throw exception in your shutdown function, you will always end up with uncaught exception - shutdown functions are called outside tr/catch block.
Secondly you have no attempt to intercept unknown exception - you are only catching ExitApp types. you may want to try something like this:
try {
//some stuff
} catch(ExitApp $ea) {
//normal exit, nothing to do here
} catch(Exception $e){
//something rather unexpected, log it
}
Your shutdown() function is not even in a try/catch block, so it will never jump down to the catch for this exception type. It is going to run on exit so you will not longer be in that try/catch block.
On a more spiritual, try/catch is not meant for flow control. I'm not quite sure why you're trying to throw this to cause script exit, rather than just calling your own shutdown() method.
Hope that helps.