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;
|
namespace Doctrine\ORM;
|
||||||
|
|
||||||
use Doctrine\DBAL\Types\Type,
|
use Doctrine\DBAL\Types\Type,
|
||||||
|
Doctrine\DBAL\Cache\QueryCacheProfile,
|
||||||
Doctrine\ORM\Query\QueryException,
|
Doctrine\ORM\Query\QueryException,
|
||||||
Doctrine\DBAL\Cache\QueryCacheProfile;
|
Doctrine\ORM\Internal\Hydration\CacheHydrator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base contract for ORM queries. Base class for Query and NativeQuery.
|
* 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
|
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
|
||||||
* @link www.doctrine-project.org
|
* @link www.doctrine-project.org
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
* @version $Revision$
|
|
||||||
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
* @author Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||||
* @author Jonathan Wage <jonwage@gmail.com>
|
* @author Jonathan Wage <jonwage@gmail.com>
|
||||||
@ -101,6 +101,11 @@ abstract class AbstractQuery
|
|||||||
*/
|
*/
|
||||||
protected $_expireResultCache = false;
|
protected $_expireResultCache = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||||
|
*/
|
||||||
|
protected $_hydrationCacheProfile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
|
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
|
||||||
*
|
*
|
||||||
@ -299,6 +304,68 @@ abstract class AbstractQuery
|
|||||||
return $this;
|
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.
|
* 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);
|
$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();
|
$stmt = $this->_doExecute();
|
||||||
|
|
||||||
if (is_numeric($stmt)) {
|
if (is_numeric($stmt)) {
|
||||||
|
$setCacheEntry($stmt);
|
||||||
|
|
||||||
return $stmt;
|
return $stmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
|
$data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
|
||||||
$stmt, $this->_resultSetMapping, $this->_hints
|
$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;
|
$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.
|
* 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