Паттерн Прототип в PHP
Короткий скринкаст: паттерн «Прототип» (Prototype) — клонируем объект через clone и __clone() без повторного вызова конструктора.
Описание
В скринкасте мы клонируем отчёт. Обычный clone в PHP копирует «по ссылке» вложенные объекты, поэтому меняется и оригинал. Метод __clone() делает копию глубокой: автор клонируется, а клон получает свежий id — оригинал остаётся нетронутым.
UML-диаграмма
Код
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();
Заметка: почему __clone()
Без __clone() клонирование в PHP «поверхностное»: $base->author и $copy->author — один и тот же объект. Переименовать автора в копии — значит переименовать и в оригинале. __clone() вклинивается после копирования полей и позволяет доклонировать вложенные объекты, а идентификатор — взять свежий из счётчика. Так клон получает собственное состояние и не ломает прототип.
Пример результата
После php demo.php в терминале появится:
#1 Q3 Report by Anna: intro
#2 Q3 Report (rev) by Bob: intro; summary
Преимущества
- Позволяет копировать сложные объекты, не привязываясь к их классам.
- Избавляет от повторной дорогой инициализации при создании похожих объектов.
- Новые варианты получаются правкой копии, а не созданием с нуля.
- Упрощает добавление объектов, задаваемых во время выполнения.
Когда использовать
- Когда создание объекта дороже, чем его копирование.
- Когда нужно много похожих объектов с небольшими отличиями.
- Когда конкретные классы задаются во время выполнения.
- Когда важно избежать жёсткой иерархии фабрик.
Использование в Laravel
В Laravel Прототип — это клонирование моделей: Illuminate\Database\Eloquent\Model::replicate() создаёт копию модели с теми же атрибутами, которую дальше меняют перед сохранением. Тот же приём используется при клонировании HTTP-запроса (clone $request) и в тестах, где копия объекта настраивается отдельно.