PHPObject Cloning

Object Cloning

In PHP, a variable holding an object doesn't hold the object itself — it holds a handle that points to it. That distinction matters enormously the moment you try to duplicate an object, because the obvious approach, $copy = $original;, doesn't create a duplicate at all. It just hands out a second label for the exact same underlying object. To actually get an independent copy, PHP gives you the clone keyword, along with a hook — the __clone() magic method — for controlling exactly how deep that copy goes.

Assignment does not clone

When you assign one object variable to another with =, both variables end up pointing at the same object in memory. There is only ever one Account object here, and $owner and $sameAccount are just two names for it.

Assignment copies the handle, not the object

PHP
<?php
class Account {
    public function __construct(public string $owner, public float $balance) {}
}

$original = new Account('Maya', 100.0);
$sameAccount = $original; // NOT a copy

$sameAccount->balance += 50;

echo $original->balance, "\n";
echo $sameAccount->balance, "\n";
150
150

Depositing into $sameAccount also changed $original->balance, because there was never a second Account to begin with — just a second reference to the one that already existed. This is a frequent source of confusion for people coming from languages where assigning a struct or record copies its fields automatically.

Creating a real copy with clone

The clone keyword tells PHP to actually allocate a new object and copy every property's value across. For properties holding scalars — strings, numbers, booleans — this produces two fully independent objects.

clone produces an independent object for scalar properties

PHP
<?php
class Account {
    public function __construct(public string $owner, public float $balance) {}
}

$original = new Account('Maya', 100.0);
$copy = clone $original;

$copy->balance += 50;

echo $original->balance, "\n";
echo $copy->balance, "\n";
100
150

This time the two balances diverge, because clone allocated a second Account object and copied owner and balance into it. Changing $copy no longer touches $original.

The shallow-copy problem with nested objects

clone performs a shallow copy: it duplicates the object itself and copies each property's value, but if a property's value is itself an object, that nested object is not duplicated — both the original and the clone end up holding a handle to the very same nested object. Mutating the nested object through one of them is visible through the other, which usually isn't what you wanted when you reached for clone in the first place.

Shallow clone shares nested objects
Cloning a `Cart` object with `clone` does not clone the `Discount` object stored inside it. Both the original cart and the cloned cart keep pointing at the same `Discount` instance, so changing the discount through the clone silently changes it for the original too.

A shallow clone leaks changes through a shared nested object

PHP
<?php
class Discount {
    public function __construct(public float $percentOff) {}
}

class Cart {
    public function __construct(public array $items, public Discount $discount) {}
}

$original = new Cart(['book'], new Discount(10.0));
$copy = clone $original;

// Mutate the nested Discount object through the clone.
$copy->discount->percentOff = 50.0;

echo $original->discount->percentOff, "\n"; // changed too!
echo $copy->discount->percentOff, "\n";
echo ($original->discount === $copy->discount) ? 'same object' : 'different objects', "\n";
50
50
same object

$original and $copy are genuinely different Cart objects — the outer clone did its job — but their discount properties still point at one shared Discount object, confirmed by === reporting true. Note that $copy->items (the plain array) did get properly duplicated: arrays in PHP are copied by value, so only object properties are affected by this shallow-copy behavior.

Deep-copying with __clone()

PHP calls a special magic method named __clone() automatically, immediately after it finishes the shallow copy, but only if you define one on the class. Inside __clone(), $this refers to the brand-new clone, so you can replace any property that holds a nested object with a clone of that nested object — turning a shallow copy into a deep one exactly where it matters.

Fixing the leak with a custom __clone()

PHP
<?php
class Discount {
    public function __construct(public float $percentOff) {}
}

class Cart {
    public function __construct(public array $items, public Discount $discount) {}

    public function __clone(): void {
        // Replace the shared Discount with a fresh clone of it.
        $this->discount = clone $this->discount;
    }
}

$original = new Cart(['book'], new Discount(10.0));
$copy = clone $original;

$copy->discount->percentOff = 50.0;

echo $original->discount->percentOff, "\n"; // unaffected now
echo $copy->discount->percentOff, "\n";
echo ($original->discount === $copy->discount) ? 'same object' : 'different objects', "\n";
10
50
different objects

With __clone() in place, cloning a Cart also clones its Discount, so the two carts genuinely stop sharing any mutable state. If Discount itself held further nested objects that also needed independence, its own __clone() method would handle that layer, and PHP would call it automatically the moment the outer __clone() does clone $this->discount. This is how deep cloning composes across several levels without any single class needing to know about the internals of the others.

Only object properties need special handling

It's worth being precise about what actually needs fixing inside __clone(). Scalars (int, float, string, bool), and even arrays — including arrays of scalars — are already copied correctly by PHP's default shallow clone, because PHP copies array values rather than sharing them by reference. The only properties that need re-cloning by hand are the ones that hold objects (or resources, which can't be meaningfully duplicated at all and usually shouldn't be stored per-instance in a cloneable class).

  • clone $obj allocates a new object and copies each property's value across (a shallow copy).

  • Scalars and arrays are duplicated correctly by a shallow clone; nested objects are not — both copies keep the same reference.

  • Define __clone() to run custom logic right after the shallow copy; inside it, $this is the new clone.

  • Re-assign any object-valued property to clone $this->thatProperty inside __clone() to make the copy deep for that property.

  • $a = $b never clones anything — both variables reference the identical object.

Cloning does not re-run the constructor
`__clone()` is not a constructor and receives no arguments — it's purely a post-processing hook that runs on an already-populated clone. If your class needs to reset some state (like a unique ID or a "created at" timestamp) on every clone, `__clone()` is exactly where that logic belongs.
Tip
As soon as a class stores another object as a property, decide deliberately whether cloning that class should share or duplicate the nested object, and write a `__clone()` method that says so explicitly — don't rely on the default shallow behavior by accident. A quick way to audit this is to grep the class for property types that are themselves classes, and check each one against your `__clone()` implementation.