Uncaught Exceptions

Uncaught Exception Handlers and Shutdown Functions

set_exception_handler() receives any Throwable no catch took; the script ends after it. register_shutdown_function() (Callbacks) runs however the script ends, even after a fatal error such as memory exhaustion, which nothing else sees. There error_get_last() reports the fatal error, and in PHP 8.5 its array has a trace key.

crash.php: a last-chance exception handler and a shutdown function that detects fatalsPHP
<?php
set_exception_handler(function (Throwable $e): void {
    error_log('uncaught ' . $e::class . ': ' . $e->getMessage());
    echo "Sorry, something went wrong.\n";
});
register_shutdown_function(function (): void {
    $err = error_get_last();
    if ($err && $err['type'] === E_ERROR) {
        echo "shutdown: fatal error, ", count($err['trace'] ?? []), " frame(s) in its trace\n";
    }
});
$argv[1] === 'throw' ? throw new LogicException('no route for /cart') : str_repeat('x', 8e6);

Run as php crash.php throw, then as php -d memory_limit=4M crash.php oom, each followed by echo "exit status $?":

Output of 137
uncaught LogicException: no route for /cart
Sorry, something went wrong.
exit status 0
PHP Fatal error:  Allowed memory size of 4194304 bytes exhausted (tried to allocate 8000032
  bytes) in /home/dev/shop/crash.php on line 12
Stack trace:
#0 /home/dev/shop/crash.php(12): str_repeat()
#1 {main}
shutdown: fatal error, 1 frame(s) in its trace
exit status 255

After your handler runs, the CLI exits with 0 and a cron job thinks it succeeded: end a CLI handler with exit(1) and a web one with http_response_code(500).