A minimal utility that brings defer-style execution (similar to Go) to PHP using object scope and destructors. It allows you to register callbacks that will run automatically at the end of a scope, in LIFO order (Last-In, First-Out).
A Defer instance represents a scope-bound execution stack.
- Register callbacks using $defer(...) or $defer->defer(...)
- Callbacks execute automatically when the object goes out of scope
- Execution is always LIFO (stack behavior)
- Execution is guaranteed during exception unwinding
- Errors inside callbacks are captured and reported, without interrupting the chain
use FastForward\Defer\Defer;
function example(): void
{
$defer = new Defer();
$defer(function () {
echo "First defer\n";
});
$defer(function () {
echo "Second defer\n";
});
echo "Inside function\n";
}
example();
Inside function
Second defer
First defer
Deferred callbacks receive exactly the arguments you pass.
function example(): void
{
$defer = new Defer();
$defer(function ($file) {
echo "Deleting {$file}\n";
}, 'temp.txt');
echo "Working...\n";
}
example();
Working...
Deleting temp.txt
Deferred callbacks execute in reverse order of registration.
$defer(fn() => unlink($file));
$defer(fn() => fclose($handle));
Execution order:
fclose($handle)
unlink($file)
Deferred callbacks always run, even if an exception occurs.
function process(): void
{
$defer = new Defer();
$defer(fn() => print "Cleanup\n");
throw new Exception('Failure');
}
try {
process();
} catch (Exception) {
echo "Exception caught\n";
}
Cleanup
Exception caught
Each Defer instance is isolated.
function outer(): void
{
$defer = new Defer();
$defer(fn() => print "Outer cleanup\n");
inner();
}
function inner(): void
{
$defer = new Defer();
$defer(fn() => print "Inner cleanup\n");
}
outer();
Inner cleanup
Outer cleanup
Creates a new Defer instance.
Inside function
Second defer
First defer0
Runs a block with an isolated defer scope.
use function FastForward\Defer\scope;
scope(function ($defer) {
$defer(fn() => print "Cleanup\n");
echo "Inside\n";
});
Structured resource management (acquire → use → cleanup).
Inside function
Second defer
First defer2
Deferred callbacks never break execution flow.
- If a callback throws, execution continues
- Errors are forwarded to an ErrorReporter
- Default behavior uses error_log()
Inside function
Second defer
First defer3
Inside function
Second defer
First defer4
use FastForward\Defer\ErrorReporter\PsrLoggerErrorReporter;
Defer::setErrorReporter(
new PsrLoggerErrorReporter($logger)
);
Inside function
Second defer
First defer6
You can bind a Defer instance to a request lifecycle.
Inside function
Second defer
First defer7
The middleware:
- creates a Defer per request
- injects it into request attributes
- ensures execution at the end of the request
use FastForward\Defer\DeferInterface;
$defer = $request->getAttribute(DeferInterface::class);
$defer(fn() => cleanup());
- Last registered → runs first
- Inner scope resolves before outer
- Deterministic cleanup
- Minimal API
- No manual flush
- Failure isolation
- Extensible reporting
- Execution is triggered by __destruct()
- Do not share instances across scopes
- Prefer short-lived instances
- Avoid long-lived/global usage
- resource cleanup
- file handling
- locks
- temporary state
- exception-safe teardown
- long-lived lifecycle management
- orchestration logic
- cases requiring explicit execution timing
Defer provides a strict and predictable cleanup model:
- automatic execution at scope end
- LIFO ordering
- safe failure handling
- pluggable reporting
- PSR-friendly integrations It is intentionally small, deterministic, and constrained.