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;
        }
    }
}

?>

Reply via email to