Object cloning — __clone, deep vs shallow copy
Concept
clone $obj creates a shallow copy of an object: all property values are copied, but object-valued properties copy the handle — both the original and the clone point to the same nested objects. This is the key subtlety: cloning is shallow by default.
What shallow copy means: If $a->child is an object, $b = clone $a creates a new $b with its own $b->child property, but $b->child and $a->child point to the same underlying object. Modifying $b->child->name also modifies $a->child->name.
__clone(): Called automatically after the clone is created. Use it to deep-copy any object properties that should be independent. Arrays in properties are automatically deep-copied (they use COW, which effectively makes them independent on modification), but nested objects are not.
Deep copy: To make nested objects independent, override __clone() and explicitly clone each object property: $this->address = clone $this->address.
Prototype pattern: Cloning is the foundation of the Prototype design pattern — create a pre-configured prototype object and clone it to produce variations without the expense of re-initialization. Useful when object creation is expensive (reading config from DB, complex setup).
Code Example
<?php
declare(strict_types=1);
class Address
{
public function __construct(
public string $city,
public string $country,
) {}
}
class User
{
public function __construct(
public string $name,
public Address $address, // object property
public array $tags = [], // array property
) {}
}
$original = new User('Alice', new Address('Paris', 'FR'), ['admin', 'user']);
// Shallow clone
$clone = clone $original;
$clone->name = 'Bob'; // only affects clone — scalar copy
$clone->address->city = 'Lyon'; // ALSO affects original! shared Address object
$clone->tags[] = 'editor'; // does NOT affect original — arrays use COW
echo $original->name; // "Alice" — unchanged
echo $original->address->city; // "Lyon" — CHANGED by clone!
echo count($original->tags); // 2 — unchanged
// Deep clone with __clone
class DeepUser
{
public function __construct(
public string $name,
public Address $address,
) {}
public function __clone(): void
{
$this->address = clone $this->address; // deep copy the Address
}
}
$orig = new DeepUser('Alice', new Address('Paris', 'FR'));
$deep = clone $orig;
$deep->address->city = 'Lyon';
echo $orig->address->city; // "Paris" — unaffected by deep clone
// Prototype pattern
class EmailTemplate
{
private array $headers = ['Content-Type: text/html'];
public function __construct(
private string $subject,
private string $body,
) {
// Simulate expensive initialization
}
public function withSubject(string $subject): static
{
$clone = clone $this;
$clone->subject = $subject;
return $clone;
}
}
$prototype = new EmailTemplate('Base Subject', '<html>...</html>');
$welcome = $prototype->withSubject('Welcome to PHP!');
$newsletter = $prototype->withSubject('Monthly Newsletter');