How can I catch ALL errors in PHP?

3.6k views Asked by At

I've found a lot of attempts at all-inclusive error handling implementations, and I figured I might write up a wiki-style to hopefully provide a complete solution that I came up with.

The question is:

"How might one catch, handle, or intercept ALL error types in PHP?"

Now - this might be deemed a 'rewrite' by some - but I don't know that there's been a comprehensive solution proposed.

3

There are 3 answers

0
DigitalJedi805 On

There are a number of levels of PHP errors, some of these require setting separate error handlers, and in order to catch every error PHP might throw up - you must write something that encompasses all of these 'types' of errors -- startup, 'runtime', and exceptions.

My solution to catch every ( as far as I can tell ) error that comes down the pipe:

  • A couple of global variables
  • An initialization method
  • 4 'non-OOP' error handler methods
  • An ErrorHandler class with an 'AppendError' method - which is where one might make modifications to how exactly errors are output or not ( in this case, errors are just dumped to the screen in some minimal HTML from this method )

...

// Moved this line to the bottom of the 'file' for usability - 
// I keep each of the above mentioned 'pieces' in separate files.
//$ErrorHandler = new ErrorHandler();

$ErrorCallback = "HandleRuntimeError";
$ExceptionCallback = "HandleException";
$FatalCallback = "HandleFatalError";

$EnableReporting = true;
$ErrorLevel = E_ALL;

function InitializeErrors()
{
    if($GLOBALS["EnableReporting"])
    {
        error_reporting($GLOBALS["ErrorLevel"]);

        if( isset($GLOBALS["ErrorCallback"]) && strlen($GLOBALS["ErrorCallback"]) > 0 )
        {
            set_error_handler($GLOBALS["ErrorCallback"]);

            // Prevent the PHP engine from displaying runtime errors on its own
            ini_set('display_errors',false);
        }
        else
            ini_set('display_errors',true);

        if( isset($GLOBALS["FatalCallback"]) && strlen($GLOBALS["FatalCallback"]) > 0 )
        {
            register_shutdown_function($GLOBALS["FatalCallback"]);

            // Prevent the PHP engine from displaying fatal errors on its own
            ini_set('display_startup_errors',false);
        }
        else
            ini_set('display_startup_errors',true);

        if( isset($GLOBALS['ExceptionCallback']) && strlen($GLOBALS['ExceptionCallback']) > 0 )
            set_exception_handler($GLOBALS["ExceptionCallback"]);
    }
    else
    {
        ini_set('display_errors',0);
        ini_set('display_startup_errors',0);
        error_reporting(0);
    }
}

function HandleRuntimeError($ErrorLevel,$ErrorMessage,$ErrorFile=null,$ErrorLine=null,$ErrorContext=null)
{
    if( isset($GLOBALS['ErrorHandler']))
    {
        //  Pass errors up to the global ErrorHandler to be later inserted into
        // final output at the appropriate time.
        $GLOBALS['ErrorHandler']->AppendError($ErrorLevel,"Runtime Error: " . $ErrorMessage,$ErrorFile,$ErrorLine,$ErrorContext);

        return true;
    }
    else
    {
        PrintError($ErrorLevel,$ErrorMessage,$ErrorFile,$ErrorLine,$ErrorContext);
        return true;
    }
}

function HandleException($Exception)
{
    if( isset($GLOBALS['ErrorCallback']))
    {
        // Parse and pass exceptions up to the standard error callback.
        $GLOBALS['ErrorCallback']($Exception->getCode(), "Exception: " . $Exception->getMessage(), $Exception->getFile(), $Exception->getLine(), $Exception->getTrace());

        return true;
    }
    else
    {       
        PrintError($Exception->getCode(), "Exception: " . $Exception->getMessage(), $Exception->getFile(), $Exception->getLine(), $Exception->getTrace());
        return true;
    }
}

function HandleFatalError()
{
    $Error = error_get_last();

    // Unset Error Type and Message implies a proper shutdown.
    if( !isset($Error['type']) && !isset($Error['message']))
        exit();
    else if( isset($GLOBALS['ErrorCallback']))
    {
        // Pass fatal errors up to the standard error callback.
        $GLOBALS["ErrorCallback"]($Error['type'], "Fatal Error: " . $Error['message'],$Error['file'],$Error['line']);

        return null;
    }
    else
    {
        PrintError($Error['type'], "Fatal Error: " . $Error['message'],$Error['file'],$Error['line']);
        return null;
    }
}

// In the event that our 'ErrorHandler' class is in fact the generator of the error,
// we need a plain-Jane method that will still deliver the message.
function PrintError($ErrorLevel,$ErrorMessage,$ErrorFile=null,$ErrorLine=null,$ErrorContext=null)
{
    if( class_exists("ErrorHandler"))
        $ErrorTypeString = ErrorHandler::ErrorTypeString($ErrorLevel);
    else
        $ErrorTypeString = "$ErrorLevel";

    if( isset($ErrorContext) && !is_array($ErrorContext) && strlen($ErrorContext) > 0 )
        $ErrorContext = str_replace("#", "<br/>\r\n#", $ErrorContext);

    $ReturnValue = "";
    $ReturnValue .= "<div class=\"$ErrorTypeString\" style=\"margin: 10px;\">\r\n";

    $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Error Level:</span> <span class=\"ErrorValue\">$ErrorTypeString</span></p>\r\n";

    if( isset($ErrorFile) && strlen($ErrorFile) > 0 )
        $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">File:</span> <span class=\"ErrorValue\">'$ErrorFile'</span></p>\r\n";

    if( isset($ErrorLine) && strlen($ErrorLine) > 0 )
        $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Line:</span> <span class=\"ErrorValue\">$ErrorLine</span></p>\r\n";

    if( isset($ErrorContext) && is_array($ErrorContext))
        $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Context:</span><span class=\"ErrorValue\">" . var_export($ErrorContext,true) . "</span></p>\r\n";
    else if( isset($ErrorContext) && strlen($ErrorContext) > 0 )
        $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Context:</span><span class=\"ErrorValue\">$ErrorContext</span></p>\r\n";

    $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Message:</span> <span class=\"ErrorValue\">" . str_replace("\r\n","<br/>\r\n",$ErrorMessage) . "</span></p>\r\n";

    $ReturnValue .= "</div>\r\n";

    echo($ReturnValue);
}

class ErrorHandler
{   
    public function AppendError($ErrorLevel,$ErrorMessage,$ErrorFile=null,$ErrorLine=null,$ErrorContext=null)
    {
        // Perhaps evaluate the error level and respond accordingly
        //
        // In the event that this actually gets used, something that might 
        // determine if you're in a production environment or not, or that 
        // determines if you're an admin or not - or something - could belong here.
        // Redirects or response messages accordingly.
        $ErrorTypeString = ErrorHandler::ErrorTypeString($ErrorLevel);

        if( isset($ErrorContext) && !is_array($ErrorContext) && strlen($ErrorContext) > 0 )
            $ErrorContext = str_replace("#", "<br/>\r\n#", $ErrorContext);

        $ReturnValue = "";
        $ReturnValue .= "<div class=\"$ErrorTypeString\" style=\"margin: 10px;\">\r\n";

        $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Error Level:</span> <span class=\"ErrorValue\">$ErrorTypeString</span></p>\r\n";

        if( isset($ErrorFile) && strlen($ErrorFile) > 0 )
            $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">File:</span> <span class=\"ErrorValue\">'$ErrorFile'</span></p>\r\n";

        if( isset($ErrorLine) && strlen($ErrorLine) > 0 )
            $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Line:</span> <span class=\"ErrorValue\">$ErrorLine</span></p>\r\n";

        if( isset($ErrorContext) && is_array($ErrorContext))
            $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Context:</span><span class=\"ErrorValue\">" . var_export($ErrorContext,true) . "</span></p>\r\n";
        else if( isset($ErrorContext) && strlen($ErrorContext) > 0 )
            $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Context:</span><span class=\"ErrorValue\">$ErrorContext</span></p>\r\n";

        $ReturnValue .= "<p class=\"ErrorData\"><span class=\"ErrorKey\">Message:</span> <span class=\"ErrorValue\">" . str_replace("\r\n","<br/>\r\n",$ErrorMessage) . "</span></p>\r\n";

        $ReturnValue .= "</div>\r\n";

        echo($ReturnValue);
    }

    public static function ErrorTypeString($ErrorType)
    {
        $ReturnValue = "";

        switch( $ErrorType )
        {
            default:
                $ReturnValue = "E_UNSPECIFIED_ERROR"; 
                break;
            case E_ERROR: // 1 //
                $ReturnValue = 'E_ERROR'; 
                break;
            case E_WARNING: // 2 //
                $ReturnValue = 'E_WARNING'; 
                break;
            case E_PARSE: // 4 //
                $ReturnValue = 'E_PARSE'; 
                break;
            case E_NOTICE: // 8 //
                $ReturnValue = 'E_NOTICE'; 
                break;
            case E_CORE_ERROR: // 16 //
                $ReturnValue = 'E_CORE_ERROR'; 
                break;
            case E_CORE_WARNING: // 32 //
                $ReturnValue = 'E_CORE_WARNING'; 
                break;
            case E_COMPILE_ERROR: // 64 //
                $ReturnValue = 'E_COMPILE_ERROR'; 
                break;
            case E_CORE_WARNING: // 128 //
                $ReturnValue = 'E_COMPILE_WARNING'; 
                break;
            case E_USER_ERROR: // 256 //
                $ReturnValue = 'E_USER_ERROR'; 
                break;
            case E_USER_WARNING: // 512 //
                $ReturnValue = 'E_USER_WARNING'; 
                break;
            case E_USER_NOTICE: // 1024 //
                $ReturnValue = 'E_USER_NOTICE'; 
                break;
            case E_STRICT: // 2048 //
                $ReturnValue = 'E_STRICT';
                break;
            case E_RECOVERABLE_ERROR: // 4096 //
                $ReturnValue = 'E_RECOVERABLE_ERROR';
                break;
            case E_DEPRECATED: // 8192 //
                $ReturnValue = 'E_DEPRECATED'; 
                break;
            case E_USER_DEPRECATED: // 16384 //
                $ReturnValue = 'E_USER_DEPRECATED'; 
                break;
        }

        return $ReturnValue;
    }
}

$ErrorHandler = new ErrorHandler();

Now - code out of the way...

To implement this, it's as simple as including this file, and executing the 'InitializeErrors' method. Beyond this, it's up to you what you want to Do with the errors; this is simply a wrapper for every error that PHP might generate - and to make changes to how any given error is handled, it's basically as simple as evaluating it in the 'AppendError' method and responding accordingly.

-- I should note - I implemented return values for reasons I cannot explain, and I should review my own work on that front - but I'm not terribly sure it has any bearing on the result.

1
Niet the Dark Absol On

There are three kinds of error handlers you need:

  • set_exception_handler, to catch any otherwise uncaught exceptions.

  • set_error_handler to catch "standard" PHP errors. I like to first check it against error_reporting to see if it's an error that should be handled or ignored (I generally ignore Notices - probably bad but that's my choice), and throw an ErrorException, letting the exception handler catch it for outputting.

  • register_shutdown_function combined with error_get_last. Check the value of ['type'] to see if it is E_ERROR, E_PARSE or basically any fatal error types you want to catch. These normally bypass set_error_handler, so catching them here lets you grab them. Again, I tend to just throw an ErrorException, so that all errors end up being handled by the same exception handler.

As for how you implement these, it depends entirely on how your code is set up. I usually do an ob_end_clean() to purge any output, and present a nice error page telling them that the error has been reported.

0
Stackoverflow On

Into file [Config.php] in [phpMyAdmin-4.6.5.2-all-languages] you can find:

/**
 * Error handler to catch fatal errors when loading configuration
 * file
 *
 *
 * PMA_Config_fatalErrorHandler
 * @return void
 */
public static function fatalErrorHandler()
{
    if (!isset($GLOBALS['pma_config_loading'])
        || !$GLOBALS['pma_config_loading']
    ) {
        return;
    }

    $error = error_get_last();
    if ($error === null) {
        return;
    }

    PMA_fatalError(
        sprintf(
            'Failed to load phpMyAdmin configuration (%s:%s): %s',
            Error::relPath($error['file']),
            $error['line'],
            $error['message']
        )
    );
}

and just in end of file:

register_shutdown_function(array('PMA\libraries\Config', 'fatalErrorHandler'));