set_exception_handler()
How PHP's set_exception_handler() registers a global handler for uncaught exceptions, covering signature, return value, a runnable example, and best practices.
Introduction
set_exception_handler() registers a function that PHP calls whenever an uncaught exception reaches the top of the script. Normally an uncaught exception produces a fatal error and a default stack trace; a global handler lets you replace that with consistent logging, a friendly error page, or alerting — in one place, for the whole application. This page covers the function's signature, what it returns, a runnable example, the cases it does not cover, and the gotchas worth knowing.
If you need the basics of throwing and catching first, start with PHP Exceptions and the try/catch chapter.
When is the handler called?
A normal exception is caught by the nearest matching catch block. The global handler only runs when no catch block matches — the exception "escapes" all the way out of the script:
try {
throw new RuntimeException('handled here');
} catch (RuntimeException $e) {
// caught locally — the global handler never runs
}
throw new RuntimeException('nothing catches this'); // → global handler runs, then script endsAfter the handler returns, execution does not resume — the script terminates. So the handler is your last chance to log and present the failure cleanly, not a way to recover and continue.
Signature and return value
set_exception_handler(?callable $callback): ?callable$callback— a callable that accepts the thrown object as its single argument. Since PHP 7 every exception and error implementsThrowable, so type-hint the parameter asThrowable(not justException) to also catchErrorinstances such asTypeError.- Returns the previously registered handler (or
nullif none was set), which you can keep to restore later. Passingnullremoves the handler.
Runnable example
The script below registers a handler, triggers it with an uncaught exception, and prints a formatted message. It writes to standard error via error_log() with no destination, so it is fully portable:
<?php
function appExceptionHandler(Throwable $e): void
{
$message = sprintf(
"Uncaught %s: %s in %s on line %d",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine()
);
error_log($message); // goes to the SAPI error log / stderr
echo "Something went wrong.\n"; // user-facing message
}
set_exception_handler('appExceptionHandler');
throw new RuntimeException('Database is unreachable');Output (the error_log line goes to stderr, the echo to stdout):
Something went wrong.with a line like Uncaught RuntimeException: Database is unreachable in /path/to/script.php on line 19 in the error log.
What it does NOT handle
set_exception_handler() only intercepts uncaught exceptions. It does not catch:
- Fatal errors, parse errors, or warnings — those go through
set_error_handler()(for catchable errors) orregister_shutdown_function()(for fatals). - Exceptions that are already caught by a local
try/catch.
For non-exception errors such as warnings and notices, register a separate handler with set_error_handler().
Restoring the previous handler
restore_exception_handler() reverts to whatever handler was active before your last set_exception_handler() call. Use it when a custom handler should apply only to a specific block of code:
<?php
set_exception_handler(function (Throwable $e) {
echo "Custom: {$e->getMessage()}\n";
});
// ... code that should use the custom handler ...
restore_exception_handler(); // back to the default behaviorImportant: your handler must not throw a new exception. If it does, PHP cannot dispatch it again and raises a fatal error instead.
Best Practices for Using set_exception_handler
When using set_exception_handler, there are a few best practices you should follow to ensure that your application handles errors effectively:
- Type-hint the handler's parameter as
Throwableso it catches bothExceptionandErrorsubtypes (PHP 7+). - Make sure the handler never throws a new exception — that triggers an unrecoverable fatal error.
- Log enough context to diagnose the failure: exception class, message, file, line, and the stack trace from
$e->getTraceAsString(). - Keep the user-facing message generic; never leak stack traces or messages to end users in production.
- Register the handler as early as possible (e.g. in a bootstrap file) so it covers the whole request.
- Pair it with
set_error_handler()andregister_shutdown_function()to cover warnings, notices, and fatal errors too. - Use
restore_exception_handler()to revert to the previous handler when a custom one should be scoped to a block of code.
Conclusion
set_exception_handler() gives you one place to handle every uncaught exception in an application — turning a raw fatal error into consistent logging and a clean user-facing message. Remember its limits: it runs only for uncaught exceptions, execution stops afterward, and the handler itself must never throw. Combine it with set_error_handler() for warnings, trigger-error for custom error signals, and restore_exception_handler() for scoped handling to build robust error reporting.