baraja-core / shutdown-terminator
Registers handlers that run at the end of the request.
Installs: 53 489
Dependents: 2
Suggesters: 0
Security: 0
Stars: 3
Watchers: 1
Forks: 1
Open Issues: 0
pkg:composer/baraja-core/shutdown-terminator
Requires
- php: ^8.0
Requires (Dev)
- phpstan/extension-installer: ^1.1
- phpstan/phpstan: ^1.0
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-nette: ^1.0
- phpstan/phpstan-strict-rules: ^1.0
- roave/security-advisories: dev-master
- spaze/phpstan-disallowed-calls: ^2.0
This package is auto-updated.
Last update: 2026-01-04 10:20:56 UTC
README
A lightweight PHP library for registering and executing handlers at the end of script execution, with built-in memory reservation for fatal error recovery.
💡 Key Principles
- Automatic Memory Reservation: Pre-allocates memory to ensure handlers execute even during fatal out-of-memory errors
- Priority-Based Execution: Handlers are sorted and executed in order of their assigned priority
- Graceful Error Handling: Exceptions in handlers are caught and logged without disrupting other handlers
- Zero Dependencies: Works standalone or integrates seamlessly with Tracy Debugger
- Simple Interface: Single method interface for handler implementation
- Works with
die/exit: Handlers are invoked even when script terminates viadie()orexit()
🏗️ Architecture & Components
The package consists of four main components working together:
+-------------------------------------------------------------------+
| Terminator |
| (Static orchestrator managing handler lifecycle) |
| |
| +-------------------------------------------------------------+ |
| | Reserved Memory Buffer (100KB + custom allocations) | |
| +-------------------------------------------------------------+ |
| |
| +-------------------------------------------------------------+ |
| | Handler Registry: array<RegisteredHandler> | |
| | | |
| | +--------------------+ +--------------------+ | |
| | | RegisteredHandler | | RegisteredHandler | ... | |
| | | - handler | | - handler | | |
| | | - priority: 1 | | - priority: 5 | | |
| | +---------+----------+ +---------+----------+ | |
| | | | | |
| | v v | |
| | +--------------------+ +--------------------+ | |
| | | TerminatorHandler | | TerminatorHandler | | |
| | | (interface) | | (interface) | | |
| | +--------------------+ +--------------------+ | |
| +-------------------------------------------------------------+ |
+-------------------------------------------------------------------+
Shutdown Sequence
|
v
+----------------------+
| PHP Script Ends |
| (exit/die/natural) |
+-----------+----------+
|
v
+----------------------+
| shutdownHandler() |
| called by PHP |
+-----------+----------+
|
v
+----------------------+
| Sort handlers by |
| priority (ASC) |
+-----------+----------+
|
v
+----------------------+
| Execute each handler |
| with error trapping |
+----------------------+
⚙️ Component Overview
| Component | Type | Description |
|---|---|---|
Terminator |
Static Class | Main orchestrator that manages the shutdown handler registry, memory reservation, and execution lifecycle |
TerminatorHandler |
Interface | Contract that all handler classes must implement; defines the processTerminatorHandler() method |
RegisteredHandler |
Value Object | Internal wrapper that pairs a handler instance with its execution priority |
TerminatorShutdownHandlerException |
Exception | Custom exception thrown when handler execution fails; used for Tracy Debugger logging |
💡 How It Works
Memory Reservation Strategy
When the first handler is registered, Terminator immediately:
- Reserves 100 KB of memory by allocating a string buffer
- Registers PHP's native
register_shutdown_function()to invokeshutdownHandler()
This pre-allocated memory acts as a safety buffer. If PHP runs out of memory during normal execution, the shutdown handler can still execute because the reserved memory is released and becomes available.
Additional memory can be reserved per-handler for complex cleanup operations.
Shutdown Execution Flow
- Trigger: PHP shutdown event occurs (script end,
die(),exit(), or fatal error) - Guard Check: Ensures handlers run exactly once via
$hasShutdownflag - User Abort Ignored: Calls
ignore_user_abort(true)to continue even if client disconnects - Priority Sort: Handlers are sorted ascending by priority (lower numbers execute first)
- Sequential Execution: Each handler's
processTerminatorHandler()is invoked - Error Isolation: Exceptions are caught, logged (via Tracy if available), and execution continues
Handler States
The $hasShutdown flag tracks three states:
| State | Value | Meaning |
|---|---|---|
| Not initialized | null |
No handlers registered yet |
| Ready | false |
Handlers registered, waiting for shutdown |
| Completed | true |
Shutdown function has been executed |
📦 Installation
It's best to use Composer for installation, and you can also find the package on Packagist and GitHub.
To install, simply use the command:
$ composer require baraja-core/shutdown-terminator
You can use the package manually by creating an instance of the internal classes, or register a DIC extension to link the services directly to the Nette Framework.
Requirements
- PHP 8.0 or higher
- No required dependencies (optional Tracy Debugger integration)
🚀 Basic Usage
Implementing a Handler
Create a class that implements the TerminatorHandler interface:
<?php use Baraja\ShutdownTerminator\Terminator; use Baraja\ShutdownTerminator\TerminatorHandler; class MyLogger implements TerminatorHandler { public function __construct() { // Register this service to Terminator Terminator::addHandler($this); } public function processTerminatorHandler(): void { // This logic will be called when the script terminates file_put_contents('/var/log/app.log', 'Script finished at ' . date('c'), FILE_APPEND); } }
Registering with Priority
Lower priority numbers execute first:
// Execute first (priority 1) Terminator::addHandler($criticalHandler, priority: 1); // Execute second (priority 5, default) Terminator::addHandler($normalHandler); // Execute last (priority 10) Terminator::addHandler($cleanupHandler, priority: 10);
Reserving Additional Memory
For memory-intensive cleanup operations, reserve extra memory:
// Reserve additional 50 KB for this handler Terminator::addHandler($heavyHandler, reservedMemoryKB: 50); // Combine with priority Terminator::addHandler($heavyHandler, reservedMemoryKB: 100, priority: 1);
Checking Terminator State
Verify if the Terminator is ready and waiting:
if (Terminator::isReady()) { // Handlers are registered and waiting for shutdown }
🔧 Configuration Options
addHandler() Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
$handler |
TerminatorHandler |
required | The handler instance to register |
$reservedMemoryKB |
int |
0 |
Additional memory to reserve in kilobytes |
$priority |
int |
5 |
Execution order (lower = earlier); minimum is 0 |
🔍 Advanced Examples
Database Connection Cleanup
class DatabaseCleanup implements TerminatorHandler { public function __construct( private PDO $connection, ) { Terminator::addHandler($this, priority: 10); // Run last } public function processTerminatorHandler(): void { // Commit any pending transactions if ($this->connection->inTransaction()) { $this->connection->rollBack(); } } }
Session Data Persistence
class SessionPersister implements TerminatorHandler { public function __construct( private SessionManager $session, ) { Terminator::addHandler($this, priority: 1); // Run first } public function processTerminatorHandler(): void { $this->session->save(); } }
Metrics Collection
class MetricsCollector implements TerminatorHandler { private float $startTime; public function __construct() { $this->startTime = microtime(true); Terminator::addHandler($this, reservedMemoryKB: 10); } public function processTerminatorHandler(): void { $duration = microtime(true) - $this->startTime; $memory = memory_get_peak_usage(true); // Send metrics to monitoring service $this->sendMetrics([ 'duration_ms' => $duration * 1000, 'peak_memory_bytes' => $memory, ]); } private function sendMetrics(array $data): void { // Implementation } }
Error State Reporting
class ErrorReporter implements TerminatorHandler { private array $errors = []; public function __construct() { Terminator::addHandler($this, reservedMemoryKB: 50, priority: 2); set_error_handler([$this, 'captureError']); } public function captureError(int $errno, string $errstr): bool { $this->errors[] = ['code' => $errno, 'message' => $errstr]; return false; } public function processTerminatorHandler(): void { if (!empty($this->errors)) { // Send error report to external service $this->sendErrorReport($this->errors); } } private function sendErrorReport(array $errors): void { // Implementation } }
🛡️ Error Handling
Handler Exceptions
If a handler throws an exception during execution:
- CLI Environment: Error message, file, and line are printed to stdout
- Tracy Debugger: Exception is logged via
Debugger::log()asILogger::EXCEPTION - Other Handlers: Continue executing regardless of previous failures
class FaultyHandler implements TerminatorHandler { public function processTerminatorHandler(): void { throw new RuntimeException('Something went wrong'); // Other handlers will still execute } }
Tracy Debugger Integration
When Tracy Debugger is available, handler exceptions are automatically wrapped in TerminatorShutdownHandlerException and logged:
// Logged exception message format: "An error occurred while processing the shutdown function: {original message}"
💡 Best Practices
- Keep handlers lightweight: Shutdown handlers should be fast and focused
- Use appropriate priorities: Critical operations (session save) should have lower priority numbers
- Reserve memory conservatively: Only allocate what you need
- Handle your own exceptions: While Terminator catches exceptions, handlers should manage their own error states
- Avoid infinite loops: Handlers are protected from being called twice, but internal loops can still hang
- Test in CLI: The CLI output helps debug handler issues during development
⚠️ Caveats
- Handlers run synchronously in a single thread
- The
ignore_user_abort(true)call means handlers complete even if the client disconnects - Memory reservation happens immediately when handler is registered, not at shutdown
- Fatal errors like
E_ERRORwill trigger shutdown, but parse errors (E_PARSE) occur before registration
👤 Author
Jan Barášek - https://baraja.cz
📄 License
baraja-core/shutdown-terminator is licensed under the MIT license. See the LICENSE file for more details.