Weave Code
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

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

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require laravel/serializable-closure
    

    Ensure your project uses PHP 7.4+ (tested up to PHP 8.5).

  2. 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!
    
  3. Where to Look First:

    • README.md: For basic usage and caveats (e.g., REPL unsupported, line-per-closure rule).
    • Changelog: Check for PHP version compatibility (e.g., PHP 8.4/8.5 fixes).
    • Tests: Browse tests/ for edge cases (e.g., nested closures, attributes).

Implementation Patterns

Core Workflows

  1. Storing Closures for Later Execution:

    • Use Case: Cache event listeners, deferred jobs, or middleware chains.
    • Pattern:
      $serializedClosure = serialize(new SerializableClosure(fn () => doWork()));
      cache()->put('deferred_task', $serializedClosure, now()->addHour());
      
    • Retrieval:
      $closure = unserialize(cache()->get('deferred_task'))->getClosure();
      $closure(); // Executes after deserialization
      
  2. Secure Serialization:

    • Signing: Enable signing to prevent tampering:
      $signed = new SerializableClosure($closure, sign: true);
      
    • Key Management: Use SerializableClosure::setSecretKey() (store keys securely, e.g., in .env).
  3. Integration with Laravel:

    • Queued Jobs:
      $job = new class implements ShouldQueue {
          public function handle() {
              $closure = unserialize($this->serializedClosure)->getClosure();
              $closure();
          }
      };
      $job->serializedClosure = serialize(new SerializableClosure(fn () => sendEmail()));
      
    • Event Listeners:
      Event::listen('order.placed', function () {
          $serialized = serialize(new SerializableClosure(fn () => logOrder()));
          // Store in DB or cache for later replay
      });
      
  4. Closure Reuse in APIs:

    • API Responses:
      return response()->json([
          'task' => serialize(new SerializableClosure(fn () => generateReport()))
      ]);
      
    • Client-Side Execution (if trusted):
      $closure = unserialize(request('task'))->getClosure();
      $closure(); // Execute on demand
      

Advanced Patterns

  1. Closure Factories:

    • Pre-serialize closures with dynamic arguments:
      $factory = new SerializableClosure(
          fn (string $name) => "Hello, {$name}",
          sign: true
      );
      $serialized = serialize($factory);
      
    • Usage:
      $closure = unserialize($serialized)->getClosure();
      $closure('Alice'); // Outputs: Hello, Alice
      
  2. Nested Closures:

    • Serialize closures that capture other closures:
      $outer = fn () => fn () => 'nested';
      $serialized = serialize(new SerializableClosure($outer));
      $inner = unserialize($serialized)->getClosure()();
      $inner(); // Outputs: nested
      
  3. Type-Safe Deserialization:

    • Use PHP 8.0+ return type hints to enforce closure signatures:
      /** @var callable(): string */
      $closure = unserialize($serialized)->getClosure();
      

Gotchas and Tips

Pitfalls

  1. REPL/Interactive Environments:

    • Issue: Closures defined in REPL (e.g., Tinker) may fail to serialize due to missing context.
    • Fix: Define closures in .php files or use eval cautiously.
  2. Multiple Closures on One Line:

    • Issue: Closures with identical signatures on the same line may collide during deserialization.
    • Fix: Place each closure on a separate line:
      // ❌ Avoid
      $a = fn () => 1; $b = fn () => 2;
      
      // ✅ Do this
      $a = fn () => 1;
      $b = fn () => 2;
      
  3. PHP Version Quirks:

    • PHP 8.4+: Virtual properties may cause issues (fixed in v2.0.3+).
    • PHP 8.5: Constant expressions in closures now supported (v2.0.13+).
    • Check: Always test with your PHP version; refer to the changelog.
  4. Security:

    • Unsigned Closures: Deserializing unsigned closures is unsafe. Always use sign: true for sensitive data.
    • Key Management: Store setSecretKey() values securely (e.g., Laravel’s config('app.key')).
  5. Class Properties:

    • Issue: SerializableClosure as a class property may unwrap during deserialization (fixed in v2.0.11).
    • Fix: Use private properties and ensure proper type hints:
      private SerializableClosure $serializedClosure;
      
  6. Attributes:

    • Issue: Method-only attributes (e.g., #[\Attribute]) may crash during deserialization (fixed in v2.0.11).
    • Workaround: Avoid attributes on serialized closures or update to the latest version.

Debugging Tips

  1. Validation:

    • Verify closures before serialization:
      if (!is_callable($closure)) {
          throw new \InvalidArgumentException('Not a callable');
      }
      
  2. Logging:

    • Log serialized/deserialized closures for debugging:
      error_log('Serialized: ' . var_export($serialized, true));
      
  3. Fallbacks:

    • Handle deserialization failures gracefully:
      try {
          $closure = unserialize($serialized)->getClosure();
      } catch (\Throwable $e) {
          logError('Closure deserialization failed', ['error' => $e->getMessage()]);
          $closure = fn () => throw new \RuntimeException('Failed to load closure');
      }
      

Extension Points

  1. Custom Serialization:

    • Extend SerializableClosure for custom logic:
      class CustomSerializableClosure extends SerializableClosure {
          public function __construct(callable $closure, array $metadata = []) {
              parent::__construct($closure, $metadata);
          }
      }
      
  2. Integration with Laravel:

    • Service Providers: Bind the package for framework-wide use:
      $this->app->singleton(SerializableClosure::class, function () {
          return new SerializableClosure(fn () => 'default');
      });
      
    • Macros: Add helper methods to closures:
      SerializableClosure::macro('retry', function (int $attempts) {
          return new static(fn () => retry($attempts, $this->getClosure()));
      });
      
  3. Testing:

    • Mock closures in tests:
      $mockClosure = fn () => 'test';
      $serialized = serialize(new SerializableClosure($mockClosure));
      $this->assertEquals('test', unserialize($serialized)->getClosure()());
      

Performance Notes

  • Overhead: Serialization adds ~10-20% overhead to closure execution. Benchmark in production-like conditions.
  • Caching: Cache serialized closures to avoid repeated parsing (e.g., in loops):
    $cacheKey = 'closure_' . md5($closureString);
    $serialized = cache()->remember($cacheKey, 3600, fn () => serialize(new SerializableClosure($closure)));
    
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.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony