PHPackages                             rcalicdan/defer - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. rcalicdan/defer

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

rcalicdan/defer
===============

Framework agnostic Deferred Execution Library for PHP

1.2.0(1mo ago)1433MITPHPPHP ^8.2CI passing

Since Sep 22Pushed 1mo agoCompare

[ Source](https://github.com/rcalicdan/defer)[ Packagist](https://packagist.org/packages/rcalicdan/defer)[ Docs](https://github.com/rcalicdan/defer)[ RSS](/packages/rcalicdan-defer/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (3)Dependencies (8)Versions (4)Used By (0)

Defer - PHP Deferred Execution Library
======================================

[](#defer---php-deferred-execution-library)

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
------------

[](#installation)

```
composer require rcalicdan/defer
```

**Requirements:** PHP 8.2+

Quick Start
-----------

[](#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
-------------

[](#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)

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

[](#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

[](#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

[](#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)

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

[](#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)

[](#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)

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

[](#basic-usage-1)

```
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

[](#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

[](#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
--------------

[](#advanced-usage)

### Manual Execution (Testing)

[](#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

[](#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

[](#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
--------------

[](#error-handling-1)

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
--------------------------

[](#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
-------------------

[](#real-world-examples)

### Database Transaction with Cleanup

[](#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

[](#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

[](#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

[](#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
-----------------------

[](#execution-order-summary)

ScopeOrderTriggered byFunctionLIFOInstance going out of scopeGlobalLIFOScript shutdown (+ signals if opted in)TerminateFIFOAfter HTTP response is sentLimitations
-----------

[](#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
-------

[](#license)

MIT License - see [LICENSE](LICENSE) file for details.

###  Health Score

43

—

FairBetter than 90% of packages

Maintenance90

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~112 days

Total

3

Last Release

50d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/163510169?v=4)[Reymart A. Calicdan](/maintainers/rcalicdan)[@rcalicdan](https://github.com/rcalicdan)

---

Top Contributors

[![rcalicdan](https://avatars.githubusercontent.com/u/163510169?v=4)](https://github.com/rcalicdan "rcalicdan (18 commits)")

---

Tags

phpcleanuplifecycledeferresource-management

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/rcalicdan-defer/health.svg)

```
[![Health](https://phpackages.com/badges/rcalicdan-defer/health.svg)](https://phpackages.com/packages/rcalicdan-defer)
```

###  Alternatives

[liborm85/composer-vendor-cleaner

Composer Vendor Cleaner removes unnecessary development files and directories from vendor directory.

34389.0k1](/packages/liborm85-composer-vendor-cleaner)[imanghafoori/laravel-anypass

A minimal yet powerful package to help you in development.

21422.6k](/packages/imanghafoori-laravel-anypass)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
