laravel/serializable-closure
Securely serialize and unserialize PHP closures with Laravel’s maintained fork of opis/closure 3.x, updated for modern PHP without requiring FFI. Wrap closures in SerializableClosure, set a secret key, and safely persist or transport executable callbacks.
Installation:
composer require laravel/serializable-closure
Requires PHP 7.4+ and Laravel 8+ (or standalone usage).
First Use Case: Serialize a simple closure for later execution:
use Laravel\SerializableClosure\SerializableClosure;
$closure = fn () => 'Hello, World!';
SerializableClosure::setSecretKey('your_secure_key_here');
// Serialize
$serialized = serialize(new SerializableClosure($closure));
// Later...
$unserialized = unserialize($serialized);
echo $unserialized->getClosure()(); // Outputs: Hello, World!
Where to Look First:
Storing Closures for Later Execution:
// Cache a closure for a delayed job
$delayedClosure = new SerializableClosure(fn () => $this->processPayment());
cache()->put('delayed_payment', $delayedClosure, now()->addHour());
// Retrieve and execute later
$stored = cache()->get('delayed_payment');
$stored->getClosure()();
Queue Jobs with Closures:
// Serialize a closure for a queued job
$job = new class implements ShouldQueue {
public function handle() {
$serialized = unserialize($this->serializedClosure);
$serialized->getClosure()();
}
};
$job->serializedClosure = serialize(new SerializableClosure(fn () => $this->sendEmail()));
dispatch($job);
Event Listeners with Dynamic Logic:
// Store event handlers dynamically
event(new OrderPlaced($order));
$serializedHandler = new SerializableClosure(fn () => $this->notifyAdmin($order));
cache()->forever("event_handler_{$order->id}", $serializedHandler);
// Retrieve and execute during event dispatch
$handler = cache()->get("event_handler_{$order->id}");
$handler?->getClosure()();
Middleware with Configurable Logic:
// Serialize middleware logic per route
routeMiddleware(['custom' => function ($request, $next) {
$serialized = unserialize($request->route()->middleware['serialized']);
return $serialized->getClosure()($request, $next);
}]);
// Store serialized closure in route definition
Route::get('/admin', function () {})->middleware([
'custom' => serialize(new SerializableClosure(fn ($req, $next) => $this->adminGuard($req, $next)))
]);
Laravel Caching:
Use SerializableClosure with cache()->put() to store closures for later execution (e.g., delayed processing, scheduled tasks).
Queue Jobs:
Serialize closures in job payloads to defer execution (e.g., Bus::chain() with serialized steps).
Event System: Store event handlers in caches or databases for dynamic subscription (e.g., user-specific notifications).
API Rate Limiting: Serialize rate-limiting logic per user/IP:
$limiter = new SerializableClosure(fn () => $this->throttleByRequests(100, 'minute'));
cache()->put("rate_limiter_{$ip}", $limiter);
Testing: Mock serialized closures in tests to avoid recreating complex logic:
$serialized = new SerializableClosure(fn () => $this->complexLogic());
$this->app->instance('serialized.logic', $serialized);
Secret Key Management:
SerializableClosure::setSecretKey() causes security vulnerabilities (unsigned closures can be tampered with)..env):
SerializableClosure::setSecretKey(config('app.closure_secret'));
REPL Environments:
Multiple Closures on One Line:
// Bad (may fail)
$a = fn () => 1; $b = fn () => 2;
// Good
$a = fn () => 1;
$b = fn () => 2;
PHP 8.4+ Virtual Properties:
v2.0.11+ (includes fixes for PHP 8.4+).Nested Closures:
Bus::chain()) may break in older versions.v2.0.9+ (includes nested closure fixes).First-Class Callables:
fn in namespace {}) may not serialize correctly.v2.0.0+ (explicitly supports first-class callables).Enum/Attribute Serialization:
v1.2.0+ (includes enum/attribute support).Validation Errors:
unserialize() fails, check:
use or static bindings).Logging Serialized Data:
$serialized = serialize(new SerializableClosure($closure));
file_put_contents('debug.serialized', $serialized); // Inspect manually
Testing Edge Cases:
static, self, or parent.@deprecated).Custom Signing: Override the default HMAC signing:
SerializableClosure::setSigner(function ($data, $key) {
return hash_hmac('sha256', $data, $key);
});
Unsigned Closures: Disable signing for trusted environments:
$unsigned = new SerializableClosure($closure, sign: false);
Custom Serialization:
Extend SerializableClosure to handle custom types:
class CustomSerializableClosure extends SerializableClosure {
public function __serialize(): array {
$data = parent::__serialize();
$data['custom_field'] = $this->customData;
return $data;
}
}
Integration with Laravel: Bind the package to the container for global access:
$this->app->bind(SerializableClosure::class, function () {
$closure = new SerializableClosure(...);
SerializableClosure::setSecretKey(config('app.closure_secret'));
return $closure;
});
SerializableClosure instances for the same logic to avoid re-serialization.setSecretKey() like APP_KEY—store it securely and rotate it periodically.sign: true) in production.How can I help you explore Laravel packages today?