set_exception_handler — global exception handler
Concept
set_exception_handler($callback) registers a global handler for uncaught exceptions. When an exception propagates all the way to the top of the call stack without being caught, PHP calls this handler instead of displaying a fatal error. It's the last line of defense before the application terminates.
Handler signature: handler(\Throwable $e): void. The script terminates after the handler returns — you cannot prevent termination from here.
Use cases: Generate a user-friendly error page, log the exception with full context before termination, send an alert to Sentry/Bugsnag/Rollbar, return a JSON error response for API requests.
Laravel's exception handler: App\Exceptions\Handler implements \Illuminate\Foundation\Exceptions\Handler. It overrides render() to transform exceptions into HTTP responses, report() to log/notify, and uses $dontReport / $dontFlash to exclude sensitive data. Registered via App\Http\Kernel.
restore_exception_handler(): Restores the previous handler (handlers are stacked like error handlers).
Difference from try/catch at application entry: set_exception_handler catches exceptions that slip past all try/catch blocks in the application. It's for truly unexpected exceptions. Expected exceptions (business logic errors) should be caught explicitly.
Code Example
<?php
declare(strict_types=1);
// Global exception handler for a simple PHP application
set_exception_handler(function (\Throwable $e): void {
// Log the full exception
error_log(sprintf(
"Uncaught %s: %s in %s:%d\nStack trace:\n%s",
get_class($e),
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
));
// User-facing response
if (PHP_SAPI === 'cli') {
fwrite(STDERR, "Error: " . $e->getMessage() . "\n");
exit(1);
}
$isApi = str_starts_with($_SERVER['REQUEST_URI'] ?? '', '/api/');
if ($isApi) {
header('Content-Type: application/json', true, 500);
echo json_encode(['error' => 'Internal Server Error']);
} else {
http_response_code(500);
include __DIR__ . '/views/errors/500.html';
}
});
// With Sentry integration
set_exception_handler(function (\Throwable $e): void {
\Sentry\captureException($e);
http_response_code(500);
echo json_encode(['error' => 'An unexpected error occurred']);
});
// Laravel's handler (simplified internal structure)
class Handler
{
protected array $dontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Validation\ValidationException::class,
];
public function report(\Throwable $e): void
{
if ($this->shouldntReport($e)) return;
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
public function render($request, \Throwable $e): \Symfony\Component\HttpFoundation\Response
{
if ($e instanceof \Illuminate\Validation\ValidationException) {
return response()->json($e->errors(), 422);
}
if ($e instanceof \Illuminate\Auth\AuthenticationException) {
return response()->json(['message' => 'Unauthenticated'], 401);
}
return response()->json(['message' => 'Server Error'], 500);
}
}