【PHPデザインパターン】20_Proxy～具体的な実装を隠す身代わり

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

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

> [Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Proxy～具体的な実装を隠す身代わり](http://d.hatena.ne.jp/shimooka/20141218/1418888727)

# 概要
- 「proxy」は「代理」という意味。
- やりとりをするオブジェクトの間にクッション役を用意し、このクッションに色々な役割を持たせる。
- あるオブジェクトへのアクセスを制御するために、そのオブジェクトの代理、または入れ物を提供する。
- 代理オブジェクトにアクセスしているのか目的のオブジェクトにアクセスしているのかを、意識させない。

## 構成要素
### Subjectクラス
- RealSubjectクラスとProxyクラスが提供する共通のAPIを定義するクラス。

### RealSubjectクラス
- Subjectクラスのサブクラス。

### Proxyクラス
- Subjectクラスのサブクラス。
- 具体的な処理は、内部に保持したRealSubjectクラスのインスタンスに転送する。
- RealSubjectクラスに対するアクセス制御や、RealSubjectクラスのインスタンスの生成タイミングなどを調整する。

# 実演
## 処理の流れ
- ``ItemDaoクラス``のメソッドを、各RealSubjectクラス、Proxyクラスで実装する。
- ``DbItemDaoクラス``では、配列からデータを読み込んでいる。
- ``MockItemDaoクラス``では、ダミーデータを用意する。
- ItemDaoクラスを使用した場合と、Proxyクラスである``ItemDaoProxyクラス``を使用した場合の動きを確認する。

## ファイル構造
```sh
MyProxy
  ├── DbItemDao.php
  ├── Item.php
  ├── ItemDao.php
  ├── ItemDaoProxy.php
  ├── MockItemDao.php
  └── my_client.php
```

## ソースコード
### Subjectクラス
#### ItemDao.php
```ItemDao.php
<?php
namespace DoYouPhp\PhpDesignPattern\Proxy\MyProxy;

/**
 * Subjectクラスに相当する
 */
interface ItemDao
{
    public function findById($item_id);
}
```

### RealSubjectクラス
#### DbItemDao.php
```DbItemDao.php
<?php
namespace DoYouPhp\PhpDesignPattern\Proxy\MyProxy;

use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\ItemDao;
use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\Item;

/**
 * RealSubjectクラスに相当する
 */
class DbItemDao implements ItemDao
{
    public function findById($item_id)
    {
        // 配列形式のデータからItem型オブジェクトを生成する
        $item_array = array('0' => array(1, 'apple'), '1' => array(2, 'orange'), '2' => array(3, 'peach'));

        foreach ($item_array as $data) {
            list($id, $name) = $data;

            // IDが合致した場合、Item型オブジェクトを返す
            if ($item_id === $id) {
                $item = new Item($id, $name);

                return $item;
            }
        }
    }
}
```

#### MockItemDao.php
```MockItemDao.php
<?php
namespace DoYouPhp\PhpDesignPattern\Proxy\MyProxy;

use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\ItemDao;
use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\Item;

/**
 * RealSubjectクラスに相当する
 */
class MockItemDao implements ItemDao
{
    // 商品情報を直接指定し、Item型オブジェクトを生成する
    // IDは流動的、商品名は固定する
    public function findById($item_id)
    {
        $item = new Item($item_id, 'ダミー商品');

        return $item;
    }
}
```

### Proxyクラス
#### ItemDaoProxy.php
```ItemDaoProxy.php
<?php
namespace DoYouPhp\PhpDesignPattern\Proxy\MyProxy;

use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\ItemDao;

/**
 * Proxyクラスに相当する
 * ItemDaoクラスのメソッドを実装している
 */
class ItemDaoProxy
{
    // プロパティ
    private $dao;
    private $cache;

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

    // キャッシュを作成する
    public function findById($item_id)
    {
        // キャッシュに該当IDのItemDao型オブジェクトが存在した場合、データを返す
        if (array_key_exists($item_id, $this->cache)) {
            echo 'Proxyで保持しているキャッシュからデータを返します'.'<br>'."\n";

            return $this->cache[$item_id];
        }

        // キャッシュに該当IDのItemDao型オブジェクトが存在しない場合、IDをキーにした配列の要素としてセットする
        // その後、データを返す
        $this->cache[$item_id] = $this->dao->findById($item_id);

        echo 'Proxyでデータのキャッシュを作りました'.'<br>'."\n";
        return $this->cache[$item_id];
    }
}
```

### Modelクラス
#### Item.php
```Item.php
<?php
namespace DoYouPhp\PhpDesignPattern\Proxy\MyProxy;

/**
 * Modelクラスに相当する
 */
class Item
{
    // プロパティ
    private $id;
    private $name;

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

    // 各プロパティを取得する
    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }
}
```

### Client
#### my_client.php
```my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\Proxy\MyProxy;

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

use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\ItemDao;
use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\DbItemDao;
use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\MockItemDao;
use DoYouPhp\PhpDesignPattern\Proxy\MyProxy\ItemDaoProxy;

function execute(ItemDao $dao, $use_proxy = false)
{
    // Proxyクラスを使用した場合、ItemDaoProxy型オブジェクトからメソッドが呼ばれる
    if ($use_proxy === true) {
        $dao = new ItemDaoProxy($dao);
    }

    // データを抽出する
    for ($item_id = 1; $item_id <= 3; $item_id++) {
        $item = $dao->findById($item_id);

        echo 'ID：'.$item_id.'、商品名：'.$item->getName().'<br>'."\n";
    }

    // キャッシュの動作を確認するため、再度データを取得する
    $item_id = 1;
    $item = $dao->findById($item_id);
    echo 'ID：'.$item_id.'、商品名：'.$item->getName().'<br>'."\n";
}

echo '---DbItemDao＋Proxyなし---'.'<br>'."\n";
execute(new DbItemDao());

echo '---MockItemDao＋Proxyなし---'.'<br>'."\n";
execute(new MockItemDao());

echo '---DbItemDao＋Proxyあり---'.'<br>'."\n";
execute(new DbItemDao(), true);

echo '---MockItemDao＋Proxyあり---'.'<br>'."\n";
execute(new MockItemDao(), true);
```