diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 4ada9160e..be22ad895 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -21,7 +21,8 @@ namespace Doctrine\ORM; use Doctrine\DBAL\Types\Type, Doctrine\ORM\Query\QueryException, - Doctrine\DBAL\Cache\QueryCacheProfile; + Doctrine\DBAL\Cache\QueryCacheProfile, + Doctrine\ORM\Internal\Hydration\CacheHydrator; /** * Base contract for ORM queries. Base class for Query and NativeQuery. @@ -101,6 +102,11 @@ abstract class AbstractQuery */ protected $_expireResultCache = false; + /** + * @param \Doctrine\DBAL\Cache\QueryCacheProfile + */ + protected $_hydrationCacheProfile; + /** * Initializes a new instance of a class derived from AbstractQuery. * @@ -299,6 +305,26 @@ abstract class AbstractQuery return $this; } + /** + * Set a cache profile for hydration caching. + * + * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile + * @return \Doctrine\ORM\AbstractQuery + */ + public function setHydrationCacheProfile(QueryCacheProfile $profile = null) + { + $this->_hydrationCacheProfile = $profile; + return $this; + } + + /** + * @return \Doctrine\DBAL\Cache\QueryCacheProfile + */ + public function getHydrationCacheProfile() + { + return $this->_hydrationCacheProfile; + } + /** * Defines a cache driver to be used for caching result sets and implictly enables caching. * @@ -644,15 +670,72 @@ abstract class AbstractQuery $this->setParameters($params); } + $saveCache = function() {}; + if ($this->_hydrationCacheProfile !== null) { + list($cacheKey, $realCacheKey) = $this->getHydrationCacheId(); + + $qcp = $this->getHydrationCacheProfile(); + $cache = $qcp->getResultCacheDriver(); + + $result = $cache->fetch($cacheKey); + if (isset($result[$realCacheKey])) { + return $result[$realCacheKey]; + } + + if ( ! $result) { + $result = array(); + } + $saveCache = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $qcp) { + $result[$realCacheKey] = $data; + $cache->save($cacheKey, $result, $qcp->getLifetime()); + }; + } + $stmt = $this->_doExecute(); if (is_numeric($stmt)) { + $saveCache($stmt); return $stmt; } - return $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( + $data = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll( $stmt, $this->_resultSetMapping, $this->_hints ); + + $saveCache($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) { + if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) { + if ($this->_em->getUnitOfWork()->getEntityState($value) == UnitOfWork::STATE_MANAGED) { + $idValues = $this->_em->getUnitOfWork()->getEntityIdentifier($value); + } else { + $class = $this->_em->getClassMetadata(get_class($value)); + $idValues = $class->getIdentifierValues($value); + } + $params[$key] = $idValues; + } + } + + $sql = $this->getSQL(); + $hints = $this->getHints(); + $hints['hydrationMode'] = $this->getHydrationMode(); + ksort($hints); + + $qcp = $this->getHydrationCacheProfile(); + return $qcp->generateCacheKeys($sql, $params, $hints); } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php new file mode 100644 index 000000000..9f3acf8c6 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/HydrationCacheTest.php @@ -0,0 +1,72 @@ +useModelSet('cms'); + parent::setUp(); + } + + public function testHydrationCache() + { + $cache = new ArrayCache(); + + $user = new CmsUser; + $user->name = "Benjamin"; + $user->username = "beberlei"; + $user->status = 'active'; + + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + + $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(); + $dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u"; + $users = $this->_em->createQuery($dql) + ->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache)) + ->getResult(); + + $this->assertEquals($c, $this->getCurrentQueryCount(), "Should not execute query. Its cached!"); + + $dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u"; + $users = $this->_em->createQuery($dql) + ->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache)) + ->getArrayResult(); + + $this->assertEquals($c + 1, $this->getCurrentQueryCount(), "Hydration is part of cache key."); + + $dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u"; + $users = $this->_em->createQuery($dql) + ->setHydrationCacheProfile(new QueryCacheProfile(null, null, $cache)) + ->getArrayResult(); + + $this->assertEquals($c + 1, $this->getCurrentQueryCount(), "Hydration now cached"); + + $dql = "SELECT u FROM Doctrine\Tests\Models\Cms\CmsUser u"; + $users = $this->_em->createQuery($dql) + ->setHydrationCacheProfile(new QueryCacheProfile('cachekey', null, $cache)) + ->getArrayResult(); + + $data = $this->readAttribute($cache, 'data'); + var_dump(array_keys($data)); + + $this->assertTrue($cache->contains('cachekey'), 'Explicit cache key'); + } +} +