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
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
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.
A shallow clone leaks changes through a shared nested object
<?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
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 $objallocates 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,$thisis the new clone.Re-assign any object-valued property to
clone $this->thatPropertyinside__clone()to make the copy deep for that property.$a = $bnever clones anything — both variables reference the identical object.