forked from doctrine/orm
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request doctrine#329 from doctrine/DDC-1766
[DDC-1766] Initial implementation of hydration cache.
- Loading branch information
Showing
3 changed files
with
231 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,16 +20,16 @@ | |
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. | ||
* | ||
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL | ||
* @link www.doctrine-project.org | ||
* @since 2.0 | ||
* @version $Revision$ | ||
* @author Benjamin Eberlei <[email protected]> | ||
* @author Guilherme Blanco <[email protected]> | ||
* @author Jonathan Wage <[email protected]> | ||
|
@@ -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 @@ public function setResultSetMapping(Query\ResultSetMapping $rsm) | |
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 @@ public function execute($params = array(), $hydrationMode = null) | |
$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); | ||
} | ||
|
||
/** | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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!"); | ||
} | ||
} | ||
|