Catch Unhandled Exceptions Globally in PHP

This article explains how to implement global exception handling in PHP using the built-in set_exception_handler() function. You will learn how to register a custom callback to catch any uncaught exceptions, log error details securely, and display a user-friendly error page instead of exposing sensitive system stack traces.

What is set_exception_handler()?

In PHP, any exception thrown outside of a try-catch block is considered an “uncaught exception.” By default, this results in a fatal error that halts script execution and can display sensitive path information to your users.

The set_exception_handler() function allows you to define a custom global function to handle these uncaught exceptions. Once registered, whenever an exception escapes a try-catch block, PHP bypasses the default error display and passes the exception object directly to your custom handler.

Implementing a Global Exception Handler

To set up a global exception handler, you pass a callable (such as a function name or an anonymous function) to set_exception_handler().

Since PHP 7, both engine errors and exceptions implement the Throwable interface. It is best practice to type-hint the handler’s parameter as Throwable to ensure it catches both standard exceptions and modern PHP engine errors.

Here is a basic implementation using an anonymous function:

<?php

// Register the global exception handler
set_exception_handler(function (Throwable $exception) {
    // 1. Log the error internally for debugging
    error_log(sprintf(
        "Uncaught Exception: %s in %s on line %d",
        $exception->getMessage(),
        $exception->getFile(),
        $exception->getLine()
    ));

    // 2. Clear any output buffers (optional)
    if (ob_get_length()) {
        ob_clean();
    }

    // 3. Set HTTP status code to 500 (Internal Server Error)
    http_response_code(500);

    // 4. Display a clean, user-friendly message
    echo "<h1>Something went wrong</h1>";
    echo "<p>We are experiencing technical difficulties. Please try again later.</p>";

    // 5. Terminate the script
    exit(1);
});

// Triggering an unhandled exception to test the handler
throw new Exception("Database connection failed!");

Key Considerations

1. Production vs. Development Environments

In a development environment, you want to see detailed stack traces. In production, you must hide these details to prevent security leaks. Use environment variables to toggle the verbosity of your global handler:

set_exception_handler(function (Throwable $exception) {
    error_log($exception); // Always log the full error privately

    if (getenv('APP_ENV') === 'development') {
        // Show detailed diagnostics to developers
        echo "<h2>Debug Info:</h2>";
        echo "<pre>" . print_r($exception, true) . "</pre>";
    } else {
        // Show generic message to public users
        echo "A system error occurred. Our team has been notified.";
    }
    exit(1);
});

2. Execution Flow Termination

Once the global exception handler finishes executing, the PHP script terminates automatically. You do not need to call exit() or die() unless you want to return a specific exit status code (e.g., exit(1) to signal an error to command-line runners).

3. Restoring the Previous Handler

If you need to temporarily override the global handler and then revert to your previous configuration, PHP provides the restore_exception_handler() function:

set_exception_handler('firstHandler');
set_exception_handler('secondHandler');

// This reverts the handler back to 'firstHandler'
restore_exception_handler();