Skip to content

Commit

Permalink
Merge branch 'DDC-1766' into 2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
beberlei committed Apr 7, 2012
2 parents aa82a75 + 13db276 commit 64c222e
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 3 deletions.
126 changes: 123 additions & 3 deletions lib/Doctrine/ORM/AbstractQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <kontakt@beberlei.de>
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
Expand Down Expand Up @@ -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>.
*
Expand Down Expand Up @@ -295,6 +300,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.
*
Expand Down Expand Up @@ -640,15 +707,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);
}

/**
Expand Down
22 changes: 22 additions & 0 deletions lib/Doctrine/ORM/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,28 @@ public function setQueryCacheImpl(Cache $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.
*
Expand Down
86 changes: 86 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php
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!");
}
}

0 comments on commit 64c222e

Please sign in to comment.