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. Basic Usage:

    use Laravel\SerializableClosure\SerializableClosure;
    
    // Set a secret key (required for security)
    SerializableClosure::setSecretKey('your_secure_random_key_here');
    
    // Wrap and serialize
    $closure = fn () => 'Hello';
    $serialized = serialize(new SerializableClosure($closure));
    
    // Unserialize and retrieve
    $unserialized = unserialize($serialized);
    $originalClosure = $unserialized->getClosure();
    echo $originalClosure(); // Output: "Hello"
    
  3. First Use Case:

    • Queue Jobs with Dynamic Logic: Store closures in a database (e.g., jobs table) to defer execution of logic until later.
      $job = new Job(['closure' => serialize(new SerializableClosure(fn () => $user->sendWelcomeEmail())]);
      

Implementation Patterns

Core Workflows

1. Storing Closures in Databases

  • Pattern: Serialize closures to JSON/string for storage (e.g., Laravel's jobs table).
    $serialized = serialize(new SerializableClosure(fn () => $user->updateProfile()));
    $job->closure = $serialized;
    $job->save();
    
  • Retrieval:
    $job = Job::find($id);
    $closure = unserialize($job->closure)->getClosure();
    $closure(); // Execute deferred logic
    

2. Event Listeners with Dynamic Logic

  • Pattern: Attach closures to events for runtime flexibility.
    Event::listen('user.created', function () {
        $closure = fn ($user) => Log::info("User {$user->id} created");
        $serialized = serialize(new SerializableClosure($closure));
        // Store $serialized in DB/cache for later replay
    });
    

3. Middleware with Conditional Logic

  • Pattern: Serialize middleware closures for dynamic routing.
    $middleware = serialize(new SerializableClosure(fn ($request) => auth()->check()));
    // Store in route middleware or cache
    

4. Testing and Mocking

  • Pattern: Replace real closures with serialized stubs in tests.
    $stub = serialize(new SerializableClosure(fn () => 'test'));
    $this->app->instance('closure_key', unserialize($stub));
    

Integration Tips

Laravel-Specific Integrations

  1. Queue Jobs: Use with Illuminate\Queue\Jobs\Job to defer complex logic.

    class ComplexJob implements ShouldQueue {
        public function handle() {
            $closure = unserialize($this->closure)->getClosure();
            $closure($this->payload);
        }
    }
    
  2. Cached Closures: Store serialized closures in Redis/Memcached for shared execution.

    Cache::put('dynamic_closure', serialize(new SerializableClosure($closure)), now()->addHour());
    
  3. Policy/Authorization: Serialize authorization logic for dynamic role-based access.

    $policy = serialize(new SerializableClosure(fn ($user, $model) => $user->can('edit', $model)));
    

Performance Considerations

  • Avoid Overhead: Only serialize closures that must be persisted (e.g., cross-request state).
  • Secret Key: Use a strong, unique key (e.g., openssl_random_pseudo_bytes(32)) for security.
  • Unsigned Closures: For internal use, disable signing:
    $unsigned = new SerializableClosure($closure, sign: false);
    

Advanced Patterns

  1. Closure Factories: Create reusable factories for common patterns.

    class ClosureFactory {
        public static function deferredEmail($userId) {
            return serialize(new SerializableClosure(
                fn () => User::find($userId)->sendEmail()
            ));
        }
    }
    
  2. Versioned Closures: Include a version in the serialized payload to handle breaking changes.

    $serialized = serialize([
        'version' => '1.0',
        'closure' => new SerializableClosure($closure),
    ]);
    
  3. Closure Introspection: Use reflection to inspect serialized closures (e.g., for debugging).

    $reflection = new ReflectionFunction($closure);
    $params = $reflection->getParameters();
    

Gotchas and Tips

Common Pitfalls

  1. REPL Environments (Tinker/Artisan):

    • Issue: Serialization fails in REPL due to missing context.
    • Fix: Avoid serializing closures in REPL; use them only in HTTP requests or CLI scripts.
  2. Multiple Closures on One Line:

    • Issue: Closures with identical signatures on the same line may merge after serialization.
    • Fix: Place each closure on a separate line.
      // ❌ Problematic
      $a = fn () => 1; $b = fn () => 2;
      
      // ✅ Safe
      $a = fn () => 1;
      $b = fn () => 2;
      
  3. Class Properties:

    • Issue: SerializableClosure as a class property may be unwrapped during deserialization.
    • Fix: Use private properties and ensure proper type hints.
      private $serializedClosure; // Store as string, not object
      
  4. PHP 8.4+ Virtual Properties:

    • Issue: May cause serialization errors.
    • Fix: Update to v2.0.11+ which includes fixes for this.
  5. Method-Only Attributes:

    • Issue: Crashes with attributes like [Attribute] on methods.
    • Fix: Use v2.0.11+ or avoid such attributes in closures.
  6. Carbon/DateTime Objects:

    • Issue: May not serialize correctly if referenced in closures.
    • Fix: Ensure Carbon instances are serializable or use ->toDateTimeString().

Debugging Tips

  1. Validation:

    • Verify the secret key is set:
      if (!SerializableClosure::getSecretKey()) {
          throw new \RuntimeException('Secret key not set!');
      }
      
  2. Inspect Serialized Data:

    • Use var_dump() to check the serialized structure:
      $serialized = serialize(new SerializableClosure($closure));
      var_dump($serialized); // Look for errors or unexpected data
      
  3. Handle Exceptions:

    • Wrap deserialization in try-catch:
      try {
          $closure = unserialize($serialized)->getClosure();
      } catch (\Throwable $e) {
          Log::error("Closure deserialization failed: {$e->getMessage()}");
          throw new \RuntimeException('Invalid closure data');
      }
      
  4. Test Edge Cases:

    • Test with:
      • Nested closures.
      • Closures capturing $this.
      • Closures with named arguments (fn ($arg1, $arg2 = 'default') => ...).

Extension Points

  1. Custom Serialization:

    • Extend SerializableClosure to add metadata:
      class CustomSerializableClosure extends SerializableClosure {
          public function __construct(\Closure $closure, public string $description = '') {
              parent::__construct($closure);
          }
      }
      
  2. Alternative Storage:

    • Use json_encode() for JSON-compatible storage:
      $serialized = json_encode([
          'closure' => serialize(new SerializableClosure($closure)),
          'metadata' => ['version' => '1.0'],
      ]);
      
  3. Security Enhancements:

    • Add HMAC validation:
      $hmac = hash_hmac('sha256', $serialized, $secretKey);
      // Store both $serialized and $hmac
      
  4. Performance Optimization:

    • Cache serialized closures in memory (e.g., Symfony Cache):
      $cache = new \Symfony\Component\Cache\Adapter\FilesystemAdapter();
      $cache->set('closure_key', $serialized, new \DateInterval('P1D'));
      

Configuration Quirks

  1. Secret Key Management:

    • Store the key in .env:
      CLOSURE_SECRET_KEY=your_secure_key_here
      
    • Load it in bootstrap/app.php:
      SerializableClosure::setSecretKey(config('app.closure_secret_key'));
      
  2. Unsigned Closures:

    • Disable signing for internal use (less secure):
      $unsigned = 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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai