Doctrine DBALで取得したデータをキャッシュする

## はじめに
例えば、都道府県のデータをデータベースに格納していて、単純に検索するだけの処理がある場合、検索結果はほぼ普遍的なわけで、都度データベースにクエリを投げるのではなく、同じクエリであれば、結果をキャッシュしたほうが、より効率的です。

Doctrine DBALの下記ページを参考に試しに、キャッシュしてみます。
http://doctrine-orm.readthedocs.org/projects/doctrine-dbal/en/latest/reference/caching.html

## QueryCacheProfileの作成
キャッシュをどのように保存するか、どの程度キャッシュするかなど、キャッシュに関する設定を行います。

```
use Doctrine\Common\Cache\FilesystemCache;
use Doctrine\DBAL\Cache\QueryCacheProfile;

$cache = new FilesystemCache('/path/to/save');
$cacheProfile = new QueryCacheProfile(0, 'cache-key', $cache);
```

### いろいろなcache
doctrine/cache には、いろいろなcacheクラスがあります。
https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache

- FilesystemCache
- MongoDBCache
- MemcachedCache
- PhpFileCache
- RedisCache


## executeCacheQuery
`executeQuery`にて、`QueryCacheProfile`を指定すると、`executeCacheQuery`がコールされます。

https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Connection.php

```php
/**
 * Executes a caching query.
 *
 * @param string                                 $query  The SQL query to execute.
 * @param array                                  $params The parameters to bind to the query, if any.
 * @param array                                  $types  The types the previous parameters are in.
 * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp    The query cache profile.
 *
 * @return \Doctrine\DBAL\Driver\ResultStatement
 *
 * @throws \Doctrine\DBAL\Cache\CacheException
 */
public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp)
{
    $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl();
    if ( ! $resultCache) {
        throw CacheException::noResultDriverConfigured();
    }

    list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types);

    // fetch the row pointers entry
    if ($data = $resultCache->fetch($cacheKey)) {
        // is the real key part of this row pointers map or is the cache only pointing to other cache keys?
        if (isset($data[$realKey])) {
            $stmt = new ArrayStatement($data[$realKey]);
        } elseif (array_key_exists($realKey, $data)) {
            $stmt = new ArrayStatement(array());
        }
    }

    if (!isset($stmt)) {
        $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime());
    }

    $stmt->setFetchMode($this->defaultFetchMode);

    return $stmt;
}

```

## closeCursor

キャッシュがない場合、`ResultCacheStatement::closeCursor`をコールすることで、結果セットをキャッシュすることができます。
https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php

```php
public function closeCursor()
{
    $this->statement->closeCursor();
    if ($this->emptied && $this->data !== null) {
        $data = $this->resultCache->fetch($this->cacheKey);
        if ( ! $data) {
            $data = array();
        }
        $data[$this->realKey] = $this->data;

        $this->resultCache->save($this->cacheKey, $data, $this->lifetime);
        unset($this->data);
    }
}
```


## 実行結果
キャッシュがない場合、`executeQuery`は、データベースからデータを取得し、`ResultCacheStatement`を返却します。
キャッシュがある場合、`executeQuery`は、キャッシュからデータを取得し、`ArrayStatement`を返却します。

```php
use Doctrine\DBAL\Cache\QueryCacheProfile;

public function findBySql($sql, $params = [], QueryCacheProfile $cacheProfile) {
    $statement = $this->connection->executeQuery($sql, $params, [], $cacheProfile);
    /* キャッシュがない場合 */
    /* @var $statement \Doctrine\DBAL\Cache\ResultCacheStatement */
    /* キャッシュがない場合 */
    /* @var $statement \Doctrine\DBAL\Cache\ArrayStatement */
    $result = $statement->fetchAll();
    $statement->closeCursor();
	return $result;
}
```

## おわりに
`Symfony Advent Calendar`なのに、Doctrine/DBALの小ネタで大変恐縮ですが、実戦ですぐ使えそうなネタだったので、調査もかねて書いてみました。
これで、2年連続参加できました。
来年も懲りずに、参加しようと思います。
それでは、少し早いですが、よいお年を。