Merge pull request #329 from doctrine/DDC-1766
[DDC-1766] Initial implementation of hydration cache.
This commit is contained in:
commit
a5c13a5ef1
@ -20,8 +20,9 @@
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\DBAL\Types\Type,
|
||||
Doctrine\DBAL\Cache\QueryCacheProfile,
|
||||
Doctrine\ORM\Query\QueryException,
|
||||
Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
Doctrine\ORM\Internal\Hydration\CacheHydrator;
|
||||
|
||||
/**
|
||||
* Base contract for ORM queries. Base class for Query and NativeQuery.
|
||||
@ -29,7 +30,6 @@ use Doctrine\DBAL\Types\Type,
|
||||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||
* @link www.doctrine-project.org
|
||||
* @since 2.0
|
||||
* @version $Revision$
|
||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Jonathan Wage <jonwage@gmail.com>
|
||||
@ -101,6 +101,11 @@ abstract class AbstractQuery
|
||||
*/
|
||||
protected $_expireResultCache = false;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
*/
|
||||
protected $_hydrationCacheProfile;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
|
||||
*
|
||||
@ -299,6 +304,68 @@ abstract class AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cache profile for hydration caching.
|
||||
*
|
||||
* If no result cache driver is set in the QueryCacheProfile, the default
|
||||
* result cache driver is used from the configuration.
|
||||
*
|
||||
* Important: Hydration caching does NOT register entities in the
|
||||
* UnitOfWork when retrieved from the cache. Never use result cached
|
||||
* entities for requests that also flush the EntityManager. If you want
|
||||
* some form of caching with UnitOfWork registration you should use
|
||||
* {@see AbstractQuery::setResultCacheProfile()}.
|
||||
*
|
||||
* @example
|
||||
* $lifetime = 100;
|
||||
* $resultKey = "abc";
|
||||
* $query->setHydrationCacheProfile(new QueryCacheProfile());
|
||||
* $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
|
||||
*
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ( ! $profile->getResultCacheDriver()) {
|
||||
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
|
||||
$profile = $profile->setResultCacheDriver($resultCacheDriver);
|
||||
}
|
||||
|
||||
$this->_hydrationCacheProfile = $profile;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
*/
|
||||
public function getHydrationCacheProfile()
|
||||
{
|
||||
return $this->_hydrationCacheProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cache profile for the result cache.
|
||||
*
|
||||
* If no result cache driver is set in the QueryCacheProfile, the default
|
||||
* result cache driver is used from the configuration.
|
||||
*
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
|
||||
* @return \Doctrine\ORM\AbstractQuery
|
||||
*/
|
||||
public function setResultCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ( ! $profile->getResultCacheDriver()) {
|
||||
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
|
||||
$profile = $profile->setResultCacheDriver($resultCacheDriver);
|
||||
}
|
||||
|
||||
$this->_queryCacheProfile = $profile;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a cache driver to be used for caching result sets and implictly enables caching.
|
||||
*
|
||||
@ -644,15 +711,68 @@ abstract class AbstractQuery
|
||||
$this->setParameters($params);
|
||||
}
|
||||
|
||||
$setCacheEntry = function() {};
|
||||
|
||||
if ($this->_hydrationCacheProfile !== null) {
|
||||
list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
|
||||
|
||||
$queryCacheProfile = $this->getHydrationCacheProfile();
|
||||
$cache = $queryCacheProfile->getResultCacheDriver();
|
||||
$result = $cache->fetch($cacheKey);
|
||||
|
||||
if (isset($result[$realCacheKey])) {
|
||||
return $result[$realCacheKey];
|
||||
}
|
||||
|
||||
if ( ! $result) {
|
||||
$result = array();
|
||||
}
|
||||
|
||||
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
|
||||
$result[$realCacheKey] = $data;
|
||||
$cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
|
||||
};
|
||||
}
|
||||
|
||||
$stmt = $this->_doExecute();
|
||||
|
||||
if (is_numeric($stmt)) {
|
||||
$setCacheEntry($stmt);
|
||||
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
|
||||
$data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
|
||||
$stmt, $this->_resultSetMapping, $this->_hints
|
||||
);
|
||||
|
||||
$setCacheEntry($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result cache id to use to store the result set cache entry.
|
||||
* Will return the configured id if it exists otherwise a hash will be
|
||||
* automatically generated for you.
|
||||
*
|
||||
* @return array ($key, $hash)
|
||||
*/
|
||||
protected function getHydrationCacheId()
|
||||
{
|
||||
$params = $this->getParameters();
|
||||
|
||||
foreach ($params AS $key => $value) {
|
||||
$params[$key] = $this->processParameterValue($value);
|
||||
}
|
||||
|
||||
$sql = $this->getSQL();
|
||||
$queryCacheProfile = $this->getHydrationCacheProfile();
|
||||
$hints = $this->getHints();
|
||||
$hints['hydrationMode'] = $this->getHydrationMode();
|
||||
ksort($hints);
|
||||
|
||||
return $queryCacheProfile->generateCacheKeys($sql, $params, $hints);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -246,6 +246,28 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
$this->_attributes['queryCacheImpl'] = $cacheImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for the hydration cache (SQL cache).
|
||||
*
|
||||
* @return \Doctrine\Common\Cache\Cache
|
||||
*/
|
||||
public function getHydrationCacheImpl()
|
||||
{
|
||||
return isset($this->_attributes['hydrationCacheImpl'])
|
||||
? $this->_attributes['hydrationCacheImpl']
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cache driver implementation that is used for the hydration cache (SQL cache).
|
||||
*
|
||||
* @param \Doctrine\Common\Cache\Cache $cacheImpl
|
||||
*/
|
||||
public function setHydrationCacheImpl(Cache $cacheImpl)
|
||||
{
|
||||
$this->_attributes['hydrationCacheImpl'] = $cacheImpl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache driver implementation that is used for metadata caching.
|
||||
*
|
||||
|
86
tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php
Normal file
86
tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Doctrine\Tests\Models\Cms\CmsUser;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
|
||||
/**
|
||||
* @group DDC-1766
|
||||
*/
|
||||
class HydrationCacheTest extends OrmFunctionalTestCase
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
$this->useModelSet('cms');
|
||||
parent::setUp();
|
||||
|
||||
$user = new CmsUser;
|
||||
$user->name = "Benjamin";
|
||||
$user->username = "beberlei";
|
||||
$user->status = 'active';
|
||||
|
||||
$this->_em->persist($user);
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
}
|
||||
|
||||
public function testHydrationCache()
|
||||
{
|
||||
$cache = new ArrayCache();
|
||||
$dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u";
|
||||
|
||||
$users = $this->_em->createQuery($dql)
|
||||
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
|
||||
->getResult();
|
||||
|
||||
$c = $this->getCurrentQueryCount();
|
||||
$users = $this->_em->createQuery($dql)
|
||||
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
|
||||
->getResult();
|
||||
|
||||
$this->assertEquals($c, $this->getCurrentQueryCount(), "Should not execute query. Its cached!");
|
||||
|
||||
$users = $this->_em->createQuery($dql)
|
||||
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
|
||||
->getArrayResult();
|
||||
|
||||
$this->assertEquals($c + 1, $this->getCurrentQueryCount(), "Hydration is part of cache key.");
|
||||
|
||||
$users = $this->_em->createQuery($dql)
|
||||
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache))
|
||||
->getArrayResult();
|
||||
|
||||
$this->assertEquals($c + 1, $this->getCurrentQueryCount(), "Hydration now cached");
|
||||
|
||||
$users = $this->_em->createQuery($dql)
|
||||
->setHydrationCacheProfile(new QueryCacheProfile(null, 'cachekey', $cache))
|
||||
->getArrayResult();
|
||||
|
||||
$this->assertTrue($cache->contains('cachekey'), 'Explicit cache key');
|
||||
|
||||
$users = $this->_em->createQuery($dql)
|
||||
->setHydrationCacheProfile(new QueryCacheProfile(null, 'cachekey', $cache))
|
||||
->getArrayResult();
|
||||
$this->assertEquals($c + 2, $this->getCurrentQueryCount(), "Hydration now cached");
|
||||
}
|
||||
|
||||
public function testHydrationParametersSerialization()
|
||||
{
|
||||
$cache = new ArrayCache();
|
||||
$user = new CmsUser();
|
||||
$user->id = 1;
|
||||
|
||||
$dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u WHERE u.id = ?1";
|
||||
$query = $this->_em->createQuery($dql)
|
||||
->setParameter(1, $user)
|
||||
->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache));
|
||||
|
||||
$query->getResult();
|
||||
$c = $this->getCurrentQueryCount();
|
||||
$query->getResult();
|
||||
$this->assertEquals($c, $this->getCurrentQueryCount(), "Should not execute query. Its cached!");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user