【PHPデザインパターン】17_Memento～スナップショットを取る

# 引用記事
この記事を書くきっかけになったブログです。

記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。

> [Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Memento～スナップショットを取る](http://d.hatena.ne.jp/shimooka/20141218/1418888730)

# 概要
- 「memento」は「記憶」「思い出」のような意味。（同名の映画もありますね。）
- 「Snapshotパターン」とも呼ばれる。
- アンドゥ（Undo：やり直し）を実現する。
- ある時点のオブジェクトの状態を別のオブジェクトとして保存しておき、状態が変化した場合でも、その時の状態に戻すことを可能にする。
- 状態を生成するクラスとその履歴の管理をするクラスを分離する。

## 構成要素
### Mementoクラス
- Originatorオブジェクトの記憶を保持する記憶用クラス。
- 厳密には、Originator以外のオブジェクトによってアクセスさせないようにする必要がある。
- PHPには「特定のクラスだけにアクセスを許可する」といった制御機構は存在しないため、本来のMementoパターンの構造を若干変更する必要がある。

### Originatorクラス
- オブジェクトの内部状態を保存される側のクラス。
- 内部状態をMementoオブジェクトに詰め込み、それを返すメソッドを持つ。

### Caretakerクラス
- Mementoオブジェクトを管理するクラス

# 実演
## 処理の流れ
- addボタンで入力したコメントを登録する。
- saveボタンで今までaddしたコメントのスナップショットが登録され、restoreボタンでその状態を呼び戻すことができる。
- スナップショットは``DataSnapshotクラス``で保持し、DataSnapshot型オブジェクトは``Dataクラス``のメソッドで生成する。そのオブジェクトを``DataCaretakerクラス``でセッションとして保管する。
- スナップショットを呼び戻したい場合は、DataCaretakerクラスからセッションデータ（DataSnapshot型オブジェクト）へアクセスし、Dataクラスのメソッドで表示する。

## ファイル構造
```sh
MyMemento
  ├── Data.php
  ├── DataCaretaker.php
  ├── DataSnapshot.php
  └── my_client.php
```

## ソースコード
### Mementoクラス
#### DataSnapshot.php
```DataSnapshot.php
<?php
namespace DoYouPhp\PhpDesignPattern\Memento\MyMemento;


/**
 * Mementoクラスに相当する
 * Data型オブジェクトの内部状態を保管する
 */
class DataSnapshot
{
    private $comment;

    // Data型オブジェクトの内部状態を保管する
    protected function __construct($comment)
    {
        $this->comment = $comment;
    }

    // 保管しているコメントを返す
    protected function getComment()
    {
        return $this->comment;
    }
}
```

### Originatorクラス
#### Data.php
```Data.php
<?php
namespace DoYouPhp\PhpDesignPattern\Memento\MyMemento;


use DoYouPhp\PhpDesignPattern\Memento\MyMemento\DataSnapshot;

/**
 * Originatorクラスに相当する
 * このオブジェクトの内部状態がDataSnapshotクラスに保管される
 */
final class Data extends DataSnapshot
{
    private $comment;

    // コンストラクタ
    public function __construct()
    {
        $this->comment = array();
    }

    // MementoクラスであるDataSnapshot型インスタンスの生成
    // コメントは、DataSnapshot型インスタンスのプロパティとしてセットする
    public function takeSnapshot()
    {
        return new DataSnapshot($this->comment);
    }

    // DataSnapshot型インスタンスからコメントを復元する
    // コメントは、Data型インスタンスのプロパティとしてセットされる
    public function restoreSnapshot(DataSnapshot $snapshot)
    {
        $this->comment = $snapshot->getComment();
    }

    // コメントを追加する
    // コメントは、Data型インスタンスのプロパティとしてセットされる
    public function addComment($comment)
    {
        $this->comment[] = $comment;
    }

    // コメントを呼び出す
    public function getComment()
    {
        return $this->comment;
    }
}
```

### Caretakerクラス
#### DataCaretaker.php
```DataCaretaker.php
<?php
namespace DoYouPhp\PhpDesignPattern\Memento\MyMemento;


/**
 * Caretakerクラスに相当する
 * DataSnapshot型オブジェクトを管理する
 */
class DataCaretaker
{
    private $snapshot;

    // コンストラクタ
    // セッションがなければ、セッションを開始する
    public function __construct()
    {
        if (!isset($_SESSION)) {
            session_start();
        }
    }

    // $snapshotをセッションとして保管
    public function setSnapshot($snapshot)
    {
        $this->snapshot = $snapshot;
        $_SESSION['snapshot'] = $this->snapshot;
    }

    // セッション($snapshot)があれば返す
    public function getSnapshot()
    {
        return (isset($_SESSION['snapshot']) ? $_SESSION['snapshot'] : null);
    }
}
```

### Clientクラス
#### my_client.php
```my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Memento\MyMemento;

require dirname(__DIR__).'/../vendor/autoload.php';

use DoYouPhp\PhpDesignPattern\Memento\MyMemento\Data;
use DoYouPhp\PhpDesignPattern\Memento\MyMemento\DataCaretaker;


// DataCaretakerクラスのコンストラクタで、セッションが開始される
$caretaker = new DataCaretaker();
// セッションがあればセッション、なければData型インスタンスをセット
$data = isset($_SESSION['data']) ? $_SESSION['data'] : new Data();
// postメソッドでボタンが押されたか確認する
$mode = (isset($_POST['mode']) ? $_POST['mode'] : null);

// 押されたボタンにより処理を分岐する
switch ($mode) {
case 'add':
    // コメントをData型インスタンスに登録する
    $data->addComment((isset($_POST['comment']) ? $_POST['comment'] : ''));
    break;
case 'save':
    // コメントのスナップショットを取り、保存する
    // addされたコメントをDataSnapshot型インスタンスにセットしている
    // それを引数にして、DataCaretaker型インスタンスにセッションとしてスナップショットを保管する
    $caretaker->setSnapshot($data->takeSnapshot());
    echo 'データを保存しました！'.'<br>'."\n";
    break;
case 'restore':
    // 保存したスナップショットを取得しコメントを復元する
    // スナップショットはセッションとして保管されている
    $data->restoreSnapshot($caretaker->getSnapshot());
    echo 'データを復元しました！'.'<br>'."\n";
    break;
case 'clear':
    // Data型インスタンスを初期化する
    // この時点では、セッションはまだ生きている状態
    $data = new Data();
}

// 登録したコメントを取得し、表示する
// add, save, clearは表示される内容がそれぞれ異なる
echo '今までのコメント'.'<br>'."\n";
if (is_object($data)) {
    echo '<ul>';
    foreach ($data->getComment() as $comment) {
        echo '<li>'.$comment.'</li>';
    }
    echo '</ul>';
}

// 次のアクセスで使うデータをセッションに保存する
$_SESSION['data'] = $data;
?>

<!-- 入力フォーム -->
<form action="" method="post">
コメント：<input type="text" name="comment"><br>
<input type="submit" name="mode" value="add">
<input type="submit" name="mode" value="save">
<input type="submit" name="mode" value="restore">
<input type="submit" name="mode" value="clear">
</form>
```