Снимок на PHP
Снимок — это поведенческий паттерн, позволяющий делать снимки внутреннего состояния объектов, а затем восстанавливать их.
При этом Снимок не раскрывает подробностей реализации объектов, и клиент не имеет доступа к защищённой информации объекта.
Сложность:
Популярность:
Применимость: Реальная применимость паттерна Снимок в мире PHP под большим вопросом. Чаще всего задачу хранения копии состояния можно решить куда проще при помощи сериализации.
Концептуальный пример
Этот пример показывает структуру паттерна Снимок, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php
namespace RefactoringGuru\Memento\Conceptual;
/**
 * Создатель содержит некоторое важное состояние, которое может со временем
 * меняться. Он также объявляет метод сохранения состояния внутри снимка и метод
 * восстановления состояния из него.
 */
class Originator
{
    /**
     * @var string Для удобства состояние создателя хранится внутри одной
     * переменной.
     */
    private $state;
    public function __construct(string $state)
    {
        $this->state = $state;
        echo "Originator: My initial state is: {$this->state}\n";
    }
    /**
     * Бизнес-логика Создателя может повлиять на его внутреннее состояние.
     * Поэтому клиент должен выполнить резервное копирование состояния с помощью
     * метода save перед запуском методов бизнес-логики.
     */
    public function doSomething(): void
    {
        echo "Originator: I'm doing something important.\n";
        $this->state = $this->generateRandomString(30);
        echo "Originator: and my state has changed to: {$this->state}\n";
    }
    private function generateRandomString(int $length = 10): string
    {
        return substr(
            str_shuffle(
                str_repeat(
                    $x = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
                    ceil($length / strlen($x))
                )
            ),
            1,
            $length,
        );
    }
    /**
     * Сохраняет текущее состояние внутри снимка.
     */
    public function save(): Memento
    {
        return new ConcreteMemento($this->state);
    }
    /**
     * Восстанавливает состояние Создателя из объекта снимка.
     */
    public function restore(Memento $memento): void
    {
        $this->state = $memento->getState();
        echo "Originator: My state has changed to: {$this->state}\n";
    }
}
/**
 * Интерфейс Снимка предоставляет способ извлечения метаданных снимка, таких как
 * дата создания или название. Однако он не раскрывает состояние Создателя.
 */
interface Memento
{
    public function getName(): string;
    public function getDate(): string;
}
/**
 * Конкретный снимок содержит инфраструктуру для хранения состояния Создателя.
 */
class ConcreteMemento implements Memento
{
    private $state;
    private $date;
    public function __construct(string $state)
    {
        $this->state = $state;
        $this->date = date('Y-m-d H:i:s');
    }
    /**
     * Создатель использует этот метод, когда восстанавливает своё состояние.
     */
    public function getState(): string
    {
        return $this->state;
    }
    /**
     * Остальные методы используются Опекуном для отображения метаданных.
     */
    public function getName(): string
    {
        return $this->date . " / (" . substr($this->state, 0, 9) . "...)";
    }
    public function getDate(): string
    {
        return $this->date;
    }
}
/**
 * Опекун не зависит от класса Конкретного Снимка. Таким образом, он не имеет
 * доступа к состоянию создателя, хранящемуся внутри снимка. Он работает со
 * всеми снимками через базовый интерфейс Снимка.
 */
class Caretaker
{
    /**
     * @var Memento[]
     */
    private $mementos = [];
    /**
     * @var Originator
     */
    private $originator;
    public function __construct(Originator $originator)
    {
        $this->originator = $originator;
    }
    public function backup(): void
    {
        echo "\nCaretaker: Saving Originator's state...\n";
        $this->mementos[] = $this->originator->save();
    }
    public function undo(): void
    {
        if (!count($this->mementos)) {
            return;
        }
        $memento = array_pop($this->mementos);
        echo "Caretaker: Restoring state to: " . $memento->getName() . "\n";
        try {
            $this->originator->restore($memento);
        } catch (\Exception $e) {
            $this->undo();
        }
    }
    public function showHistory(): void
    {
        echo "Caretaker: Here's the list of mementos:\n";
        foreach ($this->mementos as $memento) {
            echo $memento->getName() . "\n";
        }
    }
}
/**
 * Клиентский код.
 */
$originator = new Originator("Super-duper-super-puper-super.");
$caretaker = new Caretaker($originator);
$caretaker->backup();
$originator->doSomething();
$caretaker->backup();
$originator->doSomething();
$caretaker->backup();
$originator->doSomething();
echo "\n";
$caretaker->showHistory();
echo "\nClient: Now, let's rollback!\n\n";
$caretaker->undo();
echo "\nClient: Once more!\n\n";
$caretaker->undo();
Output.txt: Результат выполнения
Originator: My initial state is: Super-duper-super-puper-super. 
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: srGIngezAEboNPDjBkuvymJKUtMSFX
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: UwCZQaHJOiERLlchyVuMbXNtpqTgWF
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: incqsdoJXkbDUuVOvRFYyKBgfzwZCQ
Caretaker: Here's the list of mementos:
2018-06-04 14:50:39 / (Super-dup...)
2018-06-04 14:50:39 / (srGIngezA...)
2018-06-04 14:50:39 / (UwCZQaHJO...)
Client: Now, let's rollback!
Caretaker: Restoring state to: 2018-06-04 14:50:39 / (UwCZQaHJO...)
Originator: My state has changed to: UwCZQaHJOiERLlchyVuMbXNtpqTgWF
Client: Once more!
Caretaker: Restoring state to: 2018-06-04 14:50:39 / (srGIngezA...)
Originator: My state has changed to: srGIngezAEboNPDjBkuvymJKUtMSFX
Пример из реальной жизни
Из-за того, что скрипты на PHP всегда имеют только один поток и выполняются в пределах короткой сессии, реальное использование этого паттерна в PHP выглядит для меня туманным. Хороший и удачный пример из реальной жизни, к сожалению, мне ещё не встречался. Почти любую задачу, которую я могу вообразить, можно осуществить легче с помощью сериализации.
Если вы имеете идею для хорошего примера, пожалуйста, предложите её на нашем форуме или по email support@refactoring.guru. Спасибо!