laravel/serializable-closure
Laravel Serializable Closure lets you safely serialize closures in PHP 7.4+, so they can be stored or passed between processes and later restored as callable closures. Set a secret key for secure serialization; a maintained fork of opis/closure 3.x.
laravel/serializable-closure is a single-purpose, framework-agnostic library that solves one problem: converting a Closure (plus its captured use variables, bound $this, and declaring scope) into a byte string that can be stored and later reconstructed. It is a fork of opis/closure 3.x, deliberately kept off the 4.x FFI-based rewrite because FFI is not enabled in typical web SAPIs.
For a TPM the important framing is that adoption is not a dependency decision — it is a persistence-model decision. laravel/serializable-closure is already a transitive dependency of laravel/framework (queue/bus closure dispatch, Bus::chain, CallQueuedClosure), so in a Laravel estate the package is present on day one; "adding it" means composer require for direct use, while "leveraging it" means deliberately allowing executable code to cross a storage boundary.
Legitimate product surfaces it unlocks:
It is a poor fit for anything cross-language, anything requiring a stable long-lived schema, or anything where the "logic" should be data rather than code.
Mechanically trivial: Composer install, then SerializableClosure::setSecretKey(...) in a service provider, serialize(new SerializableClosure($fn)), and unserialize($payload)->getClosure(). No PHP extensions, no compiled artifacts, no service to run. A spike is measured in hours, not sprints.
The real feasibility question is environmental, not technical:
SerializableClosure::setSecretKey), which interacts poorly with multi-tenant or per-tenant key isolation.eval-adjacent by design. Signing is recommended in the README but not enforced by the API; unsigned payloads are tamper-to-RCE. v2.0.16 tightened this by ensuring the signer is configured before unserialize(), which tells you unsigned use was a real-world footgun. Any design that lets a non-trusted party write a serialized closure to your storage tier is a critical-severity finding.Bus::chain nested-closure regression fixed in v2.0.11. Property hooks (8.4), virtual properties, readonly props, enums, first-class callables, and attributes each needed dedicated fixes. The library is actively chasing the PHP type system, which is good, but means patch releases are not risk-free.nunomaduro, crynobone, calebdw) rather than a solo author — acceptable. But there is no vendor SLA, and 607 stars understates reach: the dependent count in registry metadata does not capture framework-level coupling.setSecretKey is guaranteed to run before any unserialize? Octane and long-lived workers make static config assumptions fragile.Requires PHP 7.4+ per the README (2.x in practice tracks 8.1+; verify the current composer.json constraint before committing). No extensions, PSR-4 autoload, MIT license — passes typical enterprise license and SBOM review without negotiation. Composer 2 only is fine. Works identically in web, CLI, queue worker, and scheduler contexts; the one caveat is that key configuration must be applied in a bootstrap path that every entrypoint shares (a service provider for HTTP/queue, an explicit bootstrap for standalone artisan/worker/Octane).
opis/closure 3.x: near drop-in — same class shape and semantics. A protocol collision between the two stream formats was fixed in 1.0.3, which implies you should not run both against the same storage; plan to re-serialize existing payloads during cutover, then remove opis/closure. Payloads are not guaranteed interchangeable after migration.call_user_func registries, string class names, or DB-stored class references with closures), and it is usually best done for exactly one feature, not platform-wide.Well covered for the modern PHP surface: enums, readonly properties, first-class callable syntax, function attributes (including array arguments), named arguments, nullsafe operators, Carbon instances, anonymous classes, virtual properties and property hooks (8.4), constant-expression closures (8.5). Laravel 12 and 13 are explicitly tested. The compatibility gaps are source-shape gaps, not type-system gaps: closures defined in REPL/Tinker, and multiple identical-signature closures on one source line.
opis/closure or serialize()-of-callables practice. Confirm the framework's own usage is what you think it is.$this, enums, readonly props, nested closures).Small, stable API surface (one class, one signer, one set of static config calls) with frequent patch releases — v2.0.9 through v2.0.16 landed in a tight window, mixing feature enablement, PHP-version chasing, and CI hardening (pinned action SHAs, Dependabot with cooldown and auto-merge). Budget for routine patch bumps on every Laravel/PHP upgrade. Because the package is usually present transitively, the practical maintenance question is whether you pin it explicitly (^2.0) so your application, not the framework, controls the version.
No commercial support, no SLA, no LTS. Resolution path is GitHub issues/PRs and the Laravel ecosystem's general familiarity — most PHP engineers will have seen it, few will have debugged a tokenizer failure. Write an internal runbook: how to reproduce a failing payload, how to dump the embedded source, how to invalidate stored payloads, and who owns the secret key.
Cost is CPU, not I/O: each serialize/unserialize tokenizes and parses source to locate the closure body, so throughput is roughly proportional to closure complexity rather than payload size. It is a per-operation cost at the queue-dispatch, cache-write, and cache-read boundaries — benchmark it under expected volume rather than assuming it is free, especially on high-fan-out job dispatch. Payloads grow with captured state; captured fat objects (full Eloquent models, service container references) bloat storage and slow serialization. There is no cluster state to manage, but the secret key is process-global static state, so per-tenant key isolation requires process/worker separation — a real constraint if you plan multi-tenant user-authored logic.
Engineer-level ramp-up is under a day — the API is three lines. Organizational ramp-up is the real cost: a written policy on what may be persisted (closure vs. named callable vs. declarative config), signing as a non-negotiable default, a code-review checklist item for any unserialize() of a closure payload, and a deploy-time invalidation habit. Treat the enablement as a governance task with an engineering spike attached, and restrict first adoption to features where payload lifetime is shorter than a release cycle.
How can I help you explore Laravel packages today?