Hi, having worked on the user land side of error handling [1]<https://github.com/nicolas-grekas/Patchwork-Logger/blob/master/class/Patchwork/PHP/ErrorHandler.php>, I wanted to share some more ideas that could help enhance the current error handling mechanism.
Concerning throwing vs not throwing exceptions, the current error handling mechanism allows everyone to code what they prefer, thanks to custom error handling. Personally, I always throw on E_RECOVERABLE_ERROR and E_USER_ERROR. I've found that it's the only safe and compatible choice if I want to reuse some code from others, like a PEAR lib for example, while allowing to recover from situations that otherwise halt script's execution. The really cool thing about error handling in PHP is that we can customize it quite extensively. The bad thing is that we can't customize the behavior for non-catchable errors (E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE, E_CORE_WARNING and E_COMPILE_WARNING). Of course, I understand that the PHP engine can't be made to recover from all errors. But my feeling is that some which are currently fatal could be made recoverable [2]<https://bugs.php.net/bug.php?id=55428>. It would be awesome if as much E_ERROR as possible could be converted to E_RECOVERABLE_ERROR! As a side note for E_PARSE: it is fatal, but only when including and not when inside an eval(). This dual E_PARSE behavior is strange, and I wonder if "E_PARSE when including" could be made E_RECOVERABLE_ERROR? Then, could also "E_PARSE when eval()" be made user-catchable? The second idea I wanted to share concerns error silencing. The @ operator can sometimes make debugging a nightmare, especially when the error it hides is one of the non-catchable ones. I know about xdebug.scream, but it works by removing @s, so it makes impossible to differentiate between "would be silenced" and "is not silenced" errors and as such it can't be enabled all the time. On my side, to work around silencing, I use a custom error handler [1]<https://github.com/nicolas-grekas/Patchwork-Logger/blob/master/class/Patchwork/PHP/ErrorHandler.php>for catchable errors, and some special code [3] around includes. In my error handler, I use a bit field to configure a set of error levels that are never silenced. The hack [3] for non-catchable errors works, but needs special care. A more general approach would be to add a "minimal error reporting level" or otherwise named "screamed errors" bit field in PHP core. Would this be possible? Is it a good idea? Then comes the third idea I wanted to share: to me, the last area where PHP doesn't help debugging is at shutdown time, with the "Exception thrown without a stack frame in Unknown on line 0" error. Before shutdown time, uncaught exceptions are transfered to the exception handler (php default or user callback). But at shutdown time, this cryptic error message is always reported instead. I know that shutdown time is very special and code running there should be kept rare, but it happens... So, could we get rid of this cryptic error message, and replace it with the regular exception handling path? I tried and somewhat managed to enhance the situation user land side: if you replace every single call to register_shutdown_function() with [4], and every single __destruct() with [5], then this fixes most of the pb. AFAIK, the only other case where an uncaught exception could be triggered is in a remaining output buffering handler. But as in [4], it wouldn't be hard to encapsulate output buffering handlers in a catching closure. As it looks possible in user land, is the same doable on the engine side? I'm over with my ideas, thanks for reading :) Nicolas [1] https://github.com/nicolas-grekas/Patchwork-Logger/blob/master/class/Patchwork/PHP/ErrorHandler.php<https://github.com/nicolas-grekas/Patchwork-Logger/blob/master/class/Patchwork/PHP/ErrorHandler.php> [2] https://bugs.php.net/bug.php?id=55428<https://bugs.php.net/bug.php?id=55428> [3] : <?php $error_level_bak = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); include $file; error_reporting($error_level_bak); ?> [4] : <?php function my_register_shutdown_function($callback) { $args = func_get_args(); $callback = function() use (&$args) { try { call_user_func_array(array_shift($args), $args); } catch (\Exception $e) { $h = set_exception_handler('var_dump'); // "var_dump" is never used as per the next line restore_exception_handler(); if (null !== $h) call_user_func($h, $e); else user_error("Uncaught exception '" . get_class($e) . "' in { $e->getFile()} on line {$e->getLine()}", E_USER_WARNING); exit(255); } } return register_shutdown_function($callback); } ?> [5] : <?php class foo { function __destruct() { try { // destructor's implementation here } catch (\Exception $e) { unset($t); if (empty($e->isDestructorException)) { $t = array_slice($e->getTrace(), -1); if (isset($t[0]['line']) || !isset($t[0]['class']) || strcasecmp('__destruct', $t[0]['function'])) { $e->isDestructorException = 1; } else { // This code path is taken only when the $e exception has been // thrown while PHP was calling remaining objects' destructors, // at the very end of script's execution. $e->isDestructorException = $t[0]['class']; } } if (isset($e->isDestructorException) && __CLASS__ === $e-> isDestructorException && 1 === count(debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 2))) { $t = set_exception_handler('var_dump'); // "var_dump" is never used as per the next line restore_exception_handler(); if (null !== $t) call_user_func($t, $e); else user_error("Uncaught exception '" . get_class($e) . "' in {$e->getFile()} on line {$e->getLine()}", E_USER_WARNING); exit(255); } throw $e; } } } ?>