Object Cloning
What Is Object Cloning?
In simple terms: Cloning creates a copy of an object. Normally, assigning an object to another variable does not copy it — both variables point to the same object. When you need an independent duplicate (for example, to create a new invoice based on an existing one), you clone it.
Technically: The clone operator creates a new object of the same class and copies all properties of the original into it. This is a shallow copy: properties that hold other objects still refer to the same inner objects. If the class defines a __clone() method, PHP calls it on the new object right after copying, so the class can adjust the copy — for example, by cloning nested objects (a deep copy) or resetting identifiers.
Assignment vs Clone
<?php
class Product
{
public function __construct(public string $name, public float $price) {}
}
$original = new Product('Gel Pen', 20);
$sameObject = $original;
$copy = clone $original;
$sameObject->price = 25;
$copy->price = 30;
echo "Original: {$original->price}\n";
echo "Copy: {$copy->price}\n";
var_dump($original === $sameObject, $original === $copy);
Output
Original: 25
Copy: 30
bool(true)
bool(false)
$sameObject = $originalcopies only the handle; both variables refer to one object, so changing$sameObjectchanged$original.clone $originalcreates a separate object; changing the copy does not affect the original.===compares object identity: true only for the same instance.
The Shallow Copy Problem
When an object contains other objects, clone copies the references to them, not the objects themselves:
<?php
class Address
{
public function __construct(public string $city) {}
}
class Customer
{
public function __construct(public string $name, public Address $address) {}
}
$asha = new Customer('Asha', new Address('Chennai'));
$bala = clone $asha;
$bala->name = 'Bala';
$bala->address->city = 'Madurai';
echo "{$asha->name} lives in {$asha->address->city}\n";
echo "{$bala->name} lives in {$bala->address->city}\n";
Output
Asha lives in Madurai
Bala lives in Madurai
What Went Wrong
$asha ──► Customer { name: "Asha", address ─┐ }
├──► Address { city: "Madurai" }
$bala ──► Customer { name: "Bala", address ─┘ }
The name string was copied, but both customers share one Address object. Changing Bala's city changed Asha's too.
Deep Copies With __clone()
__clone() runs on the newly created copy. Inside it, $this is the copy, so nested objects can be cloned as well:
<?php
class Address
{
public function __construct(public string $city) {}
}
class Customer
{
public function __construct(public string $name, public Address $address) {}
public function __clone(): void
{
$this->address = clone $this->address;
}
}
$asha = new Customer('Asha', new Address('Chennai'));
$bala = clone $asha;
$bala->name = 'Bala';
$bala->address->city = 'Madurai';
echo "{$asha->name} lives in {$asha->address->city}\n";
echo "{$bala->name} lives in {$bala->address->city}\n";
Output
Asha lives in Chennai
Bala lives in Madurai
How It Works
clone $ashacreates a newCustomerwith copies of the properties (still sharing the address).- PHP calls
__clone()on the new object. $this->address = clone $this->address;replaces the shared address with its own copy.- Now the two customers are fully independent.
If nested objects contain further objects, each class can define its own __clone(), so deep copying works level by level.
Practical Example: Duplicating an Invoice
A billing system often offers "Duplicate invoice": the new invoice has the same lines but must get a new number, and its lines must be independent of the original.
<?php
final class InvoiceLine
{
public function __construct(public string $item, public int $quantity, public float $price) {}
public function total(): float
{
return $this->quantity * $this->price;
}
}
final class Invoice
{
public ?string $number;
public array $lines = [];
public function __construct(string $number, public DateTimeImmutable $issuedAt)
{
$this->number = $number;
}
public function addLine(InvoiceLine $line): void
{
$this->lines[] = $line;
}
public function total(): float
{
return array_sum(array_map(fn(InvoiceLine $line): float => $line->total(), $this->lines));
}
public function __clone(): void
{
$this->number = null; // a duplicate needs a new number
$this->lines = array_map(fn(InvoiceLine $line): InvoiceLine => clone $line, $this->lines);
}
}
$original = new Invoice('INV-1001', new DateTimeImmutable('2026-10-15'));
$original->addLine(new InvoiceLine('Notebook', 3, 60));
$original->addLine(new InvoiceLine('Gel Pen', 10, 20));
$duplicate = clone $original;
var_dump($duplicate->number);
$duplicate->number = 'INV-1002';
$duplicate->lines[0]->quantity = 5;
printf("%s: ₹%.2f\n", $original->number, $original->total());
printf("%s: ₹%.2f\n", $duplicate->number, $duplicate->total());
Output
NULL
INV-1001: ₹380.00
INV-1002: ₹500.00
Step-by-Step Explanation
- Arrays are copied by value when an object is cloned, but the objects inside the array are still shared.
__clone()therefore clones eachInvoiceLinewitharray_map(). __clone()also resets the invoice number, so a duplicate can never accidentally reuse the original's number.- Changing a quantity on the duplicate does not affect the original: ₹380 vs ₹500.
- The
DateTimeImmutableis not cloned: immutable objects can be shared safely because they never change.
What Needs Deep Copying?
| Property Type | Copied by clone? | Needs Work in __clone()? |
|---|---|---|
Scalars (int, string, float, bool) | Yes, by value | No |
| Arrays of scalars | Yes, by value | No |
Mutable objects (Address, DateTime, line items) | Reference only | Yes: clone them |
| Arrays containing mutable objects | Array copied, objects shared | Yes: clone each element |
Immutable objects (DateTimeImmutable, value objects) | Reference only | No — sharing is safe |
| Resources (file handles, connections) | Same resource | Usually: open a new resource, or prevent cloning |
Readonly Properties and Cloning
Readonly properties are copied normally. Since PHP 8.3, they may be re-initialized inside __clone(), which allows deep cloning of readonly objects:
final class Order
{
public function __construct(public readonly Address $shippingAddress) {}
public function __clone(): void
{
$this->shippingAddress = clone $this->shippingAddress; // allowed in PHP 8.3+
}
}
PHP 8.5 adds "clone with", which creates a copy with selected properties changed — very useful for immutable objects:
// PHP 8.5+
$updated = clone($order, ['status' => 'shipped']);
(See Readonly Properties for the "wither" pattern.)
Preventing Cloning
Some objects must never be duplicated — for example, an object representing a database connection or a unique registry. A private __clone() method makes cloning impossible from outside the class:
<?php
final class ConnectionPool
{
public function __construct() {}
private function __clone() {}
}
try {
$copy = clone new ConnectionPool();
} catch (Error $e) {
echo $e->getMessage() . "\n";
}
Output
Call to private ConnectionPool::__clone() from global scope
Enum cases, closures created by some frameworks, and certain internal objects are also non-cloneable.
Common Mistakes
Expecting Assignment to Copy
Effect: Changes "to the copy" modify the original.
Fix: Use clone when an independent object is needed.
Shallow Copies of Nested Objects
Effect: Original and copy share inner objects and affect each other.
Fix: Clone mutable nested objects in __clone().
Forgetting Arrays of Objects
Effect: The array is copied, but its objects are shared.
Fix: Map over the array and clone each element.
Copying Identity Fields
Effect: Duplicates share IDs, numbers, or tokens that must be unique.
Fix: Reset such fields in __clone().
Cloning Objects That Hold Resources
Effect: Two objects close or use the same file handle or connection.
Fix: Recreate the resource in __clone() or forbid cloning.
Best Practices
- Use
clonedeliberately when an independent copy is required. - Implement
__clone()to deep-copy mutable nested objects and arrays of objects. - Reset unique identifiers when duplicating domain objects.
- Prefer immutable value objects, which can be shared safely and need no deep copy.
- Make
__clone()private for objects that must remain unique.
Official reference: https://www.php.net/manual/en/language.oop5.cloning.php