1
0
mirror of synced 2024-12-05 03:06:05 +03:00

Fix persister query cache invalidation

This commit is contained in:
Fabio B. Silva 2013-10-03 23:02:42 -04:00 committed by fabios
parent 1bfa8f0fc3
commit 1438a59c00
23 changed files with 909 additions and 50 deletions

View File

@ -30,7 +30,9 @@ use Doctrine\ORM\EntityManagerInterface;
*/
interface Cache
{
const DEFAULT_QUERY_REGION_NAME = 'query.cache.region';
const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
/**
* May read items from the cache, but will not add items.

View File

@ -92,4 +92,11 @@ interface CacheFactory
* @return \Doctrine\ORM\Cache\Region The cache region.
*/
public function getRegion(array $cache);
/**
* Build timestamp cache region
*
* @return \Doctrine\ORM\Cache\TimestampRegion The timestamp region.
*/
public function getTimestampRegion();
}

View File

@ -22,13 +22,15 @@ namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\Cache\RegionsConfiguration;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache\Region\FileLockRegion;
use Doctrine\ORM\Cache\Region\UpdateTimestampCache;
use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\ORM\Persisters\EntityPersister;
use Doctrine\ORM\Persisters\CollectionPersister;
use Doctrine\ORM\Cache\Persister\ReadOnlyCachedEntityPersister;
@ -54,6 +56,11 @@ class DefaultCacheFactory implements CacheFactory
*/
private $regionsConfig;
/**
* @var \Doctrine\ORM\Cache\TimestampRegion
*/
private $timestampRegion;
/**
* @var array
*/
@ -98,6 +105,15 @@ class DefaultCacheFactory implements CacheFactory
$this->regions[$region->getName()] = $region;
}
/**
* @param \Doctrine\ORM\Cache\TimestampRegion $region
*/
public function setTimestampRegion(TimestampRegion $region)
{
$this->timestampRegion = $region;
}
/**
* {@inheritdoc}
*/
@ -180,9 +196,7 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']];
}
$region = new DefaultRegion($cache['region'], clone $this->cache, array(
'lifetime' => $this->regionsConfig->getLifetime($cache['region'])
));
$region = new DefaultRegion($cache['region'], clone $this->cache, $this->regionsConfig->getLifetime($cache['region']));
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
@ -199,4 +213,19 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']] = $region;
}
/**
* {@inheritdoc}
*/
public function getTimestampRegion()
{
if ($this->timestampRegion === null) {
$name = Cache::DEFAULT_TIMESTAMP_REGION_NAME;
$lifetime = $this->regionsConfig->getLifetime($name);
$this->timestampRegion = new UpdateTimestampCache($name, clone $this->cache, $lifetime);
}
return $this->timestampRegion;
}
}

View File

@ -61,6 +61,11 @@ class DefaultQueryCache implements QueryCache
*/
private $validator;
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLogger
*/
protected $cacheLogger;
/**
* @var array
*/
@ -72,12 +77,13 @@ class DefaultQueryCache implements QueryCache
*/
public function __construct(EntityManagerInterface $em, Region $region)
{
$this->em = $em;
$this->region = $region;
$this->uow = $em->getUnitOfWork();
$this->validator = $em->getConfiguration()
->getSecondLevelCacheConfiguration()
->getQueryValidator();
$cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
$this->em = $em;
$this->region = $region;
$this->uow = $em->getUnitOfWork();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->validator = $cacheConfig->getQueryValidator();
}
/**
@ -106,14 +112,24 @@ class DefaultQueryCache implements QueryCache
$hasRelation = ( ! empty($rsm->relationMap));
$persister = $this->uow->getEntityPersister($entityName);
$region = $persister->getCacheRegion();
$regionName = $region->getName();
// @TODO - move to cache hydration componente
foreach ($entry->result as $index => $entry) {
if (($entityEntry = $region->get(new EntityCacheKey($entityName, $entry['identifier']))) === null) {
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($regionName, $entityKey);
}
return null;
}
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($regionName, $entityKey);
}
if ( ! $hasRelation) {
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints);
@ -129,12 +145,21 @@ class DefaultQueryCache implements QueryCache
if ($assoc['type'] & ClassMetadata::TO_ONE) {
if (($assocEntry = $assocRegion->get(new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
return null;
}
$data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
}
continue;
}
@ -147,13 +172,22 @@ class DefaultQueryCache implements QueryCache
foreach ($assoc['list'] as $assocIndex => $assocId) {
if (($assocEntry = $assocRegion->get(new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
}
return null;
}
$element = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);
$collection->hydrateSet($assocIndex, $element);
if ($this->cacheLogger !== null) {
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
}
}
$data[$name] = $collection;

View File

@ -0,0 +1,156 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
/**
* Cache logger chain
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class CacheLoggerChain implements CacheLogger
{
/**
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
*/
private $loggers = array();
/**
* @param string $name
* @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger
*/
public function setLogger($name, CacheLogger $logger)
{
$this->loggers[$name] = $logger;
}
/**
* @param string $name
*
* @return \Doctrine\ORM\Cache\Logging\CacheLogger|null
*/
public function getLogger($name)
{
return isset($this->loggers[$name]) ? $this->loggers[$name] : null;
}
/**
* @return array<\Doctrine\ORM\Cache\Logging\CacheLogger>
*/
public function getLoggers()
{
return $this->loggers;
}
/**
* {@inheritdoc}
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->collectionCacheHit($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->collectionCacheMiss($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function collectionCachePut($regionName, CollectionCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->collectionCachePut($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function entityCacheHit($regionName, EntityCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->entityCacheHit($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function entityCacheMiss($regionName, EntityCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->entityCacheMiss($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function entityCachePut($regionName, EntityCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->entityCachePut($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function queryCacheHit($regionName, QueryCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->queryCacheHit($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function queryCacheMiss($regionName, QueryCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->queryCacheMiss($regionName, $key);
}
}
/**
* {@inheritdoc}
*/
public function queryCachePut($regionName, QueryCacheKey $key)
{
foreach ($this->loggers as $logger) {
$logger->queryCachePut($regionName, $key);
}
}
}

View File

@ -173,6 +173,30 @@ class StatisticsCacheLogger implements CacheLogger
return isset($this->cachePutCountMap[$regionName]) ? $this->cachePutCountMap[$regionName] : 0;
}
/**
* @return array
*/
public function getRegionsMiss()
{
return $this->cacheMissCountMap;
}
/**
* @return array
*/
public function getRegionsHit()
{
return $this->cacheHitCountMap;
}
/**
* @return array
*/
public function getRegionsPut()
{
return $this->cachePutCountMap;
}
/**
* Clear region statistics
*

View File

@ -24,6 +24,7 @@ use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
@ -69,6 +70,16 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
protected $region;
/**
* @var \Doctrine\ORM\Cache\TimestampRegion
*/
protected $timestampRegion;
/**
* @var \Doctrine\ORM\Cache\TimestampCacheKey
*/
protected $timestampKey;
/**
* @var \Doctrine\ORM\Cache\EntityHydrator
*/
@ -109,7 +120,9 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
$this->cacheLogger = $cacheConfig->getCacheLogger();
$this->timestampRegion = $cacheFactory->getTimestampRegion();
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
}
/**
@ -218,13 +231,20 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* Generates a string of currently query
*
* @param array $query
* @param string $criteria
* @param array $orderBy
* @param integer $limit
* @param integer $offset
* @param integer $timestamp
*
* @return string
*/
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null)
{
list($params) = $this->expandParameters($criteria);
list($params) = $this->persister->expandParameters($criteria);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
}
/**
@ -288,8 +308,9 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
//handle only EntityRepository#findOneBy
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, 0, $limit, 0, $orderBy);
$hash = $this->getHash($query, $criteria);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);
@ -326,8 +347,9 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
{
$timestamp = $this->timestampRegion->get($this->timestampKey);
$query = $this->persister->getSelectSQL($criteria, null, 0, $limit, $offset, $orderBy);
$hash = $this->getHash($query, $criteria);
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
$rsm = $this->getResultSetMapping();
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
$queryCache = $this->cache->getQueryCache($this->regionName);

View File

@ -37,6 +37,8 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
*/
public function afterTransactionComplete()
{
$isChanged = false;
if (isset($this->queuedCache['insert'])) {
foreach ($this->queuedCache['insert'] as $entity) {
@ -47,9 +49,10 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
$class = $this->metadataFactory->getMetadataFor($className);
}
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$isChanged = $isChanged ?: $cached;
if ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);
@ -67,9 +70,10 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
$class = $this->metadataFactory->getMetadataFor($className);
}
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$isChanged = $isChanged ?: $cached;
if ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);
@ -80,9 +84,15 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $key) {
$this->region->evict($key);
$isChanged = true;
}
}
if ($isChanged) {
$this->timestampRegion->update($this->timestampKey);
}
$this->queuedCache = array();
}

View File

@ -56,18 +56,28 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
*/
public function afterTransactionComplete()
{
$isChanged = true;
if (isset($this->queuedCache['update'])) {
foreach ($this->queuedCache['update'] as $item) {
$this->region->evict($item['key']);
$isChanged = true;
}
}
if (isset($this->queuedCache['delete'])) {
foreach ($this->queuedCache['delete'] as $item) {
$this->region->evict($item['key']);
$isChanged = true;
}
}
if ($isChanged) {
$this->timestampRegion->update($this->timestampKey);
}
$this->queuedCache = array();
}

View File

@ -43,7 +43,7 @@ class QueryCacheKey extends CacheKey
* @param integer $lifetime Query lifetime
* @param integer $cacheMode Query cache mode
*/
public function __construct($hash, $lifetime, $cacheMode = 3)
public function __construct($hash, $lifetime = 0, $cacheMode = 3)
{
$this->hash = $hash;
$this->lifetime = $lifetime;

View File

@ -39,33 +39,30 @@ class DefaultRegion implements Region
/**
* @var \Doctrine\Common\Cache\CacheProvider
*/
private $cache;
protected $cache;
/**
* @var string
*/
private $name;
protected $name;
/**
* @var integer
*/
private $lifetime = 0;
protected $lifetime = 0;
/**
* @param string $name
* @param \Doctrine\Common\Cache\CacheProvider $cache
* @param array $configuration
* @param integer $lifetime
*/
public function __construct($name, CacheProvider $cache, array $configuration = array())
public function __construct($name, CacheProvider $cache, $lifetime = 0)
{
$this->name = $name;
$this->cache = $cache;
$this->name = $name;
$this->cache = $cache;
$this->lifetime = $lifetime;
$this->cache->setNamespace($this->name);
if (isset($configuration['lifetime']) && $configuration['lifetime'] > 0) {
$this->lifetime = (integer) $configuration['lifetime'];
}
}
/**

View File

@ -0,0 +1,42 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\TimestampCacheEntry;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\Cache\CacheKey;
/**
* Tracks the timestamps of the most recent updates to particular keys.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
{
/**
* {@inheritdoc}
*/
public function update(CacheKey $key)
{
$this->put($key, new TimestampCacheEntry);
}
}

View File

@ -0,0 +1,51 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
/**
* Timestamp cache entry
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class TimestampCacheEntry implements CacheEntry
{
/**
* @var integer
*/
public $time;
/**
* @param array $result
*/
public function __construct($time = null)
{
$this->time = $time ?: microtime(true);
}
/**
* @param array $values
*/
public static function __set_state(array $values)
{
return new self($values['time']);
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
/**
* A key that identifies a timestamped space.
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
class TimestampCacheKey extends CacheKey
{
/**
* @param string $space Result cache id
*/
public function __construct($space)
{
$this->hash = $space;
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Cache;
/**
* Defines the contract for a cache region which will specifically be used to store entity "update timestamps".
*
* @since 2.5
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
*/
interface TimestampRegion extends Region
{
/**
* Update an specific key into the cache region.
*
* @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock.
*
* @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region.
*/
public function update(CacheKey $key);
}

View File

@ -26,7 +26,7 @@ namespace Doctrine\ORM\Mapping;
* @since 2.5
*
* @Annotation
* @Target("CLASS")
* @Target({"CLASS","PROPERTY"})
*/
final class Cache implements Annotation
{

View File

@ -0,0 +1,14 @@
<?php
namespace Doctrine\Tests\Mocks;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\Cache\CacheKey;
class TimestampRegionMock extends CacheRegionMock implements TimestampRegion
{
public function update(CacheKey $key)
{
$this->calls[__FUNCTION__][] = array('key' => $key);
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace Doctrine\Tests\ORM\Cache;
use Doctrine\ORM\Cache\Logging\CacheLoggerChain;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\Tests\Models\Cache\State;
use Doctrine\Tests\DoctrineTestCase;
/**
* @group DDC-2183
*/
class CacheLoggerChainTest extends DoctrineTestCase
{
/**
* @var \Doctrine\ORM\Cache\Logging\CacheLoggerChain
*/
private $logger;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|\Doctrine\ORM\Cache\Logging\CacheLogger
*/
private $mock;
protected function setUp()
{
parent::setUp();
$this->logger = new CacheLoggerChain();
$this->mock = $this->getMock('Doctrine\ORM\Cache\Logging\CacheLogger');
}
public function testGetAndSetLogger()
{
$this->assertEmpty($this->logger->getLoggers());
$this->assertNull($this->logger->getLogger('mock'));
$this->logger->setLogger('mock', $this->mock);
$this->assertSame($this->mock, $this->logger->getLogger('mock'));
$this->assertEquals(array('mock' => $this->mock), $this->logger->getLoggers());
}
public function testEntityCacheChain()
{
$name = 'my_entity_region';
$key = new EntityCacheKey(State::CLASSNAME, array('id' => 1));
$this->logger->setLogger('mock', $this->mock);
$this->mock->expects($this->once())
->method('entityCacheHit')
->with($this->equalTo($name), $this->equalTo($key));
$this->mock->expects($this->once())
->method('entityCachePut')
->with($this->equalTo($name), $this->equalTo($key));
$this->mock->expects($this->once())
->method('entityCacheMiss')
->with($this->equalTo($name), $this->equalTo($key));
$this->logger->entityCacheHit($name, $key);
$this->logger->entityCachePut($name, $key);
$this->logger->entityCacheMiss($name, $key);
}
public function testCollectionCacheChain()
{
$name = 'my_collection_region';
$key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id' => 1));
$this->logger->setLogger('mock', $this->mock);
$this->mock->expects($this->once())
->method('collectionCacheHit')
->with($this->equalTo($name), $this->equalTo($key));
$this->mock->expects($this->once())
->method('collectionCachePut')
->with($this->equalTo($name), $this->equalTo($key));
$this->mock->expects($this->once())
->method('collectionCacheMiss')
->with($this->equalTo($name), $this->equalTo($key));
$this->logger->collectionCacheHit($name, $key);
$this->logger->collectionCachePut($name, $key);
$this->logger->collectionCacheMiss($name, $key);
}
public function testQueryCacheChain()
{
$name = 'my_query_region';
$key = new QueryCacheKey('my_query_hash');
$this->logger->setLogger('mock', $this->mock);
$this->mock->expects($this->once())
->method('queryCacheHit')
->with($this->equalTo($name), $this->equalTo($key));
$this->mock->expects($this->once())
->method('queryCachePut')
->with($this->equalTo($name), $this->equalTo($key));
$this->mock->expects($this->once())
->method('queryCacheMiss')
->with($this->equalTo($name), $this->equalTo($key));
$this->logger->queryCacheHit($name, $key);
$this->logger->queryCachePut($name, $key);
$this->logger->queryCacheMiss($name, $key);
}
}

View File

@ -525,4 +525,9 @@ class CacheFactoryDefaultQueryCacheTest extends \Doctrine\ORM\Cache\DefaultCache
{
return $this->region;
}
public function getTimestampRegion()
{
return new \Doctrine\Tests\Mocks\TimestampRegionMock();
}
}

View File

@ -0,0 +1,134 @@
<?php
namespace Doctrine\Tests\ORM\Cache;
use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\Tests\Models\Cache\State;
use Doctrine\Tests\DoctrineTestCase;
/**
* @group DDC-2183
*/
class StatisticsCacheLoggerTest extends DoctrineTestCase
{
/**
* @var \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger
*/
private $logger;
protected function setUp()
{
parent::setUp();
$this->logger = new StatisticsCacheLogger();
}
public function testEntityCache()
{
$name = 'my_entity_region';
$key = new EntityCacheKey(State::CLASSNAME, array('id' => 1));
$this->logger->entityCacheHit($name, $key);
$this->logger->entityCachePut($name, $key);
$this->logger->entityCacheMiss($name, $key);
$this->assertEquals(1, $this->logger->getHitCount());
$this->assertEquals(1, $this->logger->getPutCount());
$this->assertEquals(1, $this->logger->getMissCount());
$this->assertEquals(1, $this->logger->getRegionHitCount($name));
$this->assertEquals(1, $this->logger->getRegionPutCount($name));
$this->assertEquals(1, $this->logger->getRegionMissCount($name));
}
public function testCollectionCache()
{
$name = 'my_collection_region';
$key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id' => 1));
$this->logger->collectionCacheHit($name, $key);
$this->logger->collectionCachePut($name, $key);
$this->logger->collectionCacheMiss($name, $key);
$this->assertEquals(1, $this->logger->getHitCount());
$this->assertEquals(1, $this->logger->getPutCount());
$this->assertEquals(1, $this->logger->getMissCount());
$this->assertEquals(1, $this->logger->getRegionHitCount($name));
$this->assertEquals(1, $this->logger->getRegionPutCount($name));
$this->assertEquals(1, $this->logger->getRegionMissCount($name));
}
public function testQueryCache()
{
$name = 'my_query_region';
$key = new QueryCacheKey('my_query_hash');
$this->logger->queryCacheHit($name, $key);
$this->logger->queryCachePut($name, $key);
$this->logger->queryCacheMiss($name, $key);
$this->assertEquals(1, $this->logger->getHitCount());
$this->assertEquals(1, $this->logger->getPutCount());
$this->assertEquals(1, $this->logger->getMissCount());
$this->assertEquals(1, $this->logger->getRegionHitCount($name));
$this->assertEquals(1, $this->logger->getRegionPutCount($name));
$this->assertEquals(1, $this->logger->getRegionMissCount($name));
}
public function testMultipleCaches()
{
$coolRegion = 'my_collection_region';
$entityRegion = 'my_entity_region';
$queryRegion = 'my_query_region';
$coolKey = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id' => 1));
$entityKey = new EntityCacheKey(State::CLASSNAME, array('id' => 1));
$queryKey = new QueryCacheKey('my_query_hash');
$this->logger->queryCacheHit($queryRegion, $queryKey);
$this->logger->queryCachePut($queryRegion, $queryKey);
$this->logger->queryCacheMiss($queryRegion, $queryKey);
$this->logger->entityCacheHit($entityRegion, $entityKey);
$this->logger->entityCachePut($entityRegion, $entityKey);
$this->logger->entityCacheMiss($entityRegion, $entityKey);
$this->logger->collectionCacheHit($coolRegion, $coolKey);
$this->logger->collectionCachePut($coolRegion, $coolKey);
$this->logger->collectionCacheMiss($coolRegion, $coolKey);
$this->assertEquals(3, $this->logger->getHitCount());
$this->assertEquals(3, $this->logger->getPutCount());
$this->assertEquals(3, $this->logger->getMissCount());
$this->assertEquals(1, $this->logger->getRegionHitCount($queryRegion));
$this->assertEquals(1, $this->logger->getRegionPutCount($queryRegion));
$this->assertEquals(1, $this->logger->getRegionMissCount($queryRegion));
$this->assertEquals(1, $this->logger->getRegionHitCount($coolRegion));
$this->assertEquals(1, $this->logger->getRegionPutCount($coolRegion));
$this->assertEquals(1, $this->logger->getRegionMissCount($coolRegion));
$this->assertEquals(1, $this->logger->getRegionHitCount($entityRegion));
$this->assertEquals(1, $this->logger->getRegionPutCount($entityRegion));
$this->assertEquals(1, $this->logger->getRegionMissCount($entityRegion));
$miss = $this->logger->getRegionsMiss();
$hit = $this->logger->getRegionsHit();
$put = $this->logger->getRegionsPut();
$this->assertArrayHasKey($coolRegion, $miss);
$this->assertArrayHasKey($queryRegion, $miss);
$this->assertArrayHasKey($entityRegion, $miss);
$this->assertArrayHasKey($coolRegion, $put);
$this->assertArrayHasKey($queryRegion, $put);
$this->assertArrayHasKey($entityRegion, $put);
$this->assertArrayHasKey($coolRegion, $hit);
$this->assertArrayHasKey($queryRegion, $hit);
$this->assertArrayHasKey($entityRegion, $hit);
}
}

View File

@ -138,4 +138,9 @@ class CacheFactorySecondLevelCacheConcurrentTest extends \Doctrine\ORM\Cache\Def
return $mock;
}
public function getTimestampRegion()
{
return new \Doctrine\Tests\Mocks\TimestampRegionMock();
}
}

View File

@ -53,7 +53,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertCount(2, $result2);
$this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName()));
@ -70,7 +70,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertEquals($result1[1]->getName(), $result2[1]->getName());
$this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName()));
@ -256,7 +256,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertCount(2, $result2);
$this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName()));
@ -273,7 +273,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertEquals($result1[1]->getName(), $result2[1]->getName());
$this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName()));
@ -357,7 +357,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertCount(2, $result2);
$this->assertEquals(5, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName()));
$this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName()));
@ -639,7 +639,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertCount(2, $result2);
$this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName()));
@ -656,7 +656,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertEquals($result1[1]->getName(), $result2[1]->getName());
$this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName()));
@ -804,7 +804,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertNotEmpty($result3);
$this->assertEquals($queryCount + 2, $this->getCurrentQueryCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount('foo_region'));
@ -818,7 +818,7 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest
$this->assertNotEmpty($result3);
$this->assertEquals($queryCount + 2, $this->getCurrentQueryCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(6, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount('bar_region'));

View File

@ -3,6 +3,7 @@
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Cache\Country;
use Doctrine\Tests\Models\Cache\State;
/**
* @group DDC-2183
@ -60,13 +61,61 @@ class SecondLevelCacheRepositoryTest extends SecondLevelCacheAbstractTest
$this->assertInstanceOf(Country::CLASSNAME, $countries[0]);
$this->assertInstanceOf(Country::CLASSNAME, $countries[1]);
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId()));
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId()));
}
public function testRepositoryCacheFindAllInvalidation()
{
$this->loadFixturesCountries();
$this->evictRegions();
$this->secondLevelCacheLogger->clearStats();
$this->_em->clear();
$this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId()));
$this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId()));
$repository = $this->_em->getRepository(Country::CLASSNAME);
$queryCount = $this->getCurrentQueryCount();
$this->assertCount(2, $repository->findAll());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$queryCount = $this->getCurrentQueryCount();
$countries = $repository->findAll();
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
$this->assertCount(2, $countries);
$this->assertInstanceOf(Country::CLASSNAME, $countries[0]);
$this->assertInstanceOf(Country::CLASSNAME, $countries[1]);
$country = new Country('foo');
$this->_em->persist($country);
$this->_em->flush();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$this->assertCount(3, $repository->findAll());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$country = $repository->find($country->getId());
$this->_em->remove($country);
$this->_em->flush();
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$this->assertCount(2, $repository->findAll());
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
}
public function testRepositoryCacheFindBy()
{
$this->loadFixturesCountries();
@ -91,7 +140,7 @@ class SecondLevelCacheRepositoryTest extends SecondLevelCacheAbstractTest
$this->assertCount(1, $countries);
$this->assertInstanceOf(Country::CLASSNAME, $countries[0]);
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId()));
@ -120,9 +169,82 @@ class SecondLevelCacheRepositoryTest extends SecondLevelCacheAbstractTest
$this->assertInstanceOf(Country::CLASSNAME, $country);
$this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount());
$this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount());
$this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId()));
}
public function testRepositoryCacheFindAllToOneAssociation()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->evictRegions();
$this->secondLevelCacheLogger->clearStats();
$this->_em->clear();
// load from database
$repository = $this->_em->getRepository(State::CLASSNAME);
$queryCount = $this->getCurrentQueryCount();
$entities = $repository->findAll();
$this->assertCount(4, $entities);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$this->assertInstanceOf(State::CLASSNAME, $entities[0]);
$this->assertInstanceOf(State::CLASSNAME, $entities[1]);
$this->assertInstanceOf(Country::CLASSNAME, $entities[0]->getCountry());
$this->assertInstanceOf(Country::CLASSNAME, $entities[0]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[0]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[1]->getCountry());
// load from cache
$queryCount = $this->getCurrentQueryCount();
$entities = $repository->findAll();
$this->assertCount(4, $entities);
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
$this->assertInstanceOf(State::CLASSNAME, $entities[0]);
$this->assertInstanceOf(State::CLASSNAME, $entities[1]);
$this->assertInstanceOf(Country::CLASSNAME, $entities[0]->getCountry());
$this->assertInstanceOf(Country::CLASSNAME, $entities[1]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[0]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[1]->getCountry());
// invalidate cache
$this->_em->persist(new State('foo', $this->_em->find(Country::CLASSNAME, $this->countries[0]->getId())));
$this->_em->flush();
$this->_em->clear();
// load from database
$queryCount = $this->getCurrentQueryCount();
$entities = $repository->findAll();
$this->assertCount(5, $entities);
$this->assertEquals($queryCount + 1, $this->getCurrentQueryCount());
$this->assertInstanceOf(State::CLASSNAME, $entities[0]);
$this->assertInstanceOf(State::CLASSNAME, $entities[1]);
$this->assertInstanceOf(Country::CLASSNAME, $entities[0]->getCountry());
$this->assertInstanceOf(Country::CLASSNAME, $entities[1]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[0]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[1]->getCountry());
// load from cache
$queryCount = $this->getCurrentQueryCount();
$entities = $repository->findAll();
$this->assertCount(5, $entities);
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
$this->assertInstanceOf(State::CLASSNAME, $entities[0]);
$this->assertInstanceOf(State::CLASSNAME, $entities[1]);
$this->assertInstanceOf(Country::CLASSNAME, $entities[0]->getCountry());
$this->assertInstanceOf(Country::CLASSNAME, $entities[1]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[0]->getCountry());
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $entities[1]->getCountry());
}
}