Prototype — cloning objects, __clone in PHP
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:
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
// 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.