rcalicdan/defer 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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:

  1. Function Scope - Executes when the defer instance goes out of scope (LIFO order)
  2. Global Scope - Executes during script shutdown (LIFO order)
  3. 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):

  • Windowssapi_windows_set_ctrl_handler() (Ctrl+C, Ctrl+Break, window close)
  • Unix/Linux with pcntlSIGTERM, SIGINT, SIGHUP
  • Unix/Linux without pcntl — process monitoring, STDIN monitoring, error handler fallbacks
  • All platformsregister_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

  1. Signal handling is opt-in via Defer::enableSignals() — global defers only cover normal shutdown by default
  2. Terminate defers work optimally in FastCGI environments; other environments use fallback methods
  3. Exceptions in defer callbacks are logged but do not propagate
  4. Defer stacks have size limits to prevent memory leaks (see Performance Considerations)
  5. 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 我们能提供哪些服务?
定制开发 / 二次开发

基于 rcalicdan/defer 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 508
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 21
  • 依赖项目数: 2
  • 推荐数: 0

GitHub 信息

  • Stars: 1
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-09-22