はじめに
例えば、都道府県のデータをデータベースに格納していて、単純に検索するだけの処理がある場合、検索結果はほぼ普遍的なわけで、都度データベースにクエリを投げるのではなく、同じクエリであれば、結果をキャッシュしたほうが、より効率的です。
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
がコールされます。
/**
* 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
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
を返却します。
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年連続参加できました。
来年も懲りずに、参加しようと思います。
それでは、少し早いですが、よいお年を。