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

Polyfill Deepclone Laravel Package

symfony/polyfill-deepclone

Pure-PHP implementation of the deepclone extension's functions for converting value graphs to arrays and back, preserving object identity, references, cycles, and private state—faster and more memory-efficient than serialize/unserialize.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require symfony/polyfill-deepclone:^1.43
    

    No additional configuration is required—it works as a drop-in replacement for the native deepclone extension. Pin to ^1.43 if you rely on strict malformed-payload reporting (see Gotchas).

  2. First Use Case: Convert an object to a serializable array and back:

    use Symfony\Polyfill\DeepClone\deepclone;
    
    $original = new class {
        public $name = "Test";
        private $secret = "hidden";
        public $nested = ["a" => 1, "b" => new stdClass()];
    };
    
    // Convert to array (preserves structure, no objects)
    $array = deepclone_to_array($original);
    
    // Rebuild object (preserves private state, cycles, references)
    $cloned = deepclone_from_array($array);
    
  3. Where to Look First:

    • API Docs: Focus on deepclone_to_array() and deepclone_from_array() in the Symfony Polyfill README.
    • Edge Cases: Review NotInstantiableException and ClassNotFoundException for error handling, plus the malformed-payload errors introduced in v1.43.2.

Implementation Patterns

Core Workflows

  1. Deep Cloning Objects with Private State:

    $user = new User(['name' => 'John', 'email' => '[email protected]']);
    $clonedUser = deepclone_from_array(deepclone_to_array($user));
    
    // Private properties and methods are preserved
    assert($clonedUser->getPrivateData() === $user->getPrivateData());
    
  2. Handling Circular References:

    $parent = new ParentModel();
    $child = new ChildModel();
    $parent->children[] = $child;
    $child->parent = $parent; // Circular reference
    
    $array = deepclone_to_array($parent);
    $clonedParent = deepclone_from_array($array);
    // Circularity is preserved without infinite recursion
    
  3. Performance Optimization with Flags (PHP 8.3+):

    // Preserve references for shared state (e.g., DTOs)
    $dto = new DataTransferObject();
    $clonedDto = deepclone($dto, DEEPCLONE_HYDRATE_PRESERVE_REFS);
    
  4. Round-Trip Cost Profile:

    • As of v1.43.2, deepclone_from_array() is measurably faster, so the hydrate half of the round trip is cheaper than it used to be. The export half (deepclone_to_array()) is unchanged—if you're profiling, start there.

Laravel-Specific Integrations

  1. Eloquent Model Cloning:

    $user = User::with('posts')->find(1);
    $clonedUser = deepclone_from_array(deepclone_to_array($user));
    
    // Relationships and accessors are preserved
    assert(count($clonedUser->posts) === count($user->posts));
    
  2. Request Object Cloning for Middleware:

    $request = app(Request::class);
    $clonedRequest = deepclone_from_array(deepclone_to_array($request));
    
    // Useful for testing or parallel processing
    
  3. Caching Complex Objects:

    $cacheKey = 'complex_object_' . md5(serialize($object));
    $cachedArray = deepclone_to_array($object);
    cache()->put($cacheKey, $cachedArray, now()->addHours(1));
    
    // Later... treat the payload as untrusted input
    try {
        $restoredObject = deepclone_from_array(cache()->get($cacheKey));
    } catch (Throwable $e) {
        cache()->forget($cacheKey); // Stale/corrupt entry — rebuild from source
        $restoredObject = rebuildObject();
    }
    

Testing Patterns

  1. Isolated Test Data:

    $original = new ExpensiveModel();
    $testData = deepclone_from_array(deepclone_to_array($original));
    
    // Safe to modify $testData without affecting $original
    
  2. Asserting Deep Clones:

    use Symfony\Polyfill\DeepClone\deepclone;
    
    $original = new class { public $value = 42; };
    $cloned = deepclone($original);
    
    $this->assertNotSame($original, $cloned);
    $this->assertEquals($original->value, $cloned->value);
    
  3. Testing Failure Paths:

    • Since v1.43.2 the polyfill reports malformed payloads the same way the native extension does. That makes it feasible to write expectException() tests against hand-crafted broken payloads, and to have them pass identically on PHP 8.3+ native deepclone.

Gotchas and Tips

Pitfalls

  1. Malformed Payloads Now Throw (v1.43.2):

    • deepclone_from_array() now reports malformed payloads like the native extension instead of tolerating them. Anything that persists a payload and restores it later—cache, queues, database columns, files—should be treated as untrusted input and wrapped:
      try {
          $cloned = deepclone_from_array($payload);
      } catch (Throwable $e) {
          // Corrupt/legacy payload: invalidate and rebuild
      }
      
    • If you were (perhaps unknowingly) relying on lenient behaviour for partially-corrupt arrays, this is the release that surfaces it.
  2. SPL \0 Keys:

    • The polyfill drops SPL \0 keys by default (fixed in v1.37.0). If your code relies on these, ensure they’re handled upstream (e.g., normalize keys before cloning).
  3. Metadata Limits:

    • Object metadata is capped at 1M entries (v1.37.0). Large object graphs (e.g., deeply nested Eloquent relations) may hit this limit. Monitor with:
      try {
          $array = deepclone_to_array($hugeObject);
      } catch (Error $e) {
          if (str_contains($e->getMessage(), 'IS_LONG objectMeta count')) {
              // Handle or split the object graph
          }
      }
      
  4. Deprecated Scoped Mode:

    • v1.37.0 removes "scoped mode." Update any custom logic using:
      // Old (deprecated)
      deepclone($obj, DeepClone::SCOPED);
      
      // New (only option)
      deepclone($obj, DeepClone::MANGLED);
      
  5. Non-Serializable Objects:

    • Resources (e.g., file handles, sockets) or objects without __serialize()/__unserialize() will throw NotInstantiableException. Handle gracefully:
      try {
          $cloned = deepclone_from_array($array);
      } catch (NotInstantiableException $e) {
          // Fallback or log
      }
      

Debugging Tips

  1. Verify Array Representation:

    • Inspect the output of deepclone_to_array() to debug issues:
      $array = deepclone_to_array($object);
      var_export($array); // Check for missing/incorrect data
      
  2. Reproduce Malformed-Payload Failures Deliberately:

    • Because restore is now strict, the fastest way to debug a failing restore is to diff the stored payload against a freshly generated one:
      $stored   = cache()->get($key);
      $expected = deepclone_to_array($object);
      
      // Any structural drift (truncated write, JSON-ish cache driver, size cap) shows up here
      dump(array_diff_key($expected, $stored), array_diff_key($stored, $expected));
      
    • Note that cache backends which round-trip through JSON will mangle integer-like keys and object markers—prefer serialize()-safe drivers for raw arrays.
  3. Check for Cycles:

    • Use var_export() to dump the array to a .php file and inspect cycles:
      file_put_contents('debug_clone.php', '<?php return ' . var_export($array, true) . ';');
      
  4. Performance Profiling:

    • Compare polyfill vs. native deepclone (PHP 8.3+), and time the two halves separately—restore got faster in v1.43.2, export did not:
      $t = microtime(true); $array  = deepclone_to_array($obj); $exportMs = (microtime(true) - $t) * 1000;
      $t = microtime(true); $cloned = deepclone_from_array($array); $importMs = (microtime(true) - $t) * 1000;
      

Extension Points

  1. Custom Allowed Classes:

    • Restrict cloning to specific classes for security:
      $array = deepclone_to_array($obj, [User::class, Post::class]);
      
  2. Post-Processing:

    • Modify the array before/after cloning:
      $array = deepclone_to_array($obj);
      $array['modified'] = true; // Add/remove data
      $cloned = deepclone_from_array($array);
      
    • Post-processing hand-built arrays is fine, but remember the restore step validates structure—mutations must keep the payload internally consistent or they'll be rejected.
  3. Integration with Symfony VarExporter:

    • Use the same wire format for interoperability:
      use Symfony\Component\VarExporter\VarExporter;
      
      $exported = VarExporter::export($obj, VarExporter::EXPORT_DEEP_CLONE);
      $cloned = deepclone_from_array(eval('return ' . $exported . ';'));
      

Laravel-Specific Quirks

  1. Eloquent Relationships:

    • Deep cloning may not preserve lazy-loaded relationships. Eager-load first:
      $user = User::with('posts.comments')->find(1);
      $cloned = deepclone_from_array(deepclone_to_array($user));
      
  2. Request Objects:

    • Cloning Illuminate\Http\Request may not preserve uploaded files. Handle separately:
      $requestData = deepclone_to_array($request->all());
      $clonedRequest = new Request($requestData);
      
  3. Service Container:

    • Avoid cloning bound services (e.g., repositories). Use factories instead:
      // Bad: Cloning a singleton
      $clonedRepo = deepclone($this->app->make(Repository::class));
      
      // Good: Re-instantiate
      $clonedRepo = $this->app->make(Repository::class);
      
  4. Queued Jobs and Cached Payloads:

    • With strict payload validation in v1.43.2, jobs that serialized model graphs through this polyfill and sat in a queue across the upgrade can fail on deserialize. Drain or version your queue name on deploy, and key cached payloads with a schema version so old entries are simply ignored rather than throwing.
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.
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen