0

Prototype — cloning objects, __clone in PHP

Intermediate5 min read·eng-02-005
compare

Concept

Prototype pattern creates new objects by cloning an existing instance (the prototype) rather than instantiating from a class. This is useful when object creation is expensive, requires complex initialization, or depends on state that's hard to reproduce.

When to use Prototype:

  • Creating an object is expensive (DB queries, network calls, complex computation) but cloning is cheap.
  • You need many similar objects with small variations.
  • The exact class of the object to create isn't known at compile time.
  • You want to avoid a class hierarchy of factories (alternative to Abstract Factory).

PHP clone: The clone keyword creates a shallow copy of an object. Scalar properties are copied by value. Object properties are copied by reference — the clone and original share the same nested objects. To deep clone, override __clone().

__clone(): Called automatically after clone. Override it to deep clone nested objects:

php
public function __clone() {
    $this->address = clone $this->address; // deep copy the Address object
}

clone vs new: clone copies the current state. new starts fresh. clone is only "expensive" if you added complex initialization in the constructor — the clone bypasses the constructor.

Registry pattern combined with Prototype: Store a registry of prototypes by name. $registry->clone('default_user') returns a clone of the stored prototype.

Code Example

php
<?php
// Deep clone example
class Address
{
    public function __construct(
        public string $street,
        public string $city,
    ) {}
}

class User
{
    public function __construct(
        public string $name,
        public Address $address,
        public array $permissions = [],
    ) {}

    public function __clone(): void
    {
        // Deep clone: create a new Address instead of sharing the reference
        $this->address = clone $this->address;
        // Arrays are copied by value in PHP — no need to clone
    }
}

// Prototype usage
$prototype = new User(
    name:    'Template User',
    address: new Address(street: '123 Main St', city: 'Anytown'),
    permissions: ['read', 'write'],
);

// Clone and customize
$user1 = clone $prototype;
$user1->name = 'Alice';
$user1->address->city = 'Portland'; // modifying clone's address, not prototype's

$user2 = clone $prototype;
$user2->name = 'Bob';
$user2->address->city = 'Seattle';

echo $prototype->address->city; // 'Anytown' — unchanged
echo $user1->address->city;     // 'Portland'
echo $user2->address->city;     // 'Seattle'

// Prototype Registry
class PrototypeRegistry
{
    private array $prototypes = [];

    public function register(string $key, object $prototype): void
    {
        $this->prototypes[$key] = $prototype;
    }

    public function get(string $key): object
    {
        if (!isset($this->prototypes[$key])) {
            throw new \InvalidArgumentException("Prototype [{$key}] not registered.");
        }
        return clone $this->prototypes[$key];
    }
}

$registry = new PrototypeRegistry();
$registry->register('admin', new User('', new Address('', ''), ['read', 'write', 'delete', 'admin']));
$registry->register('editor', new User('', new Address('', ''), ['read', 'write']));
$registry->register('viewer', new User('', new Address('', ''), ['read']));

// Create users from prototypes
$adminUser  = $registry->get('admin');
$adminUser->name = 'Alice';

$editorUser = $registry->get('editor');
$editorUser->name = 'Bob';

## Interview Q&A

**Q: What's the difference between shallow copy and deep copy in PHP's clone?**

A: Shallow copy (`clone` without `__clone()`) copies scalar properties by value but leaves object properties as references to the same objects. Modifying a nested object in the clone also modifies the original. Deep copy additionally clones all nested objects, giving the clone its own independent copies. Implement deep copy by overriding `__clone()` and explicitly cloning nested objects.

**Q: When would you prefer Prototype over the Factory pattern?**

A: Prefer Prototype when: (1) the object creation is expensive but copying is cheap, (2) you need to copy an object's current runtime state (including accumulated state not representable in a constructor), or (3) you have many similar objects and want to avoid a factory subclass for each variant. Prefer Factory when object creation is cheap and you want to centralize creation logic with dependencies injected by the factory.