Shutdown
Some work must happen before a PHP request ends, but does not need to delay the response sent to the client. For example, an application may need to flush buffered logs or telemetry to a third-party service, close request-scoped resources, or publish diagnostics collected during the request. Running that work before returning the response makes the website or API feel slower even though the result does not affect what the client receives.
Foundation Shutdown lets features contribute these end-of-request tasks from their providers. In WordPress, it attempts to finish the HTTP response before running the tasks from the shutdown action. Each task runs once in priority order, and one failed task does not prevent the remaining tasks from running.
Use shutdown tasks for bounded, best-effort work that can finish within the current PHP process. They are not asynchronous jobs: the PHP worker remains occupied until every task finishes.
Installation
Section titled “Installation”Install the runtime package:
Prepare the application
Section titled “Prepare the application”Shutdown tasks use the application’s existing container and ordered provider graph:
Configuration
Section titled “Configuration”Configure the cache write
Section titled “Configure the cache write”In the root config.php, choose the WordPress transient key and lifetime used for the cached snapshot:
Create a cache-write task
Section titled “Create a cache-write task”Create src/Product_Cache/Product_Cache_Writer.php. This example assumes Product_Cache_Buffer collects an in-memory snapshot while the application handles the request. The task writes that prepared snapshot to a WordPress transient only when it changed:
The buffer is responsible for collecting cacheable state during the request. The shutdown task only performs the final bounded write. The PHP worker remains occupied until terminate() returns, even when the response has already been sent to the client.
Contribute the task from its feature provider
Section titled “Contribute the task from its feature provider”In src/Product_Cache/Provider.php, supply the task’s scalar configuration before adding it lazily to ShutdownProvider::TASKS:
The contextual bindings target the task’s $cache_key and $ttl constructor arguments. The container autowires Product_Cache_Buffer, applies those configured scalar values when the task collection is resolved, and then constructs the task.
ShutdownTask is an immutable value object pairing one Terminable service with its priority. Lower values run first. Tasks with the same priority retain provider contribution order.
Register the providers
Section titled “Register the providers”In src/App.php, register ShutdownProvider before the feature provider that contributes the cache task:
The shutdown provider registers the shared task collection, the decorated runner, and one callback on WordPress’s shutdown action at PHP_INT_MAX. It does not register that automatic callback while WordPress is installing or the plugin is being uninstalled. Registration alone does not construct or run contributed tasks.
If the application uses foundation-log, register LogProvider with the other infrastructure providers before the feature providers. The shutdown runner receives the configured LoggerInterface automatically.
Register every contributing provider before resolving the shutdown runner. The normal application bootstrap does this automatically because the runner is resolved only when the WordPress action fires.
Run tasks at WordPress shutdown
Section titled “Run tasks at WordPress shutdown”No application hook is required after ShutdownProvider is registered. At WordPress shutdown, Foundation:
- Resolves the complete contributed task collection.
- Attempts
fastcgi_finish_request()and thenlitespeed_finish_request()when available, stopping after one succeeds. - Runs tasks from the lowest priority to the highest.
- Runs equal-priority tasks in contribution order.
- Ignores repeated or recursive calls to the same runner instance.
Response finishing is best effort. A missing function, a false result, or a thrown exception does not prevent termination tasks from running.
Invoke the runner from another lifecycle
Section titled “Invoke the runner from another lifecycle”An application with another explicit termination boundary can resolve the configured contract directly:
Each runner instance executes only once. Calling it before WordPress shutdown means the later shutdown callback is a no-op for that instance.
Handle task failures
Section titled “Handle task failures”The runner catches every Throwable from a task and continues with the remaining tasks. When a PSR-3 logger is available, it records task execution at debug and task failures at error, including the task class, priority, and exception. Logger failures are also isolated.
Testing
Section titled “Testing”Test each Terminable service directly through its observable behavior. Add one provider integration test when the contribution itself matters: register ShutdownProvider and the feature provider, resolve the ShutdownRunner contract, call terminate(), and assert the task’s effect.
The package already tests priority ordering, equal-priority stability, once-only execution, recursive invocation, response-finishing fallbacks, failure isolation, and optional logging. Application tests do not need to duplicate those generic guarantees.