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

Deeply Laravel Package

chriskonnertz/deeply

Deeply is a PHP library for deep cloning objects and arrays. It recursively copies nested objects so duplicates share no references with the original, with a simple API and support for magic methods like __clone and __wakeup. Useful for snapshots, caching, and safe duplication without side effects.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package with Composer:

composer require chriskonnertz/deeply

First place to look is the README’s copy entry point and the package source for how it treats arrays, objects, private/protected properties, and circular references. In Laravel, treat Deeply as an in-memory value-object utility. It is not a replacement for Eloquent’s replicate(), and it should usually be applied to DTOs, plain objects, and arrays rather than framework internals.

The first practical use case is a draft/preview service: copy an object graph before applying user changes so the persisted/original graph is not mutated.

Task 1.1 — Minimal deep copy

Given a Quote object with a nested Customer object and an array of QuoteLine objects, implement a preview method that deep-copies the quote, applies a change to the copy, and leaves the original untouched.

use ChrisKonnertz\Deeply\Deeply;

final class QuoteDraftService
{
    public function preview(Quote $quote, array $changes): Quote
    {
        $draft = Deeply::copy($quote);

        // Apply changes to $draft, e.g. $draft->customer->name = $changes['customer_name'];

        return $draft;
    }
}

Expected evidence:

  • $draft !== $quote
  • $draft->customer !== $quote->customer
  • Nested line objects are not shared between draft and original
  • The original object graph still has its original values after the draft is mutated

If the installed version exposes an instance API instead of a static one, accept the equivalent documented entry point, e.g. (new Deeply())->copy($quote), provided the candidate is consistent.

Implementation Patterns

Pattern 1 — Copy at the service boundary before mutation

Use Deeply at the point where an in-memory graph is about to be modified but must not affect the caller’s graph.

public function applyDiscount(Quote $quote, float $percent): Quote
{
    $updated = Deeply::copy($quote);

    foreach ($updated->lines as $line) {
        $line->discount = $line->subtotal * $percent;
    }

    return $updated;
}

This is useful for previews, draft states, simulations, and “what-if” calculations. Keep the copy boundary explicit in a service/action class rather than scattering deep copies through controllers.

Pattern 2 — Snapshot and undo/redo stacks

Deep copies make simple in-memory undo stacks possible when working with plain DTOs or value objects.

final class DraftHistory
{
    /** @var Draft[] */
    private array $snapshots = [];

    public function remember(Draft $draft): void
    {
        $this->snapshots[] = Deeply::copy($draft);
    }

    public function restore(int $index): Draft
    {
        return Deeply::copy($this->snapshots[$index]);
    }
}

This pattern is appropriate for Livewire components, admin preview screens, and editorial tools where the state graph is small and plain. Do not use it as a substitute for database transactions or persisted versioning.

Pattern 3 — Test isolation explicitly

When testing deep-copy behaviour, assert both top-level and nested object identity.

it('does not mutate the original quote when previewing', function () {
    $quote = Quote::factory()->make([
        'customer' => Customer::factory()->make(['name' => 'Original']),
    ]);

    $draft = (new QuoteDraftService())->preview($quote, [
        'customer_name' => 'Changed',
    ]);

    expect($draft)->not->toBe($quote)
        ->and($draft->customer)->not->toBe($quote->customer)
        ->and($quote->customer->name)->toBe('Original');
});

Use not->toBe() for object identity and value assertions for the original graph. This catches shallow-copy mistakes that ordinary value assertions miss.

Pattern 4 — Laravel integration and Eloquent boundaries

For plain DTOs, bind a copy service or use Deeply directly in an action/service class. If the package is instance-based in your version, register it as a singleton:

$this->app->singleton(Deeply::class);

For Eloquent models, prefer Laravel’s own primitives:

  • Use replicate() to duplicate a model’s attributes.
  • Deep-copy plain DTOs/arrays that represent the data you want to carry over.
  • Avoid deep-copying a full Eloquent model graph containing the connection, event dispatcher, relations, and internal state.

Task 2.1 — Duplicate action

Implement a DuplicateOrderAction that takes an OrderData DTO containing nested OrderLineData objects and returns a new DTO with copied lines. Explain why clone $orderData is insufficient and why deep-copying a full Eloquent Order model is risky.

Expected discussion:

  • clone only clones the top-level object; nested objects remain shared.
  • Deeply recursively copies nested objects/arrays.
  • Eloquent models carry framework internals and persistence state; use replicate() or DTO mapping for model duplication.

Gotchas and Tips

  • Circular references are handled, but not free. Deeply can safely copy cyclic graphs, but large object graphs still cost time and memory. Copy at application boundaries, not in hot loops.
  • Eloquent models are dangerous copy targets. A model may hold a database connection, event dispatcher, original attribute state, loaded relations, and observers. Deep-copying it can produce a strange half-cloned object. Use replicate() plus explicit relation handling, or copy a plain DTO.
  • Framework objects are usually bad targets. Avoid deep-copying Request, Container, UploadedFile, jobs, or objects holding closures/resources/PDO handles. Copy only the validated/plain data you actually need.
  • __clone() still runs. If a class defines __clone(), that method executes during the deep copy. Check for side effects and readonly-property constraints.
  • Static state is not copied. Static properties belong to the class, not the instance. A deep copy will not isolate static caches or class-level state.
  • Debug identity with === and spl_object_id(). Value assertions can pass even when references are shared. Use object identity checks to verify nested isolation.
dump($draft === $original);                 // false
dump($draft->customer === $original->customer); // should be false
dump(spl_object_id($draft->customer), spl_object_id($original->customer));
  • Extension point: customise cloning locally. If a value object contains a non-cloneable collaborator, implement __clone() on that class to replace or reset the collaborator, or keep non-cloneable services outside the deep-copied graph.
  • No Laravel-specific config is required. The main “configuration” is discipline: keep the copied graph small, plain, and free of framework internals.

Task 3.1 — Diagnose the bug

A developer writes this in a controller:

$draft = Deeply::copy($request);
$draft->merge(['status' => 'draft']);

What is wrong with this use of Deeply, and what should they do instead?

Expected answer:

  • Request contains framework internals, input bags, files, and potentially closures/resources; it is not a safe deep-copy target.
  • Copy only the validated plain data/DTO needed for the draft.
  • Prefer something like $draft = Deeply::copy($validatedData) or map validated input into an application DTO before copying/mutating it.
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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels