diff --git a/UPGRADE_TO_2_2 b/UPGRADE_TO_2_2
index 220fb39f9..757413029 100644
--- a/UPGRADE_TO_2_2
+++ b/UPGRADE_TO_2_2
@@ -1,3 +1,17 @@
+# ResultCache implementation rewritten
+
+The result cache is completly rewritten and now works on the database result level, not inside the ORM AbstractQuery
+anymore. This means that for result cached queries the hydration will now always be performed again, regardless of
+the hydration mode. Affected areas are:
+
+1. Fixes the problem that entities coming from the result cache were not registered in the UnitOfWork
+ leading to problems during EntityManager#flush. Calls to EntityManager#merge are not necessary anymore.
+2. Affects the array hydrator which now includes the overhead of hydration compared to caching the final result.
+
+The API is backwards compatible however most of the getter methods on the `AbstractQuery` object are now
+deprecated in favor of calling AbstractQuery#getQueryCacheProfile(). This method returns a `Doctrine\DBAL\Cache\QueryCacheProfile`
+instance with access to result cache driver, lifetime and cache key.
+
# EntityManager#getPartialReference() creates read-only entity
Entities returned from EntityManager#getPartialReference() are now marked as read-only if they
diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php
index 5d71cf0aa..0f28e71db 100644
--- a/lib/Doctrine/ORM/AbstractQuery.php
+++ b/lib/Doctrine/ORM/AbstractQuery.php
@@ -20,7 +20,8 @@
namespace Doctrine\ORM;
use Doctrine\DBAL\Types\Type,
- Doctrine\ORM\Query\QueryException;
+ Doctrine\ORM\Query\QueryException,
+ Doctrine\DBAL\Cache\QueryCacheProfile;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
@@ -91,34 +92,15 @@ abstract class AbstractQuery
protected $_hydrationMode = self::HYDRATE_OBJECT;
/**
- * The locally set cache driver used for caching result sets of this query.
- *
- * @var CacheDriver
+ * @param \Doctrine\DBAL\Cache\QueryCacheProfile
*/
- protected $_resultCacheDriver;
-
- /**
- * Boolean flag for whether or not to cache the results of this query.
- *
- * @var boolean
- */
- protected $_useResultCache;
-
- /**
- * @var string The id to store the result cache entry under.
- */
- protected $_resultCacheId;
+ protected $_queryCacheProfile;
/**
* @var boolean Boolean value that indicates whether or not expire the result cache.
*/
protected $_expireResultCache = false;
- /**
- * @var int Result Cache lifetime.
- */
- protected $_resultCacheTTL;
-
/**
* Initializes a new instance of a class derived from AbstractQuery.
*
@@ -260,7 +242,7 @@ abstract class AbstractQuery
}
/**
- * Defines a cache driver to be used for caching result sets.
+ * Defines a cache driver to be used for caching result sets and implictly enables caching.
*
* @param Doctrine\Common\Cache\Cache $driver Cache driver
* @return Doctrine\ORM\AbstractQuery
@@ -270,9 +252,10 @@ abstract class AbstractQuery
if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
throw ORMException::invalidResultCacheDriver();
}
- $this->_resultCacheDriver = $resultCacheDriver;
- if ($resultCacheDriver) {
- $this->_useResultCache = true;
+ if ($this->_queryCacheProfile) {
+ $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver);
+ } else {
+ $this->_queryCacheProfile = new QueryCacheProfile(0, null, $resultCacheDriver);
}
return $this;
}
@@ -280,12 +263,13 @@ abstract class AbstractQuery
/**
* Returns the cache driver used for caching result sets.
*
+ * @deprecated
* @return Doctrine\Common\Cache\Cache Cache driver
*/
public function getResultCacheDriver()
{
- if ($this->_resultCacheDriver) {
- return $this->_resultCacheDriver;
+ if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
+ return $this->_queryCacheProfile->getResultCacheDriver();
} else {
return $this->_em->getConfiguration()->getResultCacheImpl();
}
@@ -296,18 +280,17 @@ abstract class AbstractQuery
* how long and which ID to use for the cache entry.
*
* @param boolean $bool
- * @param integer $timeToLive
+ * @param integer $lifetime
* @param string $resultCacheId
* @return Doctrine\ORM\AbstractQuery This query instance.
*/
- public function useResultCache($bool, $timeToLive = null, $resultCacheId = null)
+ public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
{
- $this->_useResultCache = $bool;
- if ($timeToLive) {
- $this->setResultCacheLifetime($timeToLive);
- }
- if ($resultCacheId) {
- $this->_resultCacheId = $resultCacheId;
+ if ($bool) {
+ $this->setResultCacheLifetime($lifetime);
+ $this->setResultCacheId($resultCacheId);
+ } else {
+ $this->_queryCacheProfile = null;
}
return $this;
}
@@ -315,27 +298,33 @@ abstract class AbstractQuery
/**
* Defines how long the result cache will be active before expire.
*
- * @param integer $timeToLive How long the cache entry is valid.
+ * @param integer $lifetime How long the cache entry is valid.
* @return Doctrine\ORM\AbstractQuery This query instance.
*/
- public function setResultCacheLifetime($timeToLive)
+ public function setResultCacheLifetime($lifetime)
{
- if ($timeToLive !== null) {
- $timeToLive = (int) $timeToLive;
+ if ($lifetime === null) {
+ $lifetime = 0;
+ } else {
+ $lifetime = (int)$lifetime;
+ }
+ if ($this->_queryCacheProfile) {
+ $this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime);
+ } else {
+ $this->_queryCacheProfile = new QueryCacheProfile($lifetime);
}
-
- $this->_resultCacheTTL = $timeToLive;
return $this;
}
/**
* Retrieves the lifetime of resultset cache.
*
+ * @deprecated
* @return integer
*/
public function getResultCacheLifetime()
{
- return $this->_resultCacheTTL;
+ return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
}
/**
@@ -360,6 +349,14 @@ abstract class AbstractQuery
return $this->_expireResultCache;
}
+ /**
+ * @return QueryCacheProfile
+ */
+ public function getQueryCacheProfile()
+ {
+ return $this->_queryCacheProfile;
+ }
+
/**
* Change the default fetch mode of an association for this query.
*
@@ -584,28 +581,6 @@ abstract class AbstractQuery
$this->setParameters($params);
}
- // Check result cache
- if ($this->_useResultCache && $cacheDriver = $this->getResultCacheDriver()) {
- list($key, $hash) = $this->getResultCacheId();
- $cached = $this->_expireResultCache ? false : $cacheDriver->fetch($hash);
-
- if ($cached === false || !isset($cached[$key])) {
- // Cache miss.
- $stmt = $this->_doExecute();
-
- $result = $this->_em->getHydrator($this->_hydrationMode)->hydrateAll(
- $stmt, $this->_resultSetMapping, $this->_hints
- );
-
- $cacheDriver->save($hash, array($key => $result), $this->_resultCacheTTL);
-
- return $result;
- } else {
- // Cache hit.
- return $cached[$key];
- }
- }
-
$stmt = $this->_doExecute();
if (is_numeric($stmt)) {
@@ -627,43 +602,23 @@ abstract class AbstractQuery
*/
public function setResultCacheId($id)
{
- $this->_resultCacheId = $id;
+ if ($this->_queryCacheProfile) {
+ $this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id);
+ } else {
+ $this->_queryCacheProfile = new QueryCacheProfile(0, $id);
+ }
return $this;
}
/**
- * 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.
+ * Get the result cache id to use to store the result set cache entry if set.
*
- * @return array ($key, $hash)
+ * @deprecated
+ * @return string
*/
- protected function getResultCacheId()
+ public function getResultCacheId()
{
- if ($this->_resultCacheId) {
- return array($this->_resultCacheId, $this->_resultCacheId);
- } else {
- $params = $this->_params;
- 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;
- } else {
- $params[$key] = $value;
- }
- }
-
- $sql = $this->getSql();
- ksort($this->_hints);
- $key = implode(";", (array)$sql) . var_export($params, true) .
- var_export($this->_hints, true)."&hydrationMode=".$this->_hydrationMode;
- return array($key, md5($key));
- }
+ return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
}
/**
diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php
index ee6496087..eaa4df562 100644
--- a/lib/Doctrine/ORM/Configuration.php
+++ b/lib/Doctrine/ORM/Configuration.php
@@ -209,27 +209,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->_attributes['metadataDriverImpl'] : null;
}
- /**
- * Gets the cache driver implementation that is used for query result caching.
- *
- * @return \Doctrine\Common\Cache\Cache
- */
- public function getResultCacheImpl()
- {
- return isset($this->_attributes['resultCacheImpl']) ?
- $this->_attributes['resultCacheImpl'] : null;
- }
-
- /**
- * Sets the cache driver implementation that is used for query result caching.
- *
- * @param \Doctrine\Common\Cache\Cache $cacheImpl
- */
- public function setResultCacheImpl(Cache $cacheImpl)
- {
- $this->_attributes['resultCacheImpl'] = $cacheImpl;
- }
-
/**
* Gets the cache driver implementation that is used for the query cache (SQL cache).
*
diff --git a/lib/Doctrine/ORM/NativeQuery.php b/lib/Doctrine/ORM/NativeQuery.php
index 2c0a5ab28..dea223fa3 100644
--- a/lib/Doctrine/ORM/NativeQuery.php
+++ b/lib/Doctrine/ORM/NativeQuery.php
@@ -57,17 +57,17 @@ final class NativeQuery extends AbstractQuery
*/
protected function _doExecute()
{
- $stmt = $this->_em->getConnection()->prepare($this->_sql);
$params = $this->_params;
- foreach ($params as $key => $value) {
- if (isset($this->_paramTypes[$key])) {
- $stmt->bindValue($key, $value, $this->_paramTypes[$key]);
- } else {
- $stmt->bindValue($key, $value);
+ $types = $this->_paramTypes;
+ if ($params) {
+ if (is_int(key($params))) {
+ ksort($params);
+ ksort($types);
+ $params = array_values($params);
+ $types = array_values($types);
}
}
- $stmt->execute();
- return $stmt;
+ return $this->_em->getConnection()->executeQuery($this->_sql, $params, $types, $this->_queryCacheProfile);
}
}
\ No newline at end of file
diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php
index e0bbcec5a..293c64391 100644
--- a/lib/Doctrine/ORM/Query.php
+++ b/lib/Doctrine/ORM/Query.php
@@ -232,6 +232,9 @@ final class Query extends AbstractQuery
protected function _doExecute()
{
$executor = $this->_parse()->getSqlExecutor();
+ if ($this->_queryCacheProfile) {
+ $executor->setQueryCacheProfile($this->_queryCacheProfile);
+ }
// Prepare parameters
$paramMappings = $this->_parserResult->getParameterMappings();
diff --git a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php
index 7879b0ff2..f44e383b9 100644
--- a/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php
+++ b/lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php
@@ -1,7 +1,5 @@
_sqlStatements;
}
+ public function setQueryCacheProfile(QueryCacheProfile $qcp)
+ {
+ $this->queryCacheProfile = $qcp;
+ }
+
/**
* Executes all sql statements.
*
* @param Doctrine\DBAL\Connection $conn The database connection that is used to execute the queries.
* @param array $params The parameters.
+ * @param array $types The parameter types.
* @return Doctrine\DBAL\Driver\Statement
*/
- abstract public function execute(Connection $conn, array $params, array $types);
+ abstract public function execute(Connection $conn, array $params, array $types);
}
\ No newline at end of file
diff --git a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php
index a6c22cecd..5b07d4d02 100644
--- a/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php
+++ b/lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php
@@ -1,7 +1,5 @@
- * @version $Revision$
* @link www.doctrine-project.org
* @since 2.0
*/
@@ -41,8 +38,11 @@ class SingleSelectExecutor extends AbstractSqlExecutor
$this->_sqlStatements = $sqlWalker->walkSelectStatement($AST);
}
+ /**
+ * {@inheritDoc}
+ */
public function execute(Connection $conn, array $params, array $types)
{
- return $conn->executeQuery($this->_sqlStatements, $params, $types);
+ return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile);
}
}
diff --git a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php
index 94db13b05..facccb715 100644
--- a/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php
+++ b/lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php
@@ -1,7 +1,5 @@
- * @version $Revision$
* @link www.doctrine-project.org
* @since 2.0
* @todo This is exactly the same as SingleSelectExecutor. Unify in SingleStatementExecutor.
@@ -45,7 +42,10 @@ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor
$this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST);
}
}
-
+
+ /**
+ * {@inheritDoc}
+ */
public function execute(Connection $conn, array $params, array $types)
{
return $conn->executeUpdate($this->_sqlStatements, $params, $types);
diff --git a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php
index 235b4c91c..3dcae4fab 100644
--- a/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php
+++ b/tests/Doctrine/Tests/ORM/Functional/ResultCacheTest.php
@@ -90,10 +90,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testUseResultCache()
{
$cache = new \Doctrine\Common\Cache\ArrayCache();
- $this->_em->getConfiguration()->setResultCacheImpl($cache);
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
$query->useResultCache(true);
+ $query->setResultCacheDriver($cache);
$query->setResultCacheId('testing_result_cache_id');
$users = $query->getResult();
@@ -108,11 +108,11 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
public function testUseResultCacheParams()
{
$cache = new \Doctrine\Common\Cache\ArrayCache();
- $this->_em->getConfiguration()->setResultCacheImpl($cache);
$sqlCount = count($this->_sqlLoggerStack->queries);
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux WHERE ux.id = ?1');
$query->setParameter(1, 1);
+ $query->setResultCacheDriver($cache);
$query->useResultCache(true);
$query->getResult();
@@ -149,10 +149,10 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
}
/**
- * @param $query
+ * @param string $query
* @depends testNativeQueryResultCaching
*/
- public function testResultCacheDependsOnQueryHints($query)
+ public function testResultCacheNotDependsOnQueryHints($query)
{
$cache = $query->getResultCacheDriver();
$cacheCount = $this->getCacheSize($cache);
@@ -160,7 +160,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$query->setHint('foo', 'bar');
$query->getResult();
- $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
+ $this->assertEquals($cacheCount, $this->getCacheSize($cache));
}
/**
@@ -182,7 +182,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
* @param $query
* @depends testNativeQueryResultCaching
*/
- public function testResultCacheDependsOnHydrationMode($query)
+ public function testResultCacheNotDependsOnHydrationMode($query)
{
$cache = $query->getResultCacheDriver();
$cacheCount = $this->getCacheSize($cache);
@@ -190,7 +190,7 @@ class ResultCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertNotEquals(\Doctrine\ORM\Query::HYDRATE_ARRAY, $query->getHydrationMode());
$query->getArrayResult();
- $this->assertEquals($cacheCount + 1, $this->getCacheSize($cache));
+ $this->assertEquals($cacheCount, $this->getCacheSize($cache));
}
/**