Catch ErrorException that wraps a fatal PHP error

Learn catch errorexception that wraps a fatal php error with practical examples, diagrams, and best practices. Covers php, error-handling, fatal-error development techniques with visual explanations.

Catching PHP Fatal Errors with ErrorException and set_error_handler

Hero image for Catch ErrorException that wraps a fatal PHP error

Learn how to convert PHP fatal errors into catchable ErrorException instances, enabling robust error handling and graceful degradation in your applications.

PHP's error handling mechanism can be tricky, especially when dealing with fatal errors. Unlike warnings or notices, fatal errors typically halt script execution immediately, making it impossible to catch them with standard try...catch blocks. However, by leveraging PHP's set_error_handler() function and the ErrorException class, you can transform many fatal errors into catchable exceptions, allowing for more robust error management and preventing abrupt application crashes.

Understanding PHP Error Types and Catchability

Before diving into the solution, it's crucial to understand the different types of PHP errors and their default behavior. PHP errors are categorized by severity, ranging from notices (least severe) to fatal errors (most severe). Standard try...catch blocks are designed to handle exceptions, not traditional PHP errors. While set_error_handler() can convert most non-fatal errors into ErrorException instances, true fatal errors (like E_ERROR or E_PARSE in certain contexts) are often beyond its reach because they occur before the error handler can be invoked or are too severe to be recovered from.

flowchart TD
    A[PHP Script Execution] --> B{Error Occurs?}
    B -->|No| C[Continue Execution]
    B -->|Yes| D{Error Type?}
    D -->|E_NOTICE, E_WARNING, E_DEPRECATED| E[Default PHP Error Handler]
    D -->|E_ERROR, E_PARSE (Fatal)| F[Script Halts Immediately]
    E --> G{Custom Error Handler Set?}
    G -->|Yes| H[Convert to ErrorException]
    G -->|No| I[Log Error & Continue/Halt]
    H --> J[Catch with try...catch]
    J --> K[Handle Error Gracefully]
    F --> L[Application Crash]

Default PHP Error Handling Flow vs. Custom Handler

Converting Errors to Exceptions with set_error_handler

The core of this technique involves set_error_handler(). This function allows you to register a custom function to handle PHP errors. Inside your custom error handler, you can check the error severity and, for errors that are typically non-fatal but you wish to treat as exceptions, throw an ErrorException. This effectively 'upgrades' a standard PHP error into a catchable exception.

<?php

function customErrorHandler($severity, $message, $file, $line) {
    // Convert all errors to exceptions, except for E_NOTICE and E_DEPRECATED
    // which might be too noisy for some applications.
    if (!(error_reporting() & $severity)) {
        // This error code is not included in error_reporting
        return;
    }
    
    // Only convert errors that are typically recoverable or can be wrapped
    // E_ERROR (fatal) is often not catchable this way if it truly halts execution
    // before the handler can act. However, some E_ERRORs can be wrapped.
    if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
        throw new ErrorException($message, 0, $severity, $file, $line);
    }

    // For other errors (E_WARNING, E_NOTICE, etc.), you might still want to log them
    // or convert them to exceptions depending on your application's needs.
    // For demonstration, we'll convert E_WARNING as well.
    if ($severity === E_WARNING) {
        throw new ErrorException($message, 0, $severity, $file, $line);
    }

    // Let PHP's default error handler deal with anything else, or ignore it.
    return false;
}

set_error_handler('customErrorHandler');

try {
    // This will trigger an E_WARNING
    $undefinedVar = $nonExistentArray['key'];

    // This will trigger an E_RECOVERABLE_ERROR (e.g., type mismatch in strict mode)
    // function takesString(string $s) { /* ... */ }
    // takesString(123);

    // Attempting to trigger a true E_ERROR (fatal) that might be wrapped
    // Note: Not all E_ERRORs can be wrapped this way, especially parse errors.
    // Example: Calling a non-existent function will often be a fatal error.
    nonExistentFunction();

} catch (ErrorException $e) {
    echo "Caught ErrorException: ".$e->getMessage()." in ".$e->getFile()." on line ".$e->getLine()." (Severity: ".$e->getSeverity().")\n";
} catch (Throwable $e) {
    // Catch any other exceptions, including potentially other fatal errors
    // that might be converted by other mechanisms or are true exceptions.
    echo "Caught general exception: ".$e->getMessage()." in ".$e->getFile()." on line ".$e->getLine()."\n";
}

echo "Script continues after error handling.\n";

// Restore the previous error handler (optional, but good practice)
restore_error_handler();

?>

Example of a custom error handler converting warnings and recoverable errors to ErrorExceptions.

Handling True Fatal Errors with register_shutdown_function

For fatal errors that set_error_handler() cannot intercept, register_shutdown_function() provides a fallback. This function registers a callback that will be executed when the script finishes execution, regardless of whether it completed successfully or terminated due to a fatal error. Inside this shutdown function, you can check for the last error that occurred and, if it's a fatal error, log it or perform other cleanup operations.

<?php

function customErrorHandler($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        return;
    }
    // Convert all errors that are not E_ERROR or E_PARSE to ErrorException
    // E_ERROR and E_PARSE will be caught by the shutdown function if they are truly fatal.
    if (!in_array($severity, [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING])) {
        throw new ErrorException($message, 0, $severity, $file, $line);
    }
    return false; // Let PHP's default handler deal with it if not converted
}

set_error_handler('customErrorHandler');

register_shutdown_function(function() {
    $lastError = error_get_last();
    if ($lastError && in_array($lastError['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING])) {
        // This is a fatal error that was not caught by set_error_handler
        echo "\nFATAL ERROR DETECTED BY SHUTDOWN FUNCTION:\n";
        echo "Type: ".$lastError['type']."\n";
        echo "Message: ".$lastError['message']."\n";
        echo "File: ".$lastError['file']."\n";
        echo "Line: ".$lastError['line']."\n";
        // You can log this error, send an alert, etc.
    }
});

try {
    echo "Attempting to trigger a warning...\n";
    $undefinedVar = $nonExistentArray['key']; // E_WARNING

    echo "Attempting to trigger a fatal error (non-existent function)...\n";
    nonExistentFunctionCall(); // E_ERROR (often fatal)

} catch (ErrorException $e) {
    echo "Caught ErrorException: ".$e->getMessage()." in ".$e->getFile()." on line ".$e->getLine()." (Severity: ".$e->getSeverity().")\n";
} catch (Throwable $e) {
    echo "Caught general exception: ".$e->getMessage()." in ".$e->getFile()." on line ".$e->getLine()."\n";
}

echo "This line might not be reached if a true fatal error occurs before shutdown function.\n";

?>

Combining set_error_handler with register_shutdown_function for comprehensive error handling.