0

Object cloning — __clone, deep vs shallow copy

Intermediate5 min read·php-07-014
interview

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
<?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');