Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Serializable Closure Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

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:

  • User- or admin-authored logic: webhook transforms, saved filters, pricing/eligibility rules, per-tenant hooks.
  • Pipeline/workflow definitions where each step is a callable, persisted between steps.
  • Deferred execution payloads (queued jobs, scheduled one-offs, batch chains) carrying inline callbacks.
  • Cache entries holding computed closures for expensive-to-build callables.

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.

Integration feasibility

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:

  • The source file of the closure must still exist and be readable at unserialize time. The library tokenizes source to extract the closure body; there is no bytecode capture.
  • The serialized payload is coupled to source text and location. Deploying new code between serialization and deserialization can change behavior or throw.
  • Secret key state is process-global static configuration (SerializableClosure::setSecretKey), which interacts poorly with multi-tenant or per-tenant key isolation.

Technical risk

  • Remote code execution surface. Reconstruction is 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.
  • Code drift across deploys. Payloads embed source and line/column anchors. Long-lived queue backlogs, cached closures, or DB-persisted rules spanning a release are a latent breakage class. This is the single most under-estimated risk for TPMs.
  • Silent misbehavior over hard failure. The README caveats — REPL/Tinker unsupported, multiple same-signature closures on one line may be indistinguishable — are symptoms of a parser-based approach. Generated code (closures built from strings/templates, minified or obfuscated sources, closures inside anonymous migrations) has historically produced bugs; see v2.0.12.
  • Regression history in edge cases. v2.0.9 introduced a 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.
  • Bus factor / ownership. Effectively maintained by the Laravel core team (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.

Key questions

  1. Where will these payloads live, and who can write to that store? If any non-trusted principal can, this is disqualified without an out-of-band signing/authorization design.
  2. What is the maximum intended lifetime of a serialized closure? Anything beyond one deploy cycle needs an explicit invalidation or re-serialization strategy.
  3. Are we storing closures as logic (rules, hooks) or as transport (queue payloads)? The second is largely already solved by the framework; the first is a product commitment.
  4. What is the secret-key lifecycle — single global key, rotation policy, KMS/Secrets Manager integration, and what happens to existing payloads on rotation?
  5. Are we on PHP 8.4/8.5 features (property hooks, constant expressions) that force us onto a recent 2.x patch?
  6. Does the runtime include a worker/CLI path where setSecretKey is guaranteed to run before any unserialize? Octane and long-lived workers make static config assumptions fragile.

Integration Approach

Stack fit

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

Migration path

  • From 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.
  • From 1.x to 2.x: 2.0.0 changed handling of namespaced closures vs. first-class callables. Treat as breaking for stored payloads; either re-serialize or drain queues before the bump.
  • From nothing: most Laravel apps already have it transitively — the "migration" is a code-level one (replacing 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.

Compatibility

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.

Sequencing

  1. Inventory — find existing transitive usage and any current opis/closure or serialize()-of-callables practice. Confirm the framework's own usage is what you think it is.
  2. Spike (1–2 days) — one feature, signing enabled from the start, a golden-file test that round-trips representative closures (captured $this, enums, readonly props, nested closures).
  3. Harden — key from secret manager via env, injected in a bootstrap provider, rotation runbook, signing mandatory (no unsigned path in code review), and an explicit "never unserialize untrusted input" lint/ADR.
  4. Pilot — single low-blast-radius feature with a bounded payload TTL (cache entries, short-lived job callbacks) before any DB-persisted long-lived rules.
  5. Gate — only expand to durable, user-authored logic after the deploy-drift strategy (versioned payloads or invalidate-on-deploy) is demonstrated.

Operational Impact

Maintenance

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.

Support

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.

Scaling

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.

Failure modes

  • Tampered or unsigned payload → arbitrary code execution. Highest severity, lowest likelihood if signing is mandated and the store is write-protected.
  • Key loss or unplanned rotation → mass deserialization failure. All stored payloads become unusable at once; there is no recovery path other than re-serialization from source-of-truth definitions.
  • Deploy drift → exceptions or changed behavior. Payloads created before a deploy and consumed after can fail or, worse, execute stale logic. Mitigations: short TTLs, drain queues at deploy, version payload envelopes, invalidate caches on release.
  • Non-serializable captures → hard failure at write time. Captured PDO handles, sockets, stream resources, or an injected container will throw when serialized. This is the most common developer-experience failure and belongs in the spike's test matrix.
  • Parser edge cases → silent misbehavior. Multiple same-signature closures on one line, REPL-defined closures, closures generated from strings or inside anonymous migrations. Enforce a coding standard: one closure per line, no REPL-authored closures destined for persistence.
  • Worker/bootstrap misconfiguration → "signer not configured" errors surfacing only in queue or CLI contexts, per the v2.0.16 change. Test all entrypoints, not just HTTP.

Ramp-up

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.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections