PHP Prototype Pattern
A short screencast: the Prototype pattern in PHP — cloning an object with clone and __clone() without re-running the constructor.
Overview
In this screencast we clone a report. A plain clone in PHP copies nested objects by reference, so the original changes too. The __clone() method makes the copy deep: the author is cloned, and the clone receives a fresh id — the original stays untouched.
UML diagram
Code
Author.php
class Author
{
public function __construct(public string $name) {}
}
Report.php
class Report
{
private static int $nextId = 2;
public function __construct(
public int $id,
public string $title,
public Author $author,
public array $sections = [],
) {}
public function addSection(string $section): void
{
$this->sections[] = $section;
}
public function describe(): string
{
return "#{$this->id} {$this->title} by {$this->author->name}: " . implode('; ', $this->sections);
}
public function __clone()
{
$this->id = self::$nextId++;
$this->author = clone $this->author;
}
}
demo.php
require_once 'Author.php';
require_once 'Report.php';
$base = new Report(1, 'Q3 Report', new Author('Anna'), ['intro']);
$copy = clone $base;
$copy->title = 'Q3 Report (rev)';
$copy->author->name = 'Bob';
$copy->addSection('summary');
echo $base->describe();
echo PHP_EOL;
echo $copy->describe();
Note: why __clone()
Without __clone() cloning in PHP is shallow: $base->author and $copy->author are the same object. Rename the author in the copy and you rename it in the original too. __clone() hooks in right after the fields are copied, so you can re-clone nested objects and take a fresh id from the counter. That way the clone gets its own state and never corrupts the prototype.
Example output
Running php demo.php in the terminal produces:
#1 Q3 Report by Anna: intro
#2 Q3 Report (rev) by Bob: intro; summary
Advantages
- Clones complex objects without coupling to their classes.
- Avoids re-running expensive initialisation when building similar objects.
- New variants come from tweaking a copy instead of building from scratch.
- Simplifies adding objects configured at runtime.
When to use
- When creating an object is costlier than copying it.
- When you need many similar objects with small differences.
- When concrete classes are chosen at runtime.
- When you want to avoid a rigid factory hierarchy.
Usage in Laravel
In Laravel a prototype is model cloning: Illuminate\Database\Eloquent\Model::replicate() makes a copy of a model with the same attributes, which is then adjusted before saving. The same idea is used when an HTTP request is cloned (clone $request) and in tests where a copy of an object is configured independently.