In PHP, when do callbacks registered with register_shutdown_function() run, and what are they typically used for?
answer
- after script end or exit
- runs after uncaught fatal errors too
- registration order, one after another
- exit inside stops the rest
- error_get_last() for fatal logging
basics
~20 sregister_shutdown_function() queues a callback that runs when the request ends: after the script finishes, calls exit, or dies on an uncaught exception or fatal error. Callbacks run in registration order; common uses are logging fatal errors and final cleanup.
solid answer
~40 s`register_shutdown_function(callable $callback, mixed ...$args): void` adds a callback to a queue that PHP runs at the **start of request shutdown**: after the script ends normally, after `exit`, after an uncaught exception, after a fatal error such as memory exhaustion, and after a `max_execution_time` timeout. Callbacks run **in the order they were registered**, before remaining objects are destroyed and before output buffers are flushed, so they can still echo output. Calling `exit` inside one stops the queue: later callbacks are skipped. The classic use is a last-chance error logger that calls `error_get_last()` to catch fatal errors that `set_error_handler()` cannot intercept. They do **not** run if the process is killed with SIGKILL, or with SIGTERM unless a signal handler exits cleanly.
code
php · 20 lines<?php
declare(strict_types=1);
register_shutdown_function(static function (): void {
$error = error_get_last();
$fatal = [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE];
if ($error !== null && in_array($error['type'], $fatal, true)) {
error_log(sprintf(
'Fatal: %s in %s:%d',
$error['message'],
$error['file'],
$error['line'],
));
}
});
$rows = [];
while (true) {
$rows[] = str_repeat('x', 1024 * 1024); // fatal: memory exhausted
}go deeper
Remember that register_shutdown_function() queues code for the end of the request, and that it runs even after exit or a fatal error.
Explain the order: shutdown callbacks first, then object destruction and output flushing, and why exit inside a callback cuts the queue short.
Use a shutdown callback with error_get_last() as the fatal-error safety net, and know the signals and crashes that skip it, so critical work lives in durable storage.
Decide which end-of-request work may rely on in-process shutdown hooks and which needs a queue or transaction to survive crashes and forced restarts.
## What the function does `register_shutdown_function(callable $callback, mixed ...$args): void` registers a callback, plus optional arguments, to be called when the current request shuts down. You can call it many times; each call appends to a queue. A callback can itself register another shutdown callback, which is appended to the end of the queue and still runs. ## When the queue runs Shutdown callbacks are the **first** step of PHP's request shutdown sequence. In the engine's order: 1. registered shutdown callbacks run; 2. objects still alive are destroyed; 3. output buffers are flushed; 4. extensions run their per-request shutdown hooks; 5. the request's memory is released. The queue is reached from several endings: | How the request ends | Shutdown callbacks run? | |---|---| | script reaches its last line | Yes | | `exit` or `die` is called | Yes | | uncaught exception | Yes, after the "Uncaught ..." fatal error | | fatal error, such as exhausting `memory_limit` | Yes | | `max_execution_time` exceeded | Yes | | client disconnects and the script is aborted | Yes | | process killed with SIGKILL | **No** | | process sent SIGTERM with no handler | **No** | For SIGTERM, the manual's advice is to install a handler with `pcntl_signal()` that calls `exit`, which turns the signal into a normal shutdown. ## Order and early exit - Callbacks run **in registration order**, first registered, first run. - If a callback calls `exit`, processing stops: **later callbacks do not run**. Since PHP 8.4 a parameterless `exit` inside a shutdown callback resets the exit code to 0, while `exit` with an explicit status overwrites it. - An uncaught exception thrown inside a shutdown callback is reported as a fatal error. Because callbacks run **before** buffered output is flushed, anything they echo still goes into the response, which is how some frameworks render an error page after a fatal error. ## The main use: catching fatal errors `set_error_handler()` sees warnings, notices and deprecations, but it is **not** called for fatal errors such as `E_ERROR` or `E_PARSE`, and `set_exception_handler()` only sees uncaught `Throwable`s. A shutdown callback runs after both, so it is the last place to learn what killed the request: - read the last error with `error_get_last()` (the error-handling API itself is a separate topic); - check whether it was a fatal level such as `E_ERROR` or `E_PARSE`; - log it, alert on it, or emit a generic error page. ## Other typical uses - **Final cleanup** that must happen however the request ended: releasing a lock file, marking a job as failed. - **Deferred work** such as flushing collected metrics or log lines once per request. - **Timing and diagnostics**: recording how long the request ran and its `memory_get_peak_usage()`. ## Pitfalls - **Working directory.** The manual notes that under some web servers, for example Apache, the working directory can change inside a shutdown callback, so use absolute paths such as `__DIR__ . '/var/app.log'`. - **Headers may be gone.** If output was already sent, a callback can no longer change headers or the status code. - **Not a destructor.** A shutdown callback belongs to the request, not to an object; object destruction happens after the queue finishes. - **Not durable.** A crashed or killed process skips the queue, so work that must happen belongs in a job queue or database transaction, not only in a shutdown callback. ## A worked example A gallery site imports a large catalogue in one request. Midway, a malformed record makes the script allocate far more than `memory_limit` allows, and PHP stops with an "Allowed memory size exhausted" fatal error. Nothing in a `try`/`catch` block sees it, because it is not an exception. A shutdown callback registered at the top of the entry script still runs: it reads the last error, finds a fatal level, writes one structured log line with the file and line, and marks the import job as failed in the database so an operator can see it. Without the callback, the only trace would be the raw fatal message in the server's error log, and the job would stay "running" forever.
- Why can't set_error_handler() log a memory-exhaustion error?The user error handler is not called for fatal levels such as E_ERROR; the engine bails out of the script instead. Shutdown callbacks still run afterwards, so error_get_last() inside one returns the fatal error's type, message, file and line.
- Do shutdown callbacks run when a container stops the process?Only if the stop is turned into a normal exit. SIGKILL cannot be caught, and SIGTERM with no handler ends the process without running them. A pcntl_signal() handler for SIGTERM that calls exit lets the queue run.
- What happens if a shutdown callback calls exit?Processing stops there: callbacks registered after it do not run. In PHP 8.4 and later a bare exit inside a shutdown callback resets the exit code to 0, while exit with a status sets that status.
saying these in an interview costs you the question
- Shutdown functions are skipped when the script dies on a fatal error.
- set_error_handler() can catch every error, including E_ERROR.
- Shutdown callbacks run in reverse order of registration.
- Shutdown callbacks always run, even if the process is killed.
- Calling exit inside one shutdown callback still runs the others.