laravel/serializable-closure
Securely serialize and unserialize PHP closures with Laravel’s fork of opis/closure 3.x, updated for modern PHP without requiring FFI. Wrap closures in SerializableClosure, set a secret key for signing, serialize safely, then restore with getClosure().
Installation:
composer require laravel/serializable-closure
Ensure your project uses PHP 7.4+ (tested up to PHP 8.5).
First Use Case: Serialize a simple closure for later execution:
use Laravel\SerializableClosure\SerializableClosure;
$closure = fn () => 'Hello, World!';
SerializableClosure::setSecretKey('your_secure_key_here'); // Required for security
$serialized = serialize(new SerializableClosure($closure));
$unserialized = unserialize($serialized)->getClosure();
echo $unserialized(); // Outputs: Hello, World!
Where to Look First:
tests/ for edge cases (e.g., nested closures, attributes).Storing Closures for Later Execution:
$serializedClosure = serialize(new SerializableClosure(fn () => doWork()));
cache()->put('deferred_task', $serializedClosure, now()->addHour());
$closure = unserialize(cache()->get('deferred_task'))->getClosure();
$closure(); // Executes after deserialization
Secure Serialization:
$signed = new SerializableClosure($closure, sign: true);
SerializableClosure::setSecretKey() (store keys securely, e.g., in .env).Integration with Laravel:
$job = new class implements ShouldQueue {
public function handle() {
$closure = unserialize($this->serializedClosure)->getClosure();
$closure();
}
};
$job->serializedClosure = serialize(new SerializableClosure(fn () => sendEmail()));
Event::listen('order.placed', function () {
$serialized = serialize(new SerializableClosure(fn () => logOrder()));
// Store in DB or cache for later replay
});
Closure Reuse in APIs:
return response()->json([
'task' => serialize(new SerializableClosure(fn () => generateReport()))
]);
$closure = unserialize(request('task'))->getClosure();
$closure(); // Execute on demand
Closure Factories:
$factory = new SerializableClosure(
fn (string $name) => "Hello, {$name}",
sign: true
);
$serialized = serialize($factory);
$closure = unserialize($serialized)->getClosure();
$closure('Alice'); // Outputs: Hello, Alice
Nested Closures:
$outer = fn () => fn () => 'nested';
$serialized = serialize(new SerializableClosure($outer));
$inner = unserialize($serialized)->getClosure()();
$inner(); // Outputs: nested
Type-Safe Deserialization:
/** @var callable(): string */
$closure = unserialize($serialized)->getClosure();
REPL/Interactive Environments:
.php files or use eval cautiously.Multiple Closures on One Line:
// ❌ Avoid
$a = fn () => 1; $b = fn () => 2;
// ✅ Do this
$a = fn () => 1;
$b = fn () => 2;
PHP Version Quirks:
Security:
sign: true for sensitive data.setSecretKey() values securely (e.g., Laravel’s config('app.key')).Class Properties:
private properties and ensure proper type hints:
private SerializableClosure $serializedClosure;
Attributes:
#[\Attribute]) may crash during deserialization (fixed in v2.0.11).Validation:
if (!is_callable($closure)) {
throw new \InvalidArgumentException('Not a callable');
}
Logging:
error_log('Serialized: ' . var_export($serialized, true));
Fallbacks:
try {
$closure = unserialize($serialized)->getClosure();
} catch (\Throwable $e) {
logError('Closure deserialization failed', ['error' => $e->getMessage()]);
$closure = fn () => throw new \RuntimeException('Failed to load closure');
}
Custom Serialization:
SerializableClosure for custom logic:
class CustomSerializableClosure extends SerializableClosure {
public function __construct(callable $closure, array $metadata = []) {
parent::__construct($closure, $metadata);
}
}
Integration with Laravel:
$this->app->singleton(SerializableClosure::class, function () {
return new SerializableClosure(fn () => 'default');
});
SerializableClosure::macro('retry', function (int $attempts) {
return new static(fn () => retry($attempts, $this->getClosure()));
});
Testing:
$mockClosure = fn () => 'test';
$serialized = serialize(new SerializableClosure($mockClosure));
$this->assertEquals('test', unserialize($serialized)->getClosure()());
$cacheKey = 'closure_' . md5($closureString);
$serialized = cache()->remember($cacheKey, 3600, fn () => serialize(new SerializableClosure($closure)));
How can I help you explore Laravel packages today?