PHP Memento Design Pattern Поведенческий паттерн

Паттерн Снимок (Memento) в PHP

Короткий скринкаст: паттерн «Снимок» (Memento) — сохраняем состояние объекта, чтобы потом можно было к нему вернуться. Реализуем отмену правок текста.

Memento Pattern — открыть страницу урока

Описание

Снимок позволяет сохранять и восстанавливать состояние объекта, не раскрывая его внутреннее устройство. Редактор Editor — это источник состояния: его метод save() создаёт снимок Memento с текущим текстом, а restore() восстанавливает текст из снимка. Снимки хранит отдельный объект-смотритель History: он складывает их в стек (push) и достаёт последний (pop). Клиент просит редактор сохранить снимок и передаёт его истории — сам редактор не знает, где и сколько снимков лежит, а снимок снаружи доступен только для чтения.

UML-диаграмма

classDiagram class Editor { -text: string +setText(text: string): void +getText(): string +save(): Memento +restore(memento: Memento): void } class Memento { -text: string +getText(): string } class History { -mementos: array +push(memento: Memento): void +pop(): Memento } Editor ..> Memento : creates History o-- Memento : stores Editor ..> History : uses

Код

Memento.php

class Memento
{
    public function __construct(private readonly string $text) {}

    public function getText(): string
    {
        return $this->text;
    }
}

Editor.php

class Editor
{
    private string $text = '';

    public function setText(string $text): void
    {
        $this->text = $text;
    }

    public function getText(): string
    {
        return $this->text;
    }

    public function save(): Memento
    {
        return new Memento($this->text);
    }

    public function restore(Memento $memento): void
    {
        $this->text = $memento->getText();
    }
}

History.php

class History
{
    private array $mementos = [];

    public function push(Memento $memento): void
    {
        $this->mementos[] = $memento;
    }

    public function pop(): ?Memento
    {
        return array_pop($this->mementos);
    }
}

demo.php

require_once 'Memento.php';
require_once 'Editor.php';
require_once 'History.php';

$editor = new Editor;
$history = new History;

$editor->setText('Version 1');
$history->push($editor->save());

$editor->setText('Version 2');
$history->push($editor->save());

$editor->setText('Version 3');
echo $editor->getText().PHP_EOL;

$editor->restore($history->pop());
echo $editor->getText().PHP_EOL;

Пример результата

После php demo.php видна версия 3, а затем откат к версии 2 из снимка:

Version 3
Version 2

Преимущества

  • Состояние можно сохранять и откатывать, не нарушая инкапсуляцию объекта.
  • Снимок неизменяем (readonly), поэтому хранить его безопасно.
  • Логика хранения истории вынесена в отдельный объект-смотритель.
  • Упрощает реализацию отмены, истории и транзакций.

Когда использовать

  • Когда нужна отмена или откат состояния объекта.
  • Когда надо сохранить историю изменений и вернуться к прошлому снимку.
  • Когда прямое копирование полей объекта нарушило бы инкапсуляцию.
  • Когда снимок должен оставаться неизменным после создания.

Использование в Laravel

В Laravel Снимок встречается там, где важно сохранить состояние для отката. Eloquent хранит исходные значения атрибутов модели и методы getOriginal(), getChanges() и isDirty() позволяют вернуться к состоянию до сохранения. Миграции используют снимки схемы для rollback, а Illuminate\Support\Facades\Cache сохраняет готовые значения, которые позже восстанавливаются. Ближе всего к классическому снимку — транзакции БД с откатом: DB::beginTransaction() и DB::rollBack() возвращают данные к точке сохранения.