rcalicdan/defer
Composer 安装命令:
composer require rcalicdan/defer
包简介
Framework agnostic Deferred Execution Library for PHP
README 文档
README
A framework-agnostic PHP library that provides Go-style defer functionality for resource management and cleanup operations. Execute callbacks at different scopes: function-level, global (shutdown), or after HTTP response termination.
Installation
composer require rcalicdan/defer
Requirements: PHP 8.2+
Quick Start
use Rcalicdan\Defer\Defer; function processFile($filename) { $file = fopen($filename, 'r'); // Defer cleanup - executes when function ends $defer = Defer::scope(); $defer->task(fn() => fclose($file)); // Your file processing logic here $data = fread($file, 1024); // File automatically closed when function returns return $data; }
Core Concepts
The library provides three execution scopes:
- Function Scope - Executes when the defer instance goes out of scope (LIFO order)
- Global Scope - Executes during script shutdown (LIFO order)
- Terminate Scope - Executes after HTTP response is sent (FIFO order)
Function-Scoped Defers
Function-scoped defers execute when the DeferInstance object is destroyed (typically when leaving the function scope). They execute in LIFO (Last In, First Out) order.
Basic Usage
use Rcalicdan\Defer\Defer; function databaseTransaction() { $pdo = new PDO($dsn, $user, $pass); $pdo->beginTransaction(); $defer = Defer::scope(); $defer->task(fn() => $pdo->rollback()); // Safety rollback // Perform operations $stmt = $pdo->prepare("INSERT INTO users (name) VALUES (?)"); $stmt->execute(['John']); $pdo->commit(); // Defer cleanup executes here (though rollback is harmless after commit) }
Method Chaining
$defer = Defer::scope() ->task(fn() => fclose($file)) ->task(fn() => unlink($tempFile)) ->task(fn() => echo "Cleanup completed\n"); // Execution order: echo message, unlink file, close file (LIFO)
Multiple Resources
function processMultipleFiles(array $filenames) { $defer = Defer::scope(); $handles = []; foreach ($filenames as $filename) { $handle = fopen($filename, 'r'); $handles[] = $handle; // Each file gets its own cleanup defer $defer->task(fn() => fclose($handle)); } // Process all files foreach ($handles as $handle) { // ... process file } // All files automatically closed when function ends (LIFO order) }
Global Defers
Global defers execute during normal script shutdown in LIFO (Last In, First Out) order. They are guaranteed to run when the script exits naturally or encounters a fatal error.
use Rcalicdan\Defer\Defer; Defer::global(function() { echo "First registered\n"; }); Defer::global(function() { echo "Second registered\n"; }); Defer::global(function() { echo "Third registered\n"; }); // Output on shutdown (LIFO order): // Third registered // Second registered // First registered
Practical Example
Defer::global(fn() => echo "1. Final cleanup completed\n"); Defer::global(fn() => close_database_connections()); Defer::global(fn() => cleanup_temp_files()); Defer::global(fn() => echo "4. Starting cleanup sequence...\n"); $app = new Application(); Defer::global(fn() => $app->saveState());
Signal Handling (Opt-In)
By default, global defers only run on normal script shutdown. If you need defers to also run when the process is interrupted (e.g. Ctrl+C, SIGTERM, SIGHUP), you must explicitly opt in by calling Defer::enableSignals() early in your script.
This is intentionally disabled by default — registering signal handlers can have unexpected side effects in web contexts, test runners, and scripts that manage their own signals.
// Opt in once, early in your entry point Defer::enableSignals(); Defer::global(function() { file_put_contents('/tmp/shutdown.log', 'Clean shutdown: ' . date('Y-m-d H:i:s')); }); // Your long-running process while (true) { // ... do work sleep(1); } // Without enableSignals(): cleanup runs on normal exit only // With enableSignals(): cleanup also runs on Ctrl+C, SIGTERM, SIGHUP, etc.
Signal support by platform (when opted in):
- Windows —
sapi_windows_set_ctrl_handler()(Ctrl+C, Ctrl+Break, window close) - Unix/Linux with pcntl —
SIGTERM,SIGINT,SIGHUP - Unix/Linux without pcntl — process monitoring, STDIN monitoring, error handler fallbacks
- All platforms —
register_shutdown_function()as the guaranteed baseline
Note: Signal handling is only meaningful in CLI. Calling
Defer::enableSignals()in a web context is a safe no-op.
Terminate Defers
Terminate defers execute after the HTTP response is sent to the client in FIFO (First In, First Out) order, allowing for background processing without impacting response time.
Note: Terminate defers work best in FastCGI environments (PHP-FPM, FastCGI) where fastcgi_finish_request() is available. Other environments use fallback methods but may not guarantee true post-response execution.
Basic Usage
use Rcalicdan\Defer\Defer; function handleRequest($request) { $response = processRequest($request); // Background tasks execute in FIFO order after response is sent Defer::terminate(function() use ($request) { logAnalytics($request->getUri(), $request->getUserAgent()); }); Defer::terminate(function() use ($request) { sendWelcomeEmail($request->get('email')); }); return $response; }
Error Handling
By default, terminate defers skip execution on 4xx/5xx HTTP status codes. Use the $always parameter to force execution:
// Only runs on successful responses (2xx, 3xx) Defer::terminate(fn() => incrementSuccessCounter()); // Always runs, regardless of status code Defer::terminate(function() { logRequestCompletion(); }, always: true);
Environment Support
- FastCGI/FPM ✅ — Uses
fastcgi_finish_request()for true post-response execution - CLI — Executes after main script completion
- Development Server — Flushes output buffers before execution
- Other SAPIs — Fallback with output buffer handling
Advanced Usage
Manual Execution (Testing)
// Function-scoped - manual execution in LIFO order $defer = Defer::scope(); $defer->task(fn() => echo "Second\n"); $defer->task(fn() => echo "First\n"); $defer->executeAll(); // Global - manual execution in LIFO order Defer::global(fn() => echo "Global cleanup\n"); Defer::getHandler()->executeAll(); // Terminate - manual execution in FIFO order Defer::terminate(fn() => echo "First task\n"); Defer::terminate(fn() => echo "Second task\n"); Defer::getHandler()->executeTerminate();
Monitoring and Debugging
// Check pending defer count $defer = Defer::scope(); $defer->task(fn() => cleanup1()); $defer->task(fn() => cleanup2()); echo $defer->count(); // 2 // Check whether signal handling is active var_dump(Defer::signalsEnabled()); // bool(false) by default // Inspect signal handling capabilities $info = Defer::getHandler()->getSignalHandlingInfo(); print_r($info); // Run the built-in capability test (outputs platform/method details) Defer::getHandler()->testSignalHandling();
Checking FastCGI Availability
$info = Defer::getHandler()->getHandler()->getEnvironmentInfo(); if ($info['fastcgi'] && $info['fastcgi_finish_request']) { echo "✅ Optimal terminate defer support available\n"; } else { echo "⚠️ Using fallback terminate handling\n"; }
Error Handling
All defer types include robust error handling. Exceptions in callbacks are logged but do not prevent remaining callbacks from executing:
$defer = Defer::scope() ->task(function() { throw new Exception("This won't stop other defers"); }) ->task(function() { echo "This will still execute\n"; }); // Exception is logged via error_log(), execution continues in LIFO order
Performance Considerations
- Function Scope: Limited to 50 defers per instance (oldest dropped when exceeded)
- Global Scope: Limited to 100 defers total (oldest dropped when exceeded)
- Terminate Scope: Limited to 50 defers (oldest dropped when exceeded)
- Function and Global defers execute in LIFO order
- Terminate defers execute in FIFO order
- Minimal overhead for registration and cleanup
Real-World Examples
Database Transaction with Cleanup
function transferFunds($fromAccount, $toAccount, $amount) { $pdo = new PDO($dsn, $user, $pass); $pdo->beginTransaction(); $defer = Defer::scope() ->task(fn() => auditLog("Transaction attempt completed")) // Runs first (LIFO) ->task(fn() => $pdo->rollback()); // Runs second - safety net $stmt = $pdo->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?"); $stmt->execute([$amount, $fromAccount]); $stmt = $pdo->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?"); $stmt->execute([$amount, $toAccount]); $pdo->commit(); // LIFO: audit log, then rollback (harmless after commit) }
File Processing with Temporary Cleanup
function processUploadedImage($uploadedFile) { $tempPath = '/tmp/' . uniqid() . '.tmp'; move_uploaded_file($uploadedFile['tmp_name'], $tempPath); $defer = Defer::scope() ->task(fn() => echo "Processing completed\n") // Runs first (LIFO) ->task(fn() => unlink($tempPath)); // Runs second - cleanup $image = imagecreatefromjpeg($tempPath); $resized = imagescale($image, 800, 600); $finalPath = '/uploads/' . $uploadedFile['name']; imagejpeg($resized, $finalPath); imagedestroy($image); imagedestroy($resized); return $finalPath; }
Background Processing with Terminate
function processOrder($orderData) { $order = createOrder($orderData); // Background tasks execute in FIFO order after response is sent Defer::terminate(function() use ($order) { updateInventory($order); // Runs first }); Defer::terminate(function() use ($order) { sendConfirmationEmail($order); // Runs second }); Defer::terminate(function() use ($order) { logOrderCompletion($order); // Runs third }); return ['success' => true, 'order_id' => $order->id]; }
Long-Running CLI Process with Graceful Shutdown
// Opt in to signal handling for graceful interruption Defer::enableSignals(); // Register cleanup in LIFO order Defer::global(fn() => echo "Shutdown complete\n"); Defer::global(function() { file_put_contents('/var/log/worker.log', "Worker stopped: " . date('c') . "\n", FILE_APPEND); }); Defer::global(fn() => echo "Starting shutdown sequence...\n"); while (true) { $job = getNextJob(); if (!$job) { sleep(1); continue; } processJob($job); } // LIFO cleanup runs on normal exit AND on Ctrl+C / SIGTERM (because enableSignals() was called)
Execution Order Summary
| Scope | Order | Triggered by |
|---|---|---|
| Function | LIFO | Instance going out of scope |
| Global | LIFO | Script shutdown (+ signals if opted in) |
| Terminate | FIFO | After HTTP response is sent |
Limitations
- Signal handling is opt-in via
Defer::enableSignals()— global defers only cover normal shutdown by default - Terminate defers work optimally in FastCGI environments; other environments use fallback methods
- Exceptions in defer callbacks are logged but do not propagate
- Defer stacks have size limits to prevent memory leaks (see Performance Considerations)
- Execution order differs by scope — plan your cleanup registration accordingly
License
MIT License - see LICENSE file for details.
rcalicdan/defer 适用场景与选型建议
rcalicdan/defer 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 508 次下载、GitHub Stars 达 1, 最近一次更新时间为 2025 年 09 月 22 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「php」 「defer」 「cleanup」 「lifecycle」 「resource-management」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 rcalicdan/defer 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 rcalicdan/defer 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 rcalicdan/defer 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
CSS/Javascript Minificator, Compressor and Concatenator for TYPO3 - highly configurable frontend asset optimization for CSS/JS merging, minification and compression with optional body parsing, async/defer loading, inline output, data-ignore exclusions, SRI integrity validation/calculation, external
Handles image processing on request instead of during page generation
Handy additional features for Twig within symfony 2
Library that let you catch fatal errors
Defer your requirements in SilverStripe
A vendor minify package, to remove tests and documentation and minifing all php files to save space
统计信息
- 总下载量: 508
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 21
- 依赖项目数: 2
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2025-09-22