From 8fe90d1fbbc775229349fe866bd1d64cfa55203a Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Wed, 13 Feb 2013 20:42:13 -0200 Subject: [PATCH 01/71] Second cache level POC --- lib/Doctrine/ORM/Cache.php | 266 +++++++++++++ ...NonStrictReadWriteRegionAccessStrategy.php | 104 +++++ .../ORM/Cache/Access/ReadOnlyRegionAccess.php | 42 ++ lib/Doctrine/ORM/Cache/AccessProvider.php | 53 +++ .../ORM/Cache/CacheAccessProvider.php | 85 ++++ lib/Doctrine/ORM/Cache/CacheException.php | 39 ++ lib/Doctrine/ORM/Cache/CacheKey.php | 32 ++ lib/Doctrine/ORM/Cache/CollectionCacheKey.php | 65 ++++ .../ORM/Cache/CollectionEntryStructure.php | 94 +++++ lib/Doctrine/ORM/Cache/EntityCacheKey.php | 58 +++ .../ORM/Cache/EntityEntryStructure.php | 106 +++++ lib/Doctrine/ORM/Cache/Lock.php | 51 +++ lib/Doctrine/ORM/Cache/LockException.php | 31 ++ lib/Doctrine/ORM/Cache/QueryCache.php | 54 +++ lib/Doctrine/ORM/Cache/QueryCacheKey.php | 36 ++ lib/Doctrine/ORM/Cache/Region.php | 92 +++++ .../ORM/Cache/Region/DefaultRegion.php | 205 ++++++++++ lib/Doctrine/ORM/Cache/RegionAccess.php | 101 +++++ .../ORM/Cache/TransactionalRegion.php | 72 ++++ .../ORM/Cache/TransactionalRegionAccess.php | 51 +++ lib/Doctrine/ORM/Configuration.php | 41 ++ lib/Doctrine/ORM/EntityManager.php | 23 +- lib/Doctrine/ORM/Mapping/Cache.php | 49 +++ .../ORM/Mapping/ClassMetadataInfo.php | 65 +++- .../ORM/Mapping/Driver/AnnotationDriver.php | 20 + .../Mapping/Driver/DoctrineAnnotations.php | 3 +- .../AbstractCollectionPersister.php | 107 ++++- .../ORM/Persisters/BasicEntityPersister.php | 229 ++++++++++- lib/Doctrine/ORM/Proxy/ProxyFactory.php | 23 +- lib/Doctrine/ORM/UnitOfWork.php | 63 ++- tests/Doctrine/Tests/Models/Cache/City.php | 66 ++++ tests/Doctrine/Tests/Models/Cache/Country.php | 48 +++ tests/Doctrine/Tests/Models/Cache/State.php | 90 +++++ .../ORM/Cache/ReadOnlyRegionAccessTest.php | 68 ++++ .../ORM/Functional/SecondLevelCacheTest.php | 366 ++++++++++++++++++ .../ORM/Performance/SencondLevelCacheTest.php | 86 ++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 42 ++ tests/Doctrine/Tests/OrmTestCase.php | 17 + 38 files changed, 3002 insertions(+), 41 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache.php create mode 100644 lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php create mode 100644 lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php create mode 100644 lib/Doctrine/ORM/Cache/AccessProvider.php create mode 100644 lib/Doctrine/ORM/Cache/CacheAccessProvider.php create mode 100644 lib/Doctrine/ORM/Cache/CacheException.php create mode 100644 lib/Doctrine/ORM/Cache/CacheKey.php create mode 100644 lib/Doctrine/ORM/Cache/CollectionCacheKey.php create mode 100644 lib/Doctrine/ORM/Cache/CollectionEntryStructure.php create mode 100644 lib/Doctrine/ORM/Cache/EntityCacheKey.php create mode 100644 lib/Doctrine/ORM/Cache/EntityEntryStructure.php create mode 100644 lib/Doctrine/ORM/Cache/Lock.php create mode 100644 lib/Doctrine/ORM/Cache/LockException.php create mode 100644 lib/Doctrine/ORM/Cache/QueryCache.php create mode 100644 lib/Doctrine/ORM/Cache/QueryCacheKey.php create mode 100644 lib/Doctrine/ORM/Cache/Region.php create mode 100644 lib/Doctrine/ORM/Cache/Region/DefaultRegion.php create mode 100644 lib/Doctrine/ORM/Cache/RegionAccess.php create mode 100644 lib/Doctrine/ORM/Cache/TransactionalRegion.php create mode 100644 lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php create mode 100644 lib/Doctrine/ORM/Mapping/Cache.php create mode 100644 tests/Doctrine/Tests/Models/Cache/City.php create mode 100644 tests/Doctrine/Tests/Models/Cache/Country.php create mode 100644 tests/Doctrine/Tests/Models/Cache/State.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php create mode 100644 tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php new file mode 100644 index 00000000000..4530283f5ab --- /dev/null +++ b/lib/Doctrine/ORM/Cache.php @@ -0,0 +1,266 @@ +. + */ + +namespace Doctrine\ORM; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; + +/** + * Provides an API for querying/managing the second level cache regions. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class Cache +{ + /** + * @var \Doctrine\ORM\EntityManager + */ + private $em; + + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @param \Doctrine\ORM\EntityManager $em + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + $this->uow = $this->em->getUnitOfWork(); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * + * @return \Doctrine\ORM\Cache\RegionAccess + */ + public function getEntityCacheRegionAcess(ClassMetadata $metadata) + { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if ( ! $persister->hasCache()) { + return null; + } + + return $persister->getCacheRegionAcess(); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $association The field name that represents the association. + * + * @return \Doctrine\ORM\Cache\RegionAccess + */ + public function getCollectionCacheRegionAcess(ClassMetadata $metadata, $association) + { + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if ( ! $persister->hasCache()) { + return null; + } + + return $persister->getCacheRegionAcess(); + } + + /** + * Determine whether the cache contains data for the given entity "instance". + *

+ * The semantic here is whether the cache contains data visible for the + * current call context. + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param array $identifier The entity identifier + * + * @return boolean true if the underlying cache contains corresponding data; false otherwise. + */ + public function containsEntity(ClassMetadata $metadata, array $identifier) + { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + $key = $this->buildEntityCacheKey($metadata, $identifier); + + if ( ! $persister->hasCache()) { + return false; + } + + return $persister->getCacheRegionAcess()->getRegion()->contains($key); + } + + /** + * Evicts the entity data for a particular entity "instance". + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param mixed $identifier The entity identifier. + */ + public function evictEntity(ClassMetadata $metadata, $identifier) + { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + $key = $this->buildEntityCacheKey($metadata, $identifier); + + if ( ! $persister->hasCache()) { + return; + } + + return $persister->getCacheRegionAcess()->evict($key); + } + + /** + * Evicts all entity data from the given region. + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + */ + public function evictEntityRegion(ClassMetadata $metadata) + { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if ( ! $persister->hasCache()) { + return; + } + + return $persister->getCacheRegionAcess()->evictAll(); + } + + /** + * Determine whether the cache contains data for the given collection. + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + * + * @return boolean true if the underlying cache contains corresponding data; false otherwise. + */ + public function containsCollection(ClassMetadata $metadata, $association, array $ownerIdentifier) + { + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); + + if ( ! $persister->hasCache()) { + return; + } + + return $persister->getCacheRegionAcess()->getRegion()->contains($key); + } + + /** + * Evicts the cache data for the given identified collection instance. + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + */ + public function evictCollection(ClassMetadata $metadata, $association, array $ownerIdentifier) + { + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); + + if ( ! $persister->hasCache()) { + return; + } + + return $persister->getCacheRegionAcess()->evict($key); + } + + /** + * Evicts all entity data from the given region. + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $association The field name that represents the association. + */ + public function evictCollectionRegion(ClassMetadata $metadata, $association) + { + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if ( ! $persister->hasCache()) { + return; + } + + return $persister->getCacheRegionAcess()->evictAll(); + } + + /** + * Determine whether the cache contains data for the given query. + * + * @param string $regionName The cache name given to the query. + * + * @return boolean true if the underlying cache contains corresponding data; false otherwise. + */ + public function containsQuery($regionName) + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * Evicts all cached query results under the given name. + * + * @param string $regionName The cache name associated to the queries being cached. + */ + public function evictQueryRegion($regionName) + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * Evict data from all query regions. + */ + public function evictQueryRegions() + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * Get query cache by region name or create a new one if none exist. + * + * @param regionName Query cache region name. + * + * @return Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. + */ + public function getQueryCache($regionName) + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param array $identifier The entity identifier. + * + * @return \Doctrine\ORM\Cache\EntityCacheKey + */ + public function buildEntityCacheKey(ClassMetadata $metadata, array $identifier) + { + return new EntityCacheKey($identifier, $metadata->rootEntityName); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + * + * @return \Doctrine\ORM\Cache\CollectionCacheKey + */ + public function buildCollectionCacheKey(ClassMetadata $metadata, $association, array $ownerIdentifier) + { + return new CollectionCacheKey($ownerIdentifier, $metadata->rootEntityName, $association); + } +} diff --git a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php new file mode 100644 index 00000000000..5789288d275 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php @@ -0,0 +1,104 @@ +. + */ + +namespace Doctrine\ORM\Cache\Access; + +use Doctrine\ORM\Cache\RegionAccess; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\ORM\Cache\Region; +use Doctrine\ORM\Cache\Lock; + +/** + * Specific non-strict read/write region access strategy + * + * @since 2.5 + * @author Fabio B. Silva + */ +class NonStrictReadWriteRegionAccessStrategy implements RegionAccess +{ + /** + * @var \Doctrine\ORM\Cache\Region + */ + private $region; + + /** + * @param \Doctrine\ORM\Cache\Region $region + */ + public function __construct(Region $region) + { + $this->region = $region; + } + + /** + * {@inheritdoc} + */ + public function getRegion() + { + return $this->region; + } + + /** + * {@inheritdoc} + */ + public function afterInsert(CacheKey $key, array $value) + { + return $this->region->put($key, $value); + } + + /** + * {@inheritdoc} + */ + public function afterUpdate(CacheKey $key, array $value, Lock $lock = null) + { + return $this->region->put($key, $value); + } + + /** + * {@inheritdoc} + */ + public function get(CacheKey $key) + { + return $this->region->get($key); + } + + /** + * {@inheritdoc} + */ + public function put(CacheKey $key, array $value) + { + return $this->region->put($key, $value); + } + + /** + * {@inheritdoc} + */ + public function evict(CacheKey $key) + { + return $this->region->evict($key); + } + + /** + * {@inheritdoc} + */ + public function evictAll() + { + return $this->region->evictAll(); + } +} diff --git a/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php b/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php new file mode 100644 index 00000000000..a3884f282f9 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\ORM\Cache\Access; + +use Doctrine\ORM\Cache\CacheException; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\ORM\Cache\Lock; + +/** + * Specific read-only region access strategy + * + * @since 2.5 + * @author Fabio B. Silva + */ +class ReadOnlyRegionAccess extends NonStrictReadWriteRegionAccessStrategy +{ + /** + * {@inheritdoc} + */ + public function afterUpdate(CacheKey $key, array $value, Lock $lock = null) + { + throw CacheException::updateReadOnlyobject(); + } +} diff --git a/lib/Doctrine/ORM/Cache/AccessProvider.php b/lib/Doctrine/ORM/Cache/AccessProvider.php new file mode 100644 index 00000000000..4d834434238 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/AccessProvider.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Mapping\ClassMetadata; + +/** + * @since 2.5 + * @author Fabio B. Silva + */ +interface AccessProvider +{ + /** + * Build an entity RegionAccess for the input entity. + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * + * @return \Doctrine\ORM\Cache\RegionAccess The built region access. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + */ + public function buildEntityRegionAccessStrategy(ClassMetadata $metadata); + + /** + * Build an collection RegionAccess for the input entity accociation. + * + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $fieldName The field name that represents the association. + * + * @return \Doctrine\ORM\Cache\RegionAccess The built region access. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + */ + public function buildCollectioRegionAccessStrategy(ClassMetadata $metadata, $fieldName); +} diff --git a/lib/Doctrine/ORM/Cache/CacheAccessProvider.php b/lib/Doctrine/ORM/Cache/CacheAccessProvider.php new file mode 100644 index 00000000000..78e9283377a --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CacheAccessProvider.php @@ -0,0 +1,85 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\Common\Cache\Cache; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Cache\Region\DefaultRegion; +use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess; +use Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy; + +/** + * @since 2.5 + * @author Fabio B. Silva + */ +class CacheAccessProvider implements AccessProvider +{ + /** + * @var \Doctrine\Common\Cache\Cache + */ + private $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) + { + $properties = $metadata->cache['properties']; + $regionName = $metadata->cache['region']; + $usage = $metadata->cache['usage']; + + if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { + return new ReadOnlyRegionAccess(new DefaultRegion($regionName, $this->cache, $properties)); + } + + if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + return new NonStrictReadWriteRegionAccessStrategy(new DefaultRegion($regionName, $this->cache, $properties)); + } + + throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); + } + + /** + * {@inheritdoc} + */ + public function buildCollectioRegionAccessStrategy(ClassMetadata $metadata, $fieldName) + { + $mapping = $metadata->getAssociationMapping($fieldName); + $properties = $mapping['cache']['properties']; + $regionName = $mapping['cache']['region']; + $usage = $mapping['cache']['usage']; + + if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { + return new ReadOnlyRegionAccess(new DefaultRegion($regionName, $this->cache, $properties)); + } + + if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + return new NonStrictReadWriteRegionAccessStrategy(new DefaultRegion($regionName, $this->cache, $properties)); + } + + throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); + } +} diff --git a/lib/Doctrine/ORM/Cache/CacheException.php b/lib/Doctrine/ORM/Cache/CacheException.php new file mode 100644 index 00000000000..e04d0f0ed6f --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CacheException.php @@ -0,0 +1,39 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\ORMException; + +/** + * Exception for cache. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class CacheException extends ORMException +{ + /** + * @return \Doctrine\ORM\Cache\CacheException + */ + public static function updateReadOnlyobject() + { + return new self("Can't update a readonly object"); + } +} diff --git a/lib/Doctrine/ORM/Cache/CacheKey.php b/lib/Doctrine/ORM/Cache/CacheKey.php new file mode 100644 index 00000000000..a607e585d2b --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CacheKey.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Defines entity classes / collection key to be stored in the cache region. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface CacheKey +{ + public function hash(); +} diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheKey.php b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php new file mode 100644 index 00000000000..068f926b075 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php @@ -0,0 +1,65 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Defines entity collection roles to be stored in the cache region. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class CollectionCacheKey implements CacheKey +{ + /** + * @var array + */ + public $ownerIdentifier; + + /** + * @var string + */ + public $entityClass; + + /** + * @var string + */ + public $association; + + /** + * @param string $entityClass The entity class. + * @param string $association The field name that represents the association. + * @param array $ownerIdentifier The identifier of the owning entity. + */ + public function __construct($entityClass, $association, array $ownerIdentifier) + { + $this->entityClass = $entityClass; + $this->association = $association; + $this->ownerIdentifier = $ownerIdentifier; + } + + /** + * {@inheritdoc} + */ + public function hash() + { + return hash('sha512', $this->entityClass . $this->association . serialize($this->ownerIdentifier)); + } +} diff --git a/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php new file mode 100644 index 00000000000..c790166a62a --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php @@ -0,0 +1,94 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Cache\CollectionCacheKey; + +/** + * Structured cache entry for collection + * + * @since 2.5 + * @author Fabio B. Silva + */ +class CollectionEntryStructure +{ + /** + * @var \Doctrine\ORM\EntityManager + */ + private $em; + + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @param \Doctrine\ORM\EntityManager $em The entity manager. + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + $this->uow = $em->getUnitOfWork(); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key. + * @param array|\Doctrine\Common\Collections\Collection $collection The collection. + * + * @return array + */ + public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection) + { + $data = array(); + + foreach ($collection as $key => $entity) { + $data[$key] = $this->uow->getEntityIdentifier($entity); + } + + return $data; + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key. + * @param array $cache Cached collection data. + * @param Doctrine\ORM\PersistentCollection $collection The collection to load the cache into. + * + * @return array + */ + public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array $cache, PersistentCollection $collection) + { + $list = array(); + + foreach ($cache as $key => $entry) { + $entity = $this->em->getReference($metadata->rootEntityName, $entry); + $list[$key] = $entity; + + $collection->hydrateSet($key, $entity); + } + + return $list; + } +} diff --git a/lib/Doctrine/ORM/Cache/EntityCacheKey.php b/lib/Doctrine/ORM/Cache/EntityCacheKey.php new file mode 100644 index 00000000000..46fb5ff1e67 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/EntityCacheKey.php @@ -0,0 +1,58 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Defines entity classes roles to be stored in the cache region. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class EntityCacheKey implements CacheKey +{ + /** + * @var array + */ + public $identifier; + + /** + * @var string + */ + public $entityClass; + + /** + * @param array $identifier + * @param string $entityClass + */ + public function __construct(array $identifier, $entityClass) + { + $this->identifier = $identifier; + $this->entityClass = $entityClass; + } + + /** + * {@inheritdoc} + */ + public function hash() + { + return hash('sha512', $this->entityClass . serialize($this->identifier)); + } +} diff --git a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php new file mode 100644 index 00000000000..c9e3cfcc6ca --- /dev/null +++ b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php @@ -0,0 +1,106 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Query; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Mapping\ClassMetadata; + +/** + * Structured cache entry for entities + * + * @since 2.5 + * @author Fabio B. Silva + */ +class EntityEntryStructure +{ + /** + * @var \Doctrine\ORM\EntityManager + */ + private $em; + + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @param \Doctrine\ORM\EntityManager $em The entity manager. + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + $this->uow = $em->getUnitOfWork(); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. + * @param object $entity The entity. + * + * @return array + */ + public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity) + { + $data = $this->uow->getOriginalEntityData($entity); + $data = array_merge($data, $key->identifier); // why update has no identifier values ? + + foreach ($metadata->associationMappings as $name => $association) { + + if ( ! isset($association['cache'])) { + unset($data[$name]); + + continue; + } + + if ( ! isset($data[$name]) || $data[$name] === null) { + continue; + } + + if ($association['type'] & ClassMetadata::TO_ONE) { + $data[$name] = $this->uow->getEntityIdentifier($data[$name]); + } + + if ($association['type'] & ClassMetadata::TO_MANY) { + unset($data[$name]); // handle collection here ? + } + } + + return $data; + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. + * @param array $cache The entity data. + * @param object $entity The entity to load the cache into. If not specified, a new entity is created. + */ + public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, array $cache, $entity = null) + { + if ($entity !== null) { + $hints[Query::HINT_REFRESH] = true; + $hints[Query::HINT_REFRESH_ENTITY] = $entity; + } + + return $this->uow->createEntity($metadata->name, $cache, $hints); + } +} diff --git a/lib/Doctrine/ORM/Cache/Lock.php b/lib/Doctrine/ORM/Cache/Lock.php new file mode 100644 index 00000000000..a45746e9c32 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Lock.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Cache Lock + * + * @since 2.5 + * @author Fabio B. Silva + */ +class Lock +{ + /** + * @var string + */ + public $value; + + /** + * @var integer + */ + public $time; + + /** + * @param string $value + * @param integer $time + */ + public function __construct($value, $time = null) + { + $this->value = $value; + $this->time = $time ? : time(); + } + +} diff --git a/lib/Doctrine/ORM/Cache/LockException.php b/lib/Doctrine/ORM/Cache/LockException.php new file mode 100644 index 00000000000..0322eb51f4d --- /dev/null +++ b/lib/Doctrine/ORM/Cache/LockException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Lock exception for cache. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class LockException extends CacheException +{ + +} diff --git a/lib/Doctrine/ORM/Cache/QueryCache.php b/lib/Doctrine/ORM/Cache/QueryCache.php new file mode 100644 index 00000000000..79176fae930 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/QueryCache.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Defines the contract for caches capable of storing query results. + * These caches should only concern themselves with storing the matching result ids. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface QueryCache +{ + /** + * @return boolean + */ + public function clear(); + + /** + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param array $result + * + * @return boolean + */ + public function put(QueryCacheKey $key, array $result); + + /** + * @return array + */ + public function get(QueryCacheKey $key); + + /** + * @return \Doctrine\ORM\Cache\Region + */ + public function getRegion(); +} diff --git a/lib/Doctrine/ORM/Cache/QueryCacheKey.php b/lib/Doctrine/ORM/Cache/QueryCacheKey.php new file mode 100644 index 00000000000..654f5e27ca8 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/QueryCacheKey.php @@ -0,0 +1,36 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * A key that identifies a particular query with bound parameter values. + * This is the object Hibernate uses as its key into its query cache. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class QueryCacheKey implements CacheKey +{ + public function hash() + { + throw new \BadMethodCallException("Not implemented."); + } +} diff --git a/lib/Doctrine/ORM/Cache/Region.php b/lib/Doctrine/ORM/Cache/Region.php new file mode 100644 index 00000000000..ba63d1bdf60 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Region.php @@ -0,0 +1,92 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Defines a contract for accessing a particular named region. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface Region extends \Countable +{ + /** + * Retrieve the name of this region. + * + * @return string The region name + */ + public function getName(); + + /** + * Determine whether this region contains data for the given key. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key + * + * @return boolean TRUE if the underlying cache contains corresponding data; FALSE otherwise. + */ + public function contains(CacheKey $key); + + /** + * Get an item from the cache. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to be retrieved. + * + * @return array The cached object or data NULL + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the item or region. + */ + public function get(CacheKey $key); + + /** + * Put an item into the cache. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item. + * @param array $value The item to cache. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region. + */ + public function put(CacheKey $key, array $value); + + /** + * Remove an item from the cache. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region. + */ + public function evict(CacheKey $key); + + /** + * Remove all contents of this particular cache region. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region. + */ + public function evictAll(); + + /** + * Get the contents of this region as an array. + * + * @return array The cached data. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region. + */ + public function toArray(); +} diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php new file mode 100644 index 00000000000..ab126bddd61 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -0,0 +1,205 @@ +. + */ + +namespace Doctrine\ORM\Cache\Region; + +use Doctrine\ORM\Cache\Region; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\Common\Cache\Cache; + +/** + * Defines a contract for accessing a particular named region. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class DefaultRegion implements Region +{ + /** + * @var \Doctrine\Common\Cache\Cache + */ + private $cache; + + /** + * @var string + */ + private $name; + + /** + * @var integer + */ + private $lifetime = 0; + + /** + * @param strgin $name + * @param \Doctrine\Common\Cache\Cache $cache + * @param array $properties + */ + public function __construct($name, Cache $cache, array $properties = array()) + { + $this->name = $name; + $this->cache = $cache; + + if (isset($properties['lifetime']) && $properties['lifetime'] > 0) { + $this->lifetime = (integer) $properties['lifetime']; + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * @return \Doctrine\Common\Cache\AccessProvider + */ + public function getCache() + { + return $this->cache; + } + + private function entryKey(CacheKey $key) + { + return sprintf("%s::values[%s]", $this->name, $key->hash()); + } + + private function entriesMapKey() + { + return sprintf("%s::entries", $this->name); + } + + /** + * {@inheritdoc} + */ + public function contains(CacheKey $key) + { + return $this->cache->contains($this->entryKey($key)); + } + + /** + * {@inheritdoc} + */ + public function get(CacheKey $key) + { + return $this->cache->fetch($this->entryKey($key)) ?: null; + } + + /** + * {@inheritdoc} + */ + public function put(CacheKey $key, array $value) + { + $entriesKey = $this->entriesMapKey(); + $entryKey = $this->entryKey($key); + $entries = $this->cache->fetch($entriesKey); + + $entries[$entryKey] = true; + + if ($this->cache->save($entryKey, $value, $this->lifetime)) { + $this->cache->save($entriesKey, $entries); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function evict(CacheKey $key) + { + $entriesKey = $this->entriesMapKey(); + $entryKey = $this->entryKey($key); + $entries = $this->cache->fetch($entriesKey); + + if ($this->cache->delete($entryKey)) { + + unset($entries[$entryKey]); + + $this->cache->save($entriesKey, $entries); + + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function evictAll() + { + $entriesKey = $this->entriesMapKey(); + $entries = $this->cache->fetch($entriesKey); + + if ( ! is_array($entries) || empty($entries)) { + return true; + } + + foreach ($entries as $entryKey => $value) { + if ($this->cache->delete($entryKey)) { + unset($entries[$entryKey]); + } + } + + $this->cache->save($entriesKey, $entries); + + return empty($entries); + } + + /** + * {@inheritdoc} + */ + public function count() + { + $entriesKey = $this->entriesMapKey(); + $entries = $this->cache->fetch($entriesKey); + + if ( ! is_array($entries)) { + return 0; + } + + return count($entries); + } + + /** + * {@inheritdoc} + */ + public function toArray() + { + $data = array(); + $entriesKey = $this->entriesMapKey(); + $entries = $this->cache->fetch($entriesKey); + + if ( ! is_array($entries) || empty($entries)) { + return array(); + } + + foreach ($entries as $entryKey => $value) { + $data[] = $this->cache->fetch($entryKey); + } + + return $data; + } +} diff --git a/lib/Doctrine/ORM/Cache/RegionAccess.php b/lib/Doctrine/ORM/Cache/RegionAccess.php new file mode 100644 index 00000000000..3c33618c77c --- /dev/null +++ b/lib/Doctrine/ORM/Cache/RegionAccess.php @@ -0,0 +1,101 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Region access strategy + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface RegionAccess +{ + /** + * Get the wrapped data cache region + * + * @return \Doctrine\ORM\Cache\Region The underlying region + */ + public function getRegion(); + + /** + * Attempt to retrieve an object from the cache. + * + * @param \Doctrine\ORM\Cache\CacheKey $identifier The identifier of the item to be retrieved. + * + * @return the cached object or null + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function get(CacheKey $key); + + /** + * Attempt to cache an object, after loading from the database. + * + * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. + * @param array $value The item. + * + * @return true if the object was successfully cached. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function put(CacheKey $key, array $value); + + /** + * Called after an item has been inserted (after the transaction completes). + * + * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. + * @param array $value The item. + * + * @return boolean true If the contents of the cache actual were changed. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function afterInsert(CacheKey $key, array $value); + + /** + * Called after an item has been updated (after the transaction completes). + * + * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. + * @param array $value The item. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} + * + * @return boolean true If the contents of the cache actual were changed. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function afterUpdate(CacheKey $key, array $value, Lock $lock = null); + + /** + * Forcibly evict an item from the cache immediately without regard for locks. + * + * @param \Doctrine\ORM\Cache\CacheKey $identifier The identifier of the item to remove. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function evict(CacheKey $key); + + /** + * Forcibly evict all items from the cache immediately without regard for locks. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function evictAll(); +} diff --git a/lib/Doctrine/ORM/Cache/TransactionalRegion.php b/lib/Doctrine/ORM/Cache/TransactionalRegion.php new file mode 100644 index 00000000000..e1d3f6f6a15 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/TransactionalRegion.php @@ -0,0 +1,72 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Defines contract for regions which hold transactionally-managed data. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface TransactionalRegion extends Region +{ + /** + * Attempts to write lock the mapping for the given key. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. + * + * @return string A lock identifier + * + * @throws \Doctrine\ORM\Cache\LockException if the lock already exists. + */ + public function writeLock($key); + + /** + * Attempts to write unlock the mapping for the given key. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. + * @param string $lock The lock identifier previously obtained from {@link writeLock} + * + * @throws \Doctrine\ORM\Cache\LockException + */ + public function writeUnlock($key, $lock); + + /** + * Attempts to read lock the mapping for the given key. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. + * + * @return string A lock identifier. + * + * @throws \Doctrine\ORM\Cache\LockException if the lock already exists. + */ + public function readLock($key); + + /** + * Attempts to read unlock the mapping for the given key. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. + * @param string $lock The lock identifier previously obtained from {@link writeLock} + * + * @throws \Doctrine\ORM\Cache\LockException + */ + public function readUnlock($key, $lock); +} diff --git a/lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php b/lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php new file mode 100644 index 00000000000..c4c368111f9 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Defines contract for regions which hold transactionally-managed data. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface TransactionalRegionAccess extends RegionAccess +{ + /** + * We are going to attempt to update/delete the keyed object. + * + * @param \Doctrine\ORM\Cache\CacheKey $identifier The key of the item to lock. + * + * @return \Doctrine\ORM\Cache\Lock A representation of our lock on the item; or null. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function lockItem(CacheKey $key); + + /** + * Called when we have finished the attempted update/delete (which may or may not have been successful), after transaction completion. + * + * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function unlockItem(CacheKey $key, Lock $lock); +} diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 7ab147f9847..968bdf20ba5 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -35,6 +35,7 @@ use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\Repository\DefaultRepositoryFactory; use Doctrine\ORM\Repository\RepositoryFactory; +use Doctrine\ORM\Cache\AccessProvider; /** * Configuration container for all configuration options of Doctrine. @@ -233,6 +234,46 @@ public function getMetadataDriverImpl() : null; } + /** + * @return boolean + */ + public function isSecondLevelCacheEnabled() + { + return isset($this->_attributes['isSecondLevelCacheEnabled']) + ? $this->_attributes['isSecondLevelCacheEnabled'] + : false; + } + + /** + * @param boolean $flag + * + * @return void + */ + public function setSecondLevelCacheEnabled($flag = true) + { + $this->_attributes['isSecondLevelCacheEnabled'] = (boolean) $flag; + } + + /** + * @return \Doctrine\ORM\Cache\AccessProvider|null + */ + public function getSecondLevelCacheAccessProvider() + { + return isset($this->_attributes['secondLevelCacheAccessProvider']) + ? $this->_attributes['secondLevelCacheAccessProvider'] + : null; + } + + /** + * @param \Doctrine\ORM\Cache\AccessProvider $provider + * + * @return void + */ + public function setSecondLevelCacheAccessProvider(AccessProvider $provider) + { + $this->_attributes['secondLevelCacheAccessProvider'] = $provider; + } + /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index d21d3fb86d3..af01229c1d8 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -134,6 +134,11 @@ */ private $filterCollection; + /** + * @var \Doctrine\ORM\Cache The second level cache regions API. + */ + private $cache; + /** * Creates a new EntityManager that operates on the given database connection * and uses the given Configuration and EventManager implementations. @@ -162,6 +167,10 @@ protected function __construct(Connection $conn, Configuration $config, EventMan $config->getProxyNamespace(), $config->getAutoGenerateProxyClasses() ); + + if ($config->isSecondLevelCacheEnabled()) { + $this->cache = new Cache($this); + } } /** @@ -217,6 +226,14 @@ public function beginTransaction() $this->conn->beginTransaction(); } + /** + * @return \Doctrine\ORM\Cache + */ + public function getCache() + { + return $this->cache; + } + /** * Executes a function in a transaction. * @@ -457,14 +474,14 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion switch ($lockMode) { case LockMode::NONE: - return $persister->load($sortedId); + return $persister->loadById($sortedId); case LockMode::OPTIMISTIC: if ( ! $class->isVersioned) { throw OptimisticLockException::notVersioned($class->name); } - $entity = $persister->load($sortedId); + $entity = $persister->loadById($sortedId); $unitOfWork->lock($entity, $lockMode, $lockVersion); @@ -475,7 +492,7 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion throw TransactionRequiredException::transactionRequired(); } - return $persister->load($sortedId, null, null, array(), $lockMode); + return $persister->loadById($sortedId, $lockMode); } } diff --git a/lib/Doctrine/ORM/Mapping/Cache.php b/lib/Doctrine/ORM/Mapping/Cache.php new file mode 100644 index 00000000000..116d6b0a193 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Cache.php @@ -0,0 +1,49 @@ +. + */ + +namespace Doctrine\ORM\Mapping; + +/** + * Caching to an entity or a collection. + * + * @author Fabio B. Silva + * @since 2.5 + * + * @Annotation + * @Target("CLASS") + */ +final class Cache implements Annotation +{ + /** + * @Enum({"READ_ONLY", "NONSTRICT_READ_WRITE", "READ_WRITE"}) + * + * @var string The concurrency strategy. + */ + public $usage = 'READ_ONLY'; + + /** + * @var string Cache region name. + */ + public $region; + + /** + * @var array Cache options. + */ + public $properties; +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 9b3b294925d..29ea6911933 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -26,7 +26,6 @@ use ReflectionClass; use Doctrine\Common\Persistence\Mapping\ClassMetadata; use Doctrine\Common\ClassLoader; -use Doctrine\Common\EventArgs; /** * A ClassMetadata instance holds all the object-relational mapping metadata @@ -189,6 +188,21 @@ class ClassMetadataInfo implements ClassMetadata */ const TO_MANY = 12; + /** + * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks, + */ + const CACHE_USAGE_READ_ONLY = 1; + + /** + * Nonstrict Read Write Cache doesn’t employ any locks but can do reads, inserts and deletes. + */ + const CACHE_USAGE_NONSTRICT_READ_WRITE = 2; + + /** + * Read Write cache employs locks the entity before insert/update/delete. + */ + const CACHE_USAGE_READ_WRITE = 3; + /** * READ-ONLY: The name of the entity class. * @@ -577,6 +591,11 @@ class ClassMetadataInfo implements ClassMetadata */ public $versionField; + /** + * @var array + */ + public $cache; + /** * The ReflectionClass instance of the mapped class. * @@ -979,6 +998,50 @@ public function getReflectionClass() return $this->reflClass; } + /** + * @param array $cache + * + * @return void + */ + public function enableCache(array $cache) + { + if ( ! isset($cache['usage'])) { + $cache['usage'] = self::CACHE_USAGE_READ_ONLY; + } + + if ( ! isset($cache['region'])) { + $cache['region'] = str_replace('\\', '', $this->rootEntityName); + } + + if ( ! isset($cache['properties'])) { + $cache['properties'] = array(); + } + + $this->cache = $cache; + } + + /** + * @param array $cache + * + * @return void + */ + public function enableAssociationCache($fieldName, array $cache) + { + if ( ! isset($cache['usage'])) { + $cache['usage'] = self::CACHE_USAGE_READ_ONLY; + } + + if ( ! isset($cache['region'])) { + $cache['region'] = str_replace('\\', '', $this->rootEntityName) . '::' . $fieldName; + } + + if ( ! isset($cache['properties'])) { + $cache['properties'] = array(); + } + + $this->associationMappings[$fieldName]['cache'] = $cache; + } + /** * Sets the change tracking policy used by this class. * diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index f9aaddba709..3c029334f0a 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -128,6 +128,17 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $metadata->setPrimaryTable($primaryTable); } + // Evaluate @Cache annotation + if (isset($classAnnotations['Doctrine\ORM\Mapping\Cache'])) { + $cacheAnnot = $classAnnotations['Doctrine\ORM\Mapping\Cache']; + + $metadata->enableCache(array( + 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage), + 'properties' => $cacheAnnot->properties, + 'region' => $cacheAnnot->region, + )); + } + // Evaluate NamedNativeQueries annotation if (isset($classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries'])) { $namedNativeQueriesAnnot = $classAnnotations['Doctrine\ORM\Mapping\NamedNativeQueries']; @@ -365,6 +376,15 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $metadata->mapManyToMany($mapping); } + + // Evaluate @Cache annotation + if (($cacheAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Cache')) !== null) { + $metadata->enableAssociationCache($mapping['fieldName'], array( + 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage), + 'properties' => $cacheAnnot->properties, + 'region' => $cacheAnnot->region, + )); + } } // Evaluate AssociationOverrides annotation diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index 14abadb9e4d..bef12522b9d 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -64,4 +64,5 @@ require_once __DIR__.'/../AssociationOverrides.php'; require_once __DIR__.'/../AttributeOverride.php'; require_once __DIR__.'/../AttributeOverrides.php'; -require_once __DIR__.'/../EntityListeners.php'; \ No newline at end of file +require_once __DIR__.'/../EntityListeners.php'; +require_once __DIR__.'/../Cache.php'; \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index fdc54aee125..631fb85f74c 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -21,6 +21,9 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\CollectionEntryStructure; /** * Base class for all collection persisters. @@ -59,18 +62,77 @@ abstract class AbstractCollectionPersister */ protected $quoteStrategy; + /** + * @var array + */ + protected $association; + + /** + * @var array + */ + protected $queuedCache = array(); + + /** + * @var boolean + */ + protected $hasCache = false; + + /** + * @var boolean + */ + protected $isTransactionalRegionAccess = false; + + /** + * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\TransactionalRegionAccess + */ + protected $cacheRegionAccess; + + /** + * @var \Doctrine\ORM\Cache\CollectionEntryStructure + */ + protected $cacheEntryStructure; + /** * Initializes a new instance of a class derived from AbstractCollectionPersister. * * @param \Doctrine\ORM\EntityManager $em + * @param array $association */ - public function __construct(EntityManager $em) + public function __construct(EntityManager $em, array $association) { $this->em = $em; + $this->association = $association; $this->uow = $em->getUnitOfWork(); $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + $this->hasCache = isset($association['cache']) && $em->getConfiguration()->isSecondLevelCacheEnabled(); + + if ($this->hasCache) { + $sourceClass = $em->getClassMetadata($association['sourceEntity']); + $this->cacheRegionAccess = $em->getConfiguration() + ->getSecondLevelCacheAccessProvider() + ->buildCollectioRegionAccessStrategy($sourceClass, $association['fieldName']); + + $this->cacheEntryStructure = new CollectionEntryStructure($em); + $this->isTransactionalRegionAccess = ($this->cacheRegionAccess instanceof TransactionalRegionAccess); + } + } + + /** + * @return boolean + */ + public function hasCache() + { + return $this->hasCache; + } + + /** + * @return \Doctrine\ORM\Cache\RegionAccess + */ + public function getCacheRegionAcess() + { + return $this->cacheRegionAccess; } /** @@ -166,6 +228,49 @@ public function insertRows(PersistentCollection $coll) } } + /** + * @param \Doctrine\ORM\PersistentCollection $collection + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * + * @return \Doctrine\ORM\PersistentCollection|null + */ + public function loadCachedCollection(PersistentCollection $collection, CollectionCacheKey $key) + { + $metadata = $collection->getTypeClass(); + $cache = $this->cacheRegionAccess->get($key); + + if ($cache === null) { + return null; + } + + return $this->cacheEntryStructure->loadCacheEntry($metadata, $key, $cache, $collection); + } + + /** + * @param \Doctrine\ORM\PersistentCollection $collection + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * + * @return void + */ + public function saveLoadedCollection(PersistentCollection $collection, CollectionCacheKey $key, array $list) + { + $metadata = $collection->getTypeClass(); + $targetClass = $this->association['targetEntity']; + $targetPersister = $this->uow->getEntityPersister($targetClass); + $targetRegionAcess = $targetPersister->getCacheRegionAcess(); + $listData = $this->cacheEntryStructure->buildCacheEntry($metadata, $key, $list); + + foreach ($listData as $index => $identifier) { + $entity = $list[$index]; + $entityKey = new EntityCacheKey($identifier, $targetClass); + $entityEntry = $targetPersister->getCacheEntryStructure()->buildCacheEntry($metadata, $entityKey, $entity); + + $targetRegionAcess->put($entityKey, $entityEntry); + } + + $this->cacheRegionAccess->put($key, $listData); + } + /** * Counts the size of this persistent collection. * diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index c594f3bbf64..baeb9e7b295 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -36,6 +36,11 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\EntityEntryStructure; +use Doctrine\ORM\Cache\TransactionalRegionAccess; + /** * A BasicEntityPersister maps an entity to a single table in a relational database. * @@ -206,6 +211,31 @@ class BasicEntityPersister */ protected $quoteStrategy; + /** + * @var array + */ + protected $queuedCache = array(); + + /** + * @var boolean + */ + protected $hasCache = false; + + /** + * @var boolean + */ + protected $isTransactionalRegionAccess = false; + + /** + * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\TransactionalRegionAccess + */ + protected $cacheRegionAccess; + + /** + * @var \Doctrine\ORM\Cache\EntityEntryStructure + */ + protected $cacheEntryStructure; + /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. @@ -220,6 +250,16 @@ public function __construct(EntityManager $em, ClassMetadata $class) $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + $this->hasCache = ($class->cache !== null) && $em->getConfiguration()->isSecondLevelCacheEnabled(); + + if ($this->hasCache) { + $this->cacheRegionAccess = $em->getConfiguration() + ->getSecondLevelCacheAccessProvider() + ->buildEntityRegionAccessStrategy($this->class); + + $this->cacheEntryStructure = new EntityEntryStructure($em); + $this->isTransactionalRegionAccess = ($this->cacheRegionAccess instanceof TransactionalRegionAccess); + } } /** @@ -230,6 +270,30 @@ public function getClassMetadata() return $this->class; } + /** + * @return boolean + */ + public function hasCache() + { + return $this->hasCache; + } + + /** + * @return \Doctrine\ORM\Cache\RegionAccess + */ + public function getCacheRegionAcess() + { + return $this->cacheRegionAccess; + } + + /** + * @return \Doctrine\ORM\Cache\EntityEntryStructure + */ + public function getCacheEntryStructure() + { + return $this->cacheEntryStructure; + } + /** * Adds an entity to the queued insertions. * The entity remains queued until {@link executeInserts} is invoked. @@ -288,6 +352,14 @@ public function executeInserts() if ($this->class->isVersioned) { $this->assignDefaultVersionValue($entity, $id); } + + if ($this->hasCache) { + $this->queuedCache['insert'][] = array( + 'entity' => $entity, + 'lock' => null, + 'key' => null + ); + } } $stmt->closeCursor(); @@ -356,6 +428,14 @@ protected function fetchVersionValue($versionedClass, $id) */ public function update($entity) { + $cacheKey = null; + $cacheLock = null; + + if ($this->isTransactionalRegionAccess) { + $cacheKey = new EntityCacheKey($this->em->getUnitOfWork()->getEntityIdentifier($entity), $this->class->rootEntityName); + $cacheLock = $this->cacheRegionAccess->lockItem(); + } + $tableName = $this->class->getTableName(); $updateData = $this->prepareUpdateData($entity); @@ -373,6 +453,14 @@ public function update($entity) $this->assignDefaultVersionValue($entity, $id); } + + if ($this->hasCache) { + $this->queuedCache['update'][] = array( + 'entity' => $entity, + 'lock' => $cacheLock, + 'key' => $cacheKey + ); + } } /** @@ -562,15 +650,13 @@ protected function deleteJoinTableRecords($identifier) */ public function delete($entity) { - $class = $this->class; $em = $this->em; - + $class = $this->class; $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $tableName = $this->quoteStrategy->getTableName($class, $this->platform); $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); $id = array_combine($idColumns, $identifier); $types = array_map(function ($identifier) use ($class, $em) { - if (isset($class->fieldMappings[$identifier])) { return $class->fieldMappings[$identifier]['type']; } @@ -589,8 +675,27 @@ public function delete($entity) }, $class->identifier); + $cacheKey = null; + $cacheLock = null; + + if ($this->hasCache) { + $cacheKey = new EntityCacheKey($identifier, $this->class->rootEntityName); + } + + if ($this->isTransactionalRegionAccess) { + $cacheLock = $this->cacheRegionAccess->lockItem(); + } + $this->deleteJoinTableRecords($identifier); $this->conn->delete($tableName, $id, $types); + + if ($this->hasCache) { + $this->queuedCache['delete'][] = array( + 'lock' => $cacheLock, + 'key' => $cacheKey, + 'entity' => null, + ); + } } /** @@ -756,8 +861,42 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint $hydrator = $this->em->newHydrator($this->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->rsm, $hints); + $entity = $entities ? $entities[0] : null; + + return $entity; + } - return $entities ? $entities[0] : null; + /** + * Loads an entity by identifier. + * + * @param array $identifier The entity identifier. + * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. + * @param int $lockMode + * + * @return object The loaded and managed entity instance or NULL if the entity can not be found. + * + * @todo Check parameters + */ + public function loadById(array $identifier, $entity = null, $lockMode = 0) + { + $cacheKey = null; + + if ($this->hasCache) { + $cacheKey = new EntityCacheKey($identifier, $this->class->rootEntityName); + $cacheEntry = $this->cacheRegionAccess->get($cacheKey); + + if ($cacheEntry !== null) { + return $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity); + } + } + + $entity = $this->load($identifier, $entity, null, array(), $lockMode); + + if ($this->hasCache && $entity !== null) { + $this->cacheRegionAccess->put($cacheKey, $this->cacheEntryStructure->buildCacheEntry($this->class, $cacheKey, $entity)); + } + + return $entity; } /** @@ -1734,9 +1873,28 @@ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = nu */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { + $key = null; + $collPersister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); + $hasCache = $collPersister->hasCache(); + + if ($hasCache) { + $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($this->class->name, $assoc['fieldName'], $ownerId); + $list = $collPersister->loadCachedCollection($coll, $key); + + if ($list !== null) { + return $list; + } + } + $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); + $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); - return $this->loadCollectionFromStatement($assoc, $stmt, $coll); + if ($hasCache && ! empty($list)) { + $collPersister->saveLoadedCollection($coll, $key, $list); + } + + return $list; } /** @@ -1976,4 +2134,63 @@ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targ $sql = implode(' AND ', $filterClauses); return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" } -} \ No newline at end of file + + /** + * @param boolean $wasCommitted + */ + public function afterTransactionComplete($wasCommitted) + { + if ( ! $wasCommitted) { + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + $this->queuedCache = array(); + + return; + } + + $uow = $this->em->getUnitOfWork(); + + if (isset($this->queuedCache['insert'])) { + foreach ($this->queuedCache['insert'] as $item) { + + $key = new EntityCacheKey($uow->getEntityIdentifier($item['entity']), $this->class->rootEntityName); + $entry = $this->cacheEntryStructure->buildCacheEntry($this->class, $key, $item['entity']); + + $this->cacheRegionAccess->afterInsert($key, $entry); + } + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + + $key = $item['key'] ?: new EntityCacheKey($uow->getEntityIdentifier($item['entity']), $this->class->rootEntityName); + $entry = $this->cacheEntryStructure->buildCacheEntry($this->class, $key, $item['entity']); + + $this->cacheRegionAccess->afterUpdate($key, $entry); + + if ($this->isTransactionalRegionAccess && $item['lock'] !== null) { + $this->cacheRegionAccess->unlockItem($key, $item['lock']); + } + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->cacheRegionAccess->evict($item['key']); + } + } + + $this->queuedCache = array();; + } +} diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index dfac9a3e791..363db8f98c3 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -130,7 +130,7 @@ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPers $properties = $proxy->__getLazyProperties(); foreach ($properties as $propertyName => $property) { - if (!isset($proxy->$propertyName)) { + if ( ! isset($proxy->$propertyName)) { $proxy->$propertyName = $properties[$propertyName]; } } @@ -138,7 +138,7 @@ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPers $proxy->__setInitialized(true); $proxy->__wakeup(); - if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) { + if (null === $entityPersister->loadById($classMetadata->getIdentifierValues($proxy), $proxy)) { $proxy->__setInitializer($initializer); $proxy->__setCloner($cloner); $proxy->__setInitialized(false); @@ -169,7 +169,7 @@ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPers $proxy->__setInitialized(true); - if (null === $entityPersister->load($classMetadata->getIdentifierValues($proxy), $proxy)) { + if (null === $entityPersister->loadById($classMetadata->getIdentifierValues($proxy), $proxy)) { $proxy->__setInitializer($initializer); $proxy->__setCloner($cloner); $proxy->__setInitialized(false); @@ -198,20 +198,21 @@ private function createCloner(ClassMetadata $classMetadata, BasicEntityPersister $proxy->__setInitialized(true); $proxy->__setInitializer(null); - $class = $entityPersister->getClassMetadata(); - $original = $entityPersister->load($classMetadata->getIdentifierValues($proxy)); + + $class = $entityPersister->getClassMetadata(); + $original = $entityPersister->loadById($classMetadata->getIdentifierValues($proxy)); if (null === $original) { throw new EntityNotFoundException(); } - foreach ($class->getReflectionClass()->getProperties() as $reflectionProperty) { - $propertyName = $reflectionProperty->getName(); - - if ($class->hasField($propertyName) || $class->hasAssociation($propertyName)) { - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($proxy, $reflectionProperty->getValue($original)); + foreach ($class->getReflectionClass()->getProperties() as $property) { + if ( ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) { + continue; } + + $property->setAccessible(true); + $property->setValue($proxy, $property->getValue($original)); } }; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 5de769f6381..7f5449e0e09 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -38,6 +38,13 @@ use Doctrine\ORM\Event\PostFlushEventArgs; use Doctrine\ORM\Event\ListenersInvoker; +use Doctrine\ORM\Persisters\BasicEntityPersister; +use Doctrine\ORM\Persisters\SingleTablePersister; +use Doctrine\ORM\Persisters\JoinedSubclassPersister; +use Doctrine\ORM\Persisters\UnionSubclassPersister; +use Doctrine\ORM\Persisters\OneToManyPersister; +use Doctrine\ORM\Persisters\ManyToManyPersister; + /** * The UnitOfWork is responsible for tracking changes to objects during an * "object-level" transaction and for writing out changes to the database @@ -254,6 +261,11 @@ class UnitOfWork implements PropertyChangedListener */ private $eagerLoadingEntities = array(); + /** + * @var array + */ + private $cachedPersisters = array(); + /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. * @@ -368,6 +380,10 @@ public function commit($entity = null) $this->em->close(); $conn->rollback(); + foreach ($this->cachedPersisters as $persister) { + $persister->afterTransactionComplete(false); + } + throw $e; } @@ -378,6 +394,10 @@ public function commit($entity = null) $this->dispatchPostFlushEvent(); + foreach ($this->cachedPersisters as $persister) { + $persister->afterTransactionComplete(true); + } + // Clear up $this->entityInsertions = $this->entityUpdates = @@ -388,6 +408,7 @@ public function commit($entity = null) $this->collectionDeletions = $this->visitedCollections = $this->scheduledForDirtyCheck = + $this->cachedPersisters = $this->orphanRemovals = array(); } @@ -970,6 +991,10 @@ private function executeInserts($class) foreach ($entities as $entity) { $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } + + if ($persister->hasCache()) { + $this->cachedPersisters[$className] = $persister; + } } /** @@ -1005,6 +1030,10 @@ private function executeUpdates($class) if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke); } + + if ($persister->hasCache()) { + $this->cachedPersisters[$className] = $persister; + } } } @@ -1045,6 +1074,10 @@ private function executeDeletions($class) if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } + + if ($persister->hasCache()) { + $this->cachedPersisters[$className] = $persister; + } } } @@ -2952,15 +2985,15 @@ public function getEntityPersister($entityName) switch (true) { case ($class->isInheritanceTypeNone()): - $persister = new Persisters\BasicEntityPersister($this->em, $class); + $persister = new BasicEntityPersister($this->em, $class); break; case ($class->isInheritanceTypeSingleTable()): - $persister = new Persisters\SingleTablePersister($this->em, $class); + $persister = new SingleTablePersister($this->em, $class); break; - + case ($class->isInheritanceTypeJoined()): - $persister = new Persisters\JoinedSubclassPersister($this->em, $class); + $persister = new JoinedSubclassPersister($this->em, $class); break; default: @@ -2981,25 +3014,19 @@ public function getEntityPersister($entityName) */ public function getCollectionPersister(array $association) { - $type = $association['type']; + $role = $association['sourceEntity'] . '::' . $association['fieldName']; - if (isset($this->collectionPersisters[$type])) { - return $this->collectionPersisters[$type]; + if (isset($this->collectionPersisters[$role])) { + return $this->collectionPersisters[$role]; } - switch ($type) { - case ClassMetadata::ONE_TO_MANY: - $persister = new Persisters\OneToManyPersister($this->em); - break; - - case ClassMetadata::MANY_TO_MANY: - $persister = new Persisters\ManyToManyPersister($this->em); - break; - } + $persister = ClassMetadata::ONE_TO_MANY === $association['type'] + ? new OneToManyPersister($this->em, $association) + : new ManyToManyPersister($this->em, $association); - $this->collectionPersisters[$type] = $persister; + $this->collectionPersisters[$role] = $persister; - return $this->collectionPersisters[$type]; + return $this->collectionPersisters[$role]; } /** diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php new file mode 100644 index 00000000000..7c2523c2a25 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -0,0 +1,66 @@ +name = $name; + $this->state = $state; + } + + public function getId() + { + return $this->id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getState() + { + return $this->state; + } + + public function setState(State $state) + { + $this->state = $state; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/Country.php b/tests/Doctrine/Tests/Models/Cache/Country.php new file mode 100644 index 00000000000..e0aba68323d --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/Country.php @@ -0,0 +1,48 @@ +name = $name; + } + + public function getId() + { + return $this->id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/State.php b/tests/Doctrine/Tests/Models/Cache/State.php new file mode 100644 index 00000000000..0456ba6a2f6 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/State.php @@ -0,0 +1,90 @@ +name = $name; + $this->country = $country; + } + + public function getId() + { + return $this->id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getCountry() + { + return $this->country; + } + + public function setCountry(Country $country) + { + $this->country = $country; + } + + public function getCities() + { + return $this->cities; + } + + public function setCities(ArrayCollection $cities) + { + $this->cities = $cities; + } + + public function addCity(City $city) + { + $this->cities[] = $city; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php new file mode 100644 index 00000000000..ebb5a53cd14 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php @@ -0,0 +1,68 @@ +region = new DefaultRegion('DoctrineTestsModelsCacheCountry', new ArrayCache()); + $this->regionAccess = $this->createRegionAccess(); + } + + protected function createRegionAccess(Region $region = null) + { + return new ReadOnlyRegionAccess($region ?: $this->region); + } + + public function testGetRegion() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->regionAccess->getRegion()); + } + + static public function dataPutAndGet() + { + $entityName = '\Doctrine\Tests\Models\Cache\Country'; + + return array( + array(new EntityCacheKey(array('id'=>1), $entityName), array('id'=>1, 'name' => 'bar')), + array(new EntityCacheKey(array('id'=>2), $entityName), array('id'=>2, 'name' => 'foo')), + ); + } + + /** + * @dataProvider dataPutAndGet + */ + public function testPutAndGet($key, $value) + { + $this->regionAccess->put($key, $value); + + $actual = $this->regionAccess->get($key); + + $this->assertEquals($value, $actual); + } + +} diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php new file mode 100644 index 00000000000..6aca74caed8 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -0,0 +1,366 @@ +enableSecondLevelCache(); + + $this->useModelSet('cache'); + + parent::setUp(); + } + + private function loadFixturesCountries() + { + $brazil = new Country("Brazil"); + $germany = new Country("Germany"); + + $this->countries[] = $brazil; + $this->countries[] = $germany; + + $this->_em->persist($brazil); + $this->_em->persist($germany); + $this->_em->flush(); + } + + private function loadFixturesStates() + { + $saopaulo = new State("São Paulo", $this->countries[0]); + $rio = new State("Rio de janeiro", $this->countries[0]); + $berlin = new State("Berlin", $this->countries[1]); + $bavaria = new State("Bavaria", $this->countries[1]); + + $this->states[] = $saopaulo; + $this->states[] = $rio; + $this->states[] = $bavaria; + $this->states[] = $berlin; + + $this->_em->persist($saopaulo); + $this->_em->persist($rio); + $this->_em->persist($bavaria); + $this->_em->persist($berlin); + + $this->_em->flush(); + } + + private function loadFixturesCities() + { + $saopaulo = new City("São Paulo", $this->states[0]); + $rio = new City("Rio de janeiro", $this->states[0]); + $berlin = new City("Berlin", $this->states[1]); + $munich = new City("Munich", $this->states[1]); + + $this->cities[] = $saopaulo; + $this->cities[] = $rio; + $this->cities[] = $munich; + $this->cities[] = $berlin; + + $this->_em->persist($saopaulo); + $this->_em->persist($rio); + $this->_em->persist($munich); + $this->_em->persist($berlin); + + $this->_em->flush(); + } + + private function getQueryCount() + { + return count($this->_sqlLoggerStack->queries); + } + + public function testPutAndLoadEntities() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $countryClass = 'Doctrine\Tests\Models\Cache\Country'; + $countryMetadata = $this->_em->getClassMetadata($countryClass); + $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($countryMetadata); + $region = $cacheAccess->getRegion(); + + $cacheAccess->evictAll(); + + $this->assertCount(0, $region); + + $c1 = $this->_em->find($countryClass, $this->countries[0]->getId()); + $c2 = $this->_em->find($countryClass, $this->countries[1]->getId()); + + $this->assertCount(2, $region); + + $this->assertInstanceOf($countryClass, $c1); + $this->assertInstanceOf($countryClass, $c2); + + $this->assertEquals($this->countries[0]->getId(), $c1->getId()); + $this->assertEquals($this->countries[0]->getName(), $c1->getName()); + + $this->assertEquals($this->countries[1]->getId(), $c2->getId()); + $this->assertEquals($this->countries[1]->getName(), $c2->getName()); + + $this->_em->clear(); + + $queryCount = $this->getQueryCount(); + + $c3 = $this->_em->find($countryClass, $this->countries[0]->getId()); + $c4 = $this->_em->find($countryClass, $this->countries[1]->getId()); + + $this->assertCount(2, $region); + $this->assertEquals($queryCount, $this->getQueryCount()); + + $this->assertInstanceOf($countryClass, $c3); + $this->assertInstanceOf($countryClass, $c4); + + $this->assertEquals($c1->getId(), $c3->getId()); + $this->assertEquals($c1->getName(), $c3->getName()); + + $this->assertEquals($c2->getId(), $c4->getId()); + $this->assertEquals($c2->getName(), $c4->getName()); + } + + public function testRemoveEntities() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $countryClass = 'Doctrine\Tests\Models\Cache\Country'; + $countryMetadata = $this->_em->getClassMetadata($countryClass); + $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($countryMetadata); + $region = $cacheAccess->getRegion(); + + $cacheAccess->evictAll(); + + $this->assertCount(0, $region); + + $c1 = $this->_em->find($countryClass, $this->countries[0]->getId()); + $c2 = $this->_em->find($countryClass, $this->countries[1]->getId()); + + $this->assertCount(2, $region); + + $this->assertInstanceOf($countryClass, $c1); + $this->assertInstanceOf($countryClass, $c2); + + $this->assertEquals($this->countries[0]->getId(), $c1->getId()); + $this->assertEquals($this->countries[0]->getName(), $c1->getName()); + + $this->assertEquals($this->countries[1]->getId(), $c2->getId()); + $this->assertEquals($this->countries[1]->getName(), $c2->getName()); + + $this->_em->remove($c1); + $this->_em->remove($c2); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertCount(0, $region); + $this->assertNull($this->_em->find($countryClass, $this->countries[0]->getId())); + $this->assertNull($this->_em->find($countryClass, $this->countries[1]->getId())); + } + + public function testUpdateEntities() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->_em->clear(); + + $stateClass = 'Doctrine\Tests\Models\Cache\State'; + $stateMetadata = $this->_em->getClassMetadata($stateClass); + $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($stateMetadata); + $region = $cacheAccess->getRegion(); + + $cacheAccess->evictAll(); + + $this->assertCount(0, $region); + + $s1 = $this->_em->find($stateClass, $this->states[0]->getId()); + $s2 = $this->_em->find($stateClass, $this->states[1]->getId()); + + $this->assertCount(2, $region); + + $this->assertInstanceOf($stateClass, $s1); + $this->assertInstanceOf($stateClass, $s2); + + $this->assertEquals($this->states[0]->getId(), $s1->getId()); + $this->assertEquals($this->states[0]->getName(), $s1->getName()); + + $this->assertEquals($this->states[1]->getId(), $s2->getId()); + $this->assertEquals($this->states[1]->getName(), $s2->getName()); + + $s1->setName("NEW NAME 1"); + $s2->setName("NEW NAME 2"); + + $this->_em->persist($s1); + $this->_em->persist($s2); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertCount(2, $region); + $queryCount = $this->getQueryCount(); + + $c3 = $this->_em->find($stateClass, $this->states[0]->getId()); + $c4 = $this->_em->find($stateClass, $this->states[1]->getId()); + + $this->assertCount(2, $region); + $this->assertEquals($queryCount, $this->getQueryCount()); + + $this->assertInstanceOf($stateClass, $c3); + $this->assertInstanceOf($stateClass, $c4); + + $this->assertEquals($s1->getId(), $c3->getId()); + $this->assertEquals("NEW NAME 1", $c3->getName()); + + $this->assertEquals($s2->getId(), $c4->getId()); + $this->assertEquals("NEW NAME 2", $c4->getName()); + } + + public function testPutAndLoadManyToOneRelation() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->_em->clear(); + + $stateClass = 'Doctrine\Tests\Models\Cache\State'; + $countryClass = 'Doctrine\Tests\Models\Cache\Country'; + $stateMetadata = $this->_em->getClassMetadata($stateClass); + $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($stateMetadata); + $region = $cacheAccess->getRegion(); + + $cacheAccess->evictAll(); + + $this->assertCount(0, $region); + + $c1 = $this->_em->find($stateClass, $this->states[0]->getId()); + $c2 = $this->_em->find($stateClass, $this->states[1]->getId()); + + $this->assertCount(2, $region); + + $this->assertInstanceOf($stateClass, $c1); + $this->assertInstanceOf($stateClass, $c2); + $this->assertInstanceOf($countryClass, $c1->getCountry()); + $this->assertInstanceOf($countryClass, $c2->getCountry()); + + $this->assertEquals($this->states[0]->getId(), $c1->getId()); + $this->assertEquals($this->states[0]->getName(), $c1->getName()); + $this->assertEquals($this->states[0]->getCountry()->getId(), $c1->getCountry()->getId()); + $this->assertEquals($this->states[0]->getCountry()->getName(), $c1->getCountry()->getName()); + + $this->assertEquals($this->states[1]->getId(), $c2->getId()); + $this->assertEquals($this->states[1]->getName(), $c2->getName()); + $this->assertEquals($this->states[1]->getCountry()->getId(), $c2->getCountry()->getId()); + $this->assertEquals($this->states[1]->getCountry()->getName(), $c2->getCountry()->getName()); + + $this->_em->clear(); + + $queryCount = $this->getQueryCount(); + + $c3 = $this->_em->find($stateClass, $this->states[0]->getId()); + $c4 = $this->_em->find($stateClass, $this->states[1]->getId()); + + $this->assertCount(2, $region); + $this->assertEquals($queryCount, $this->getQueryCount()); + + $this->assertInstanceOf($stateClass, $c3); + $this->assertInstanceOf($stateClass, $c4); + $this->assertInstanceOf($countryClass, $c3->getCountry()); + $this->assertInstanceOf($countryClass, $c4->getCountry()); + + $this->assertEquals($c1->getId(), $c3->getId()); + $this->assertEquals($c1->getName(), $c3->getName()); + + $this->assertEquals($c2->getId(), $c4->getId()); + $this->assertEquals($c2->getName(), $c4->getName()); + + $this->assertEquals($this->states[0]->getCountry()->getId(), $c3->getCountry()->getId()); + $this->assertEquals($this->states[0]->getCountry()->getName(), $c3->getCountry()->getName()); + + $this->assertEquals($this->states[1]->getCountry()->getId(), $c4->getCountry()->getId()); + $this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName()); + } + + public function testPutAndLoadOneToManyRelation() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + + $targetMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Cache\City'); + $entityMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Cache\State'); + + $collCacheAccess = $this->_em->getCache()->getCollectionCacheRegionAcess($entityMetadata, 'cities'); + $entityCacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($entityMetadata); + $targetCacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($targetMetadata); + + $collRegion = $collCacheAccess->getRegion(); + $entityRegion = $entityCacheAccess->getRegion(); + $targetRegion = $targetCacheAccess->getRegion(); + + $collRegion->evictAll(); + $targetRegion->evictAll(); + $entityRegion->evictAll(); + + $this->assertCount(0, $targetRegion); + $this->assertCount(0, $entityRegion); + $this->assertCount(0, $collRegion); + + $c1 = $this->_em->find($entityMetadata->name, $this->states[0]->getId()); + $c2 = $this->_em->find($entityMetadata->name, $this->states[1]->getId()); + + //trigger lazy load + $this->assertCount(2, $c1->getCities()); + $this->assertCount(2, $c2->getCities()); + + $this->assertInstanceOf($targetMetadata->name, $c1->getCities()->get(0)); + $this->assertInstanceOf($targetMetadata->name, $c1->getCities()->get(1)); + + $this->assertInstanceOf($targetMetadata->name, $c2->getCities()->get(0)); + $this->assertInstanceOf($targetMetadata->name, $c2->getCities()->get(1)); + + $this->assertCount(4, $targetRegion); + $this->assertCount(2, $entityRegion); + $this->assertCount(2, $collRegion); + + $this->_em->clear(); + + $queryCount = $this->getQueryCount(); + + $c3 = $this->_em->find($entityMetadata->name, $this->states[0]->getId()); + $c4 = $this->_em->find($entityMetadata->name, $this->states[1]->getId()); + + //trigger lazy load from cache + $this->assertCount(2, $c3->getCities()); + $this->assertCount(2, $c4->getCities()); + + $this->assertInstanceOf($targetMetadata->name, $c3->getCities()->get(0)); + $this->assertInstanceOf($targetMetadata->name, $c3->getCities()->get(1)); + $this->assertInstanceOf($targetMetadata->name, $c4->getCities()->get(0)); + $this->assertInstanceOf($targetMetadata->name, $c4->getCities()->get(1)); + + $this->assertEquals($c1->getCities()->get(0)->getId(), $c3->getCities()->get(0)->getId()); + $this->assertEquals($c1->getCities()->get(0)->getName(), $c3->getCities()->get(0)->getName()); + + $this->assertEquals($c2->getCities()->get(1)->getId(), $c4->getCities()->get(1)->getId()); + $this->assertEquals($c2->getCities()->get(1)->getName(), $c4->getCities()->get(1)->getName()); + + $this->assertCount(4, $targetRegion); + $this->assertCount(2, $entityRegion); + $this->assertCount(2, $collRegion); + + $this->assertEquals($queryCount, $this->getQueryCount()); + } +} + diff --git a/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php new file mode 100644 index 00000000000..fe1fcc37ec4 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php @@ -0,0 +1,86 @@ +_em->persist($country); + + $countries[] = $country; + } + + $this->_em->flush(); + + printf("NOCACHE - persist %s countries %s\n", number_format(microtime(true) - $startPersist, 6), $size); + + $this->_em->clear(); + + $queryCount = count($this->_sqlLoggerStack->queries); + $startFind = microtime(true); + + foreach ($countries as $country) { + $this->_em->find('Doctrine\Tests\Models\Cache\Country', $country->getId()); + } + + $this->assertEquals($queryCount + $size, count($this->_sqlLoggerStack->queries)); + printf("NOCACHE - find %s countries %s\n", number_format(microtime(true) - $startFind, 6), $size); + } + + public function testFindCountryWithCache() + { + parent::enableSecondLevelCache(); + parent::useModelSet('cache'); + parent::setUp(); + + $size = 500; + $countries = array(); + $startPersist = microtime(true); + + for ($i = 0; $i < $size; $i++) { + $country = new Country("Country $i"); + + $this->_em->persist($country); + + $countries[] = $country; + } + + $this->_em->flush(); + + printf("CACHE - persist %s countries %s\n", number_format(microtime(true) - $startPersist, 6), $size); + + $this->_em->clear(); + + $queryCount = count($this->_sqlLoggerStack->queries); + $startFind = microtime(true); + + foreach ($countries as $country) { + $this->_em->find('Doctrine\Tests\Models\Cache\Country', $country->getId()); + } + + $this->assertEquals($queryCount, count($this->_sqlLoggerStack->queries)); + printf("CACHE - find %s countries %s\n", number_format(microtime(true) - $startFind, 6), $size); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index d2a41cfb968..6b7a7232e37 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -2,6 +2,9 @@ namespace Doctrine\Tests; +use Doctrine\Common\Cache\Cache; +use Doctrine\ORM\EntityManager; + /** * Base testcase class for all functional ORM testcases. * @@ -9,6 +12,16 @@ */ abstract class OrmFunctionalTestCase extends OrmTestCase { + /** + * @var boolean + */ + private $isSecondLevelCacheEnabled = false; + + /** + * @var \Doctrine\Common\Cache\CacheProvider + */ + private $secondLevelCacheAccessProvider; + /** * The metadata cache shared between all functional tests. * @@ -162,6 +175,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\Taxi\Car', 'Doctrine\Tests\Models\Taxi\Driver', ), + 'cache' => array( + 'Doctrine\Tests\Models\Cache\Country', + 'Doctrine\Tests\Models\Cache\State', + 'Doctrine\Tests\Models\Cache\City', + ), ); /** @@ -297,6 +315,12 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM taxi_driver'); } + if (isset($this->_usedModelSets['cache_country'])) { + $conn->executeUpdate('DELETE FROM cache_country'); + $conn->executeUpdate('DELETE FROM cache_state'); + $conn->executeUpdate('DELETE FROM cache_city'); + } + $this->_em->clear(); } @@ -411,6 +435,11 @@ protected function _getEntityManager($config = null, $eventManager = null) { $config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies'); + if ($this->isSecondLevelCacheEnabled) { + $config->setSecondLevelCacheEnabled(); + $config->setSecondLevelCacheAccessProvider($this->secondLevelCacheAccessProvider); + } + $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array(), true)); $conn = static::$_sharedConn; @@ -438,6 +467,19 @@ protected function _getEntityManager($config = null, $eventManager = null) { return \Doctrine\ORM\EntityManager::create($conn, $config); } + + /** + * @param \Doctrine\Common\Cache\Cache $cache + */ + protected function enableSecondLevelCache(Cache $cache = null) + { + $cache = $cache ?: self::getSharedSecondLevelCacheDriverImpl(); + $provider = new \Doctrine\ORM\Cache\CacheAccessProvider($cache); + + $this->secondLevelCacheAccessProvider = $provider; + $this->isSecondLevelCacheEnabled = true; + } + /** * @param \Exception $e * diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 9ba32cf8bd7..315264693bd 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -9,6 +9,11 @@ */ abstract class OrmTestCase extends DoctrineTestCase { + /** + * @var \Doctrine\Common\Cache\Cache|null + */ + protected static $sharedSecondLevelCacheDriverImpl = null; + /** * The metadata cache that is shared between all ORM tests (except functional tests). * @@ -135,4 +140,16 @@ private static function getSharedQueryCacheImpl() return self::$_queryCacheImpl; } + + /** + * @return \Doctrine\Common\Cache\Cache + */ + protected static function getSharedSecondLevelCacheDriverImpl() + { + if (self::$sharedSecondLevelCacheDriverImpl === null) { + self::$sharedSecondLevelCacheDriverImpl = new \Doctrine\Common\Cache\ArrayCache; + } + + return self::$sharedSecondLevelCacheDriverImpl; + } } From 52d4b88ebcd0e15e0d6765db85e6b81c1d7133a2 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 17 Feb 2013 12:15:08 -0300 Subject: [PATCH 02/71] First round of refactory --- lib/Doctrine/ORM/Cache.php | 130 ++++++---- lib/Doctrine/ORM/Cache/CacheKey.php | 7 +- lib/Doctrine/ORM/Cache/CollectionCacheKey.php | 14 +- .../ORM/Cache/CollectionEntryStructure.php | 4 +- ...ctionalRegion.php => ConcurrentRegion.php} | 4 +- ...nAccess.php => ConcurrentRegionAccess.php} | 6 +- lib/Doctrine/ORM/Cache/EntityCacheKey.php | 17 +- .../ORM/Cache/EntityEntryStructure.php | 20 +- lib/Doctrine/ORM/Cache/Region.php | 4 +- .../ORM/Cache/Region/DefaultRegion.php | 14 +- .../ORM/Mapping/ClassMetadataInfo.php | 8 +- .../Mapping/Driver/DoctrineAnnotations.php | 2 +- .../AbstractCollectionPersister.php | 11 +- .../ORM/Persisters/BasicEntityPersister.php | 86 +++---- lib/Doctrine/ORM/UnitOfWork.php | 4 +- tests/Doctrine/Tests/Models/Cache/City.php | 2 +- tests/Doctrine/Tests/Models/Cache/State.php | 6 +- .../Doctrine/Tests/ORM/Cache/CacheKeyTest.php | 70 ++++++ .../Tests/ORM/Cache/DefaultRegionTest.php | 148 +++++++++++ .../NonStrictReadWriteRegionAccessTest.php | 133 ++++++++++ .../ORM/Cache/ReadOnlyRegionAccessTest.php | 55 +---- .../ORM/Functional/SecondLevelCacheTest.php | 231 ++++++++++-------- .../Doctrine/Tests/OrmFunctionalTestCase.php | 4 +- 23 files changed, 695 insertions(+), 285 deletions(-) rename lib/Doctrine/ORM/Cache/{TransactionalRegion.php => ConcurrentRegion.php} (95%) rename lib/Doctrine/ORM/Cache/{TransactionalRegionAccess.php => ConcurrentRegionAccess.php} (92%) create mode 100644 tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index 4530283f5ab..63f00e41d6a 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -21,9 +21,11 @@ namespace Doctrine\ORM; use Doctrine\ORM\EntityManager; +use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\ORMInvalidArgumentException; /** * Provides an API for querying/managing the second level cache regions. @@ -33,6 +35,7 @@ */ class Cache { + /** * @var \Doctrine\ORM\EntityManager */ @@ -53,12 +56,13 @@ public function __construct(EntityManager $em) } /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * - * @return \Doctrine\ORM\Cache\RegionAccess + * @param string $className The entity class. + * + * @return \Doctrine\ORM\Cache\RegionAccess|null */ - public function getEntityCacheRegionAcess(ClassMetadata $metadata) + public function getEntityCacheRegionAcess($className) { + $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); if ( ! $persister->hasCache()) { @@ -69,13 +73,14 @@ public function getEntityCacheRegionAcess(ClassMetadata $metadata) } /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param string $association The field name that represents the association. + * @param string $className The entity class. + * @param string $association The field name that represents the association. * - * @return \Doctrine\ORM\Cache\RegionAccess + * @return \Doctrine\ORM\Cache\RegionAccess|null */ - public function getCollectionCacheRegionAcess(ClassMetadata $metadata, $association) + public function getCollectionCacheRegionAcess($className, $association) { + $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); if ( ! $persister->hasCache()) { @@ -87,19 +92,17 @@ public function getCollectionCacheRegionAcess(ClassMetadata $metadata, $associat /** * Determine whether the cache contains data for the given entity "instance". - *

- * The semantic here is whether the cache contains data visible for the - * current call context. * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param array $identifier The entity identifier + * @param string $className The entity class. + * @param mixed $identifier The entity identifier * * @return boolean true if the underlying cache contains corresponding data; false otherwise. */ - public function containsEntity(ClassMetadata $metadata, array $identifier) + public function containsEntity($className, $identifier) { - $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - $key = $this->buildEntityCacheKey($metadata, $identifier); + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + $key = $this->buildEntityCacheKey($metadata, $identifier); if ( ! $persister->hasCache()) { return false; @@ -111,11 +114,14 @@ public function containsEntity(ClassMetadata $metadata, array $identifier) /** * Evicts the entity data for a particular entity "instance". * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param mixed $identifier The entity identifier. + * @param string $className The entity class. + * @param mixed $identifier The entity identifier. + * + * @return void */ - public function evictEntity(ClassMetadata $metadata, $identifier) + public function evictEntity($className, $identifier) { + $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); $key = $this->buildEntityCacheKey($metadata, $identifier); @@ -123,41 +129,45 @@ public function evictEntity(ClassMetadata $metadata, $identifier) return; } - return $persister->getCacheRegionAcess()->evict($key); + $persister->getCacheRegionAcess()->evict($key); } /** * Evicts all entity data from the given region. * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $className The entity metadata. + * + * @return void */ - public function evictEntityRegion(ClassMetadata $metadata) + public function evictEntityRegion($className) { + $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); if ( ! $persister->hasCache()) { return; } - return $persister->getCacheRegionAcess()->evictAll(); + $persister->getCacheRegionAcess()->evictAll(); } /** * Determine whether the cache contains data for the given collection. * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param string $association The field name that represents the association. - * @param mixed $ownerIdentifier The identifier of the owning entity. + * @param string $className The entity class. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. * * @return boolean true if the underlying cache contains corresponding data; false otherwise. */ - public function containsCollection(ClassMetadata $metadata, $association, array $ownerIdentifier) + public function containsCollection($className, $association, $ownerIdentifier) { + $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); if ( ! $persister->hasCache()) { - return; + return false; } return $persister->getCacheRegionAcess()->getRegion()->contains($key); @@ -166,12 +176,15 @@ public function containsCollection(ClassMetadata $metadata, $association, array /** * Evicts the cache data for the given identified collection instance. * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param string $association The field name that represents the association. - * @param mixed $ownerIdentifier The identifier of the owning entity. + * @param string $className The entity class. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + * + * @return void */ - public function evictCollection(ClassMetadata $metadata, $association, array $ownerIdentifier) + public function evictCollection($className, $association, $ownerIdentifier) { + $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); @@ -179,24 +192,27 @@ public function evictCollection(ClassMetadata $metadata, $association, array $ow return; } - return $persister->getCacheRegionAcess()->evict($key); + $persister->getCacheRegionAcess()->evict($key); } /** * Evicts all entity data from the given region. * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param string $association The field name that represents the association. + * @param string $className The entity class. + * @param string $association The field name that represents the association. + * + * @return void */ - public function evictCollectionRegion(ClassMetadata $metadata, $association) + public function evictCollectionRegion($className, $association) { + $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); if ( ! $persister->hasCache()) { return; } - return $persister->getCacheRegionAcess()->evictAll(); + $persister->getCacheRegionAcess()->evictAll(); } /** @@ -230,7 +246,7 @@ public function evictQueryRegions() } /** - * Get query cache by region name or create a new one if none exist. + * Get query cache by region name or create a new one if none exist. * * @param regionName Query cache region name. * @@ -243,13 +259,17 @@ public function getQueryCache($regionName) /** * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param array $identifier The entity identifier. + * @param mixed $identifier The entity identifier. * * @return \Doctrine\ORM\Cache\EntityCacheKey */ - public function buildEntityCacheKey(ClassMetadata $metadata, array $identifier) + public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) { - return new EntityCacheKey($identifier, $metadata->rootEntityName); + if ( ! is_array($identifier)) { + $identifier = $this->toIdentifierArray($metadata, $identifier); + } + + return new EntityCacheKey($metadata->rootEntityName, $identifier); } /** @@ -259,8 +279,32 @@ public function buildEntityCacheKey(ClassMetadata $metadata, array $identifier) * * @return \Doctrine\ORM\Cache\CollectionCacheKey */ - public function buildCollectionCacheKey(ClassMetadata $metadata, $association, array $ownerIdentifier) + public function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier) { - return new CollectionCacheKey($ownerIdentifier, $metadata->rootEntityName, $association); + if ( ! is_array($ownerIdentifier)) { + $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);; + } + + return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param mixed $identifier The entity identifier. + * + * @return array + */ + private function toIdentifierArray(ClassMetadata $metadata, $identifier) + { + if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) { + $identifier = $this->unitOfWork->getSingleIdentifierValue($identifier); + + if ($identifier === null) { + throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); + } + } + + return array($metadata->identifier[0] => $identifier); + } + } diff --git a/lib/Doctrine/ORM/Cache/CacheKey.php b/lib/Doctrine/ORM/Cache/CacheKey.php index a607e585d2b..e163ccbccdf 100644 --- a/lib/Doctrine/ORM/Cache/CacheKey.php +++ b/lib/Doctrine/ORM/Cache/CacheKey.php @@ -21,12 +21,17 @@ namespace Doctrine\ORM\Cache; /** - * Defines entity classes / collection key to be stored in the cache region. + * Defines entity / collection key to be stored in the cache region. + * Allows multiple roles to be stored in the same cache region. * * @since 2.5 * @author Fabio B. Silva */ interface CacheKey { + + /** + * @return string Unique identifier + */ public function hash(); } diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheKey.php b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php index 068f926b075..377d881a10e 100644 --- a/lib/Doctrine/ORM/Cache/CollectionCacheKey.php +++ b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php @@ -43,6 +43,11 @@ class CollectionCacheKey implements CacheKey */ public $association; + /** + * @var string + */ + private $hash; + /** * @param string $entityClass The entity class. * @param string $association The field name that represents the association. @@ -60,6 +65,13 @@ public function __construct($entityClass, $association, array $ownerIdentifier) */ public function hash() { - return hash('sha512', $this->entityClass . $this->association . serialize($this->ownerIdentifier)); + if ($this->hash === null) { + + ksort($this->ownerIdentifier); + + $this->hash = sprintf('%s[%s].%s', str_replace('\\', '.', strtolower($this->entityClass)), implode(' ', $this->ownerIdentifier), $this->association); + } + + return $this->hash; } } diff --git a/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php index c790166a62a..6ac5d61fd89 100644 --- a/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php @@ -48,8 +48,8 @@ class CollectionEntryStructure */ public function __construct(EntityManager $em) { - $this->em = $em; - $this->uow = $em->getUnitOfWork(); + $this->em = $em; + $this->uow = $em->getUnitOfWork(); } /** diff --git a/lib/Doctrine/ORM/Cache/TransactionalRegion.php b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php similarity index 95% rename from lib/Doctrine/ORM/Cache/TransactionalRegion.php rename to lib/Doctrine/ORM/Cache/ConcurrentRegion.php index e1d3f6f6a15..bd5eba9223f 100644 --- a/lib/Doctrine/ORM/Cache/TransactionalRegion.php +++ b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php @@ -21,12 +21,12 @@ namespace Doctrine\ORM\Cache; /** - * Defines contract for regions which hold transactionally-managed data. + * Defines contract for concurrently managed data region. * * @since 2.5 * @author Fabio B. Silva */ -interface TransactionalRegion extends Region +interface ConcurrentRegion extends Region { /** * Attempts to write lock the mapping for the given key. diff --git a/lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php b/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php similarity index 92% rename from lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php rename to lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php index c4c368111f9..debd1808df4 100644 --- a/lib/Doctrine/ORM/Cache/TransactionalRegionAccess.php +++ b/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php @@ -21,19 +21,19 @@ namespace Doctrine\ORM\Cache; /** - * Defines contract for regions which hold transactionally-managed data. + * Defines contract for regions which hold concurrently managed data. * * @since 2.5 * @author Fabio B. Silva */ -interface TransactionalRegionAccess extends RegionAccess +interface ConcurrentRegionAccess extends RegionAccess { /** * We are going to attempt to update/delete the keyed object. * * @param \Doctrine\ORM\Cache\CacheKey $identifier The key of the item to lock. * - * @return \Doctrine\ORM\Cache\Lock A representation of our lock on the item; or null. + * @return \Doctrine\ORM\Cache\Lock A representation of our lock on the item * * @throws \Doctrine\ORM\Cache\CacheException */ diff --git a/lib/Doctrine/ORM/Cache/EntityCacheKey.php b/lib/Doctrine/ORM/Cache/EntityCacheKey.php index 46fb5ff1e67..25bc0dcd90b 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheKey.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheKey.php @@ -39,10 +39,15 @@ class EntityCacheKey implements CacheKey public $entityClass; /** - * @param array $identifier + * @var string + */ + private $hash; + + /** * @param string $entityClass + * @param array $identifier */ - public function __construct(array $identifier, $entityClass) + public function __construct($entityClass, array $identifier) { $this->identifier = $identifier; $this->entityClass = $entityClass; @@ -53,6 +58,12 @@ public function __construct(array $identifier, $entityClass) */ public function hash() { - return hash('sha512', $this->entityClass . serialize($this->identifier)); + if ($this->hash === null) { + ksort($this->identifier); + + return sprintf('%s[%s]', str_replace('\\', '.', strtolower($this->entityClass)) , implode(' ', $this->identifier)); + } + + return $this->hash; } } diff --git a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php index c9e3cfcc6ca..e2762f92bd9 100644 --- a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php @@ -64,9 +64,9 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e $data = $this->uow->getOriginalEntityData($entity); $data = array_merge($data, $key->identifier); // why update has no identifier values ? - foreach ($metadata->associationMappings as $name => $association) { + foreach ($metadata->associationMappings as $name => $assoc) { - if ( ! isset($association['cache'])) { + if (! $assoc['isOwningSide'] || ! $assoc['type'] & ClassMetadata::TO_ONE) { unset($data[$name]); continue; @@ -76,23 +76,17 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e continue; } - if ($association['type'] & ClassMetadata::TO_ONE) { - $data[$name] = $this->uow->getEntityIdentifier($data[$name]); - } - - if ($association['type'] & ClassMetadata::TO_MANY) { - unset($data[$name]); // handle collection here ? - } + $data[$name] = $this->uow->getEntityIdentifier($data[$name]); } return $data; } /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. - * @param array $cache The entity data. - * @param object $entity The entity to load the cache into. If not specified, a new entity is created. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. + * @param array $cache The entity data. + * @param object $entity The entity to load the cache into. If not specified, a new entity is created. */ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, array $cache, $entity = null) { diff --git a/lib/Doctrine/ORM/Cache/Region.php b/lib/Doctrine/ORM/Cache/Region.php index ba63d1bdf60..c1c1d2fba62 100644 --- a/lib/Doctrine/ORM/Cache/Region.php +++ b/lib/Doctrine/ORM/Cache/Region.php @@ -40,7 +40,7 @@ public function getName(); * * @param \Doctrine\ORM\Cache\CacheKey $key The cache key * - * @return boolean TRUE if the underlying cache contains corresponding data; FALSE otherwise. + * @return boolean TRUE if the underlying cache contains corresponding data; FALSE otherwise. */ public function contains(CacheKey $key); @@ -49,7 +49,7 @@ public function contains(CacheKey $key); * * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to be retrieved. * - * @return array The cached object or data NULL + * @return array The cached data or NULL * * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the item or region. */ diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index ab126bddd61..06586d71270 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -25,7 +25,7 @@ use Doctrine\Common\Cache\Cache; /** - * Defines a contract for accessing a particular named region. + * The simplest cache region compatible with all doctrine-cache drivers. * * @since 2.5 * @author Fabio B. Silva @@ -71,21 +71,27 @@ public function getName() } /** - * @return \Doctrine\Common\Cache\AccessProvider + * @return \Doctrine\Common\Cache\Cache */ public function getCache() { return $this->cache; } + /** + * @return string + */ private function entryKey(CacheKey $key) { - return sprintf("%s::values[%s]", $this->name, $key->hash()); + return sprintf("%s.values[%s]", $this->name, $key->hash()); } + /** + * @return string + */ private function entriesMapKey() { - return sprintf("%s::entries", $this->name); + return sprintf("%s[entries]", $this->name); } /** diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 29ea6911933..1d767eb9cf5 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -194,12 +194,12 @@ class ClassMetadataInfo implements ClassMetadata const CACHE_USAGE_READ_ONLY = 1; /** - * Nonstrict Read Write Cache doesn’t employ any locks but can do reads, inserts and deletes. + * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes. */ const CACHE_USAGE_NONSTRICT_READ_WRITE = 2; /** - * Read Write cache employs locks the entity before insert/update/delete. + * Read Write Attempts to lock the entity before update/delete. */ const CACHE_USAGE_READ_WRITE = 3; @@ -1010,7 +1010,7 @@ public function enableCache(array $cache) } if ( ! isset($cache['region'])) { - $cache['region'] = str_replace('\\', '', $this->rootEntityName); + $cache['region'] = strtolower(str_replace('\\', '.', $this->rootEntityName)); } if ( ! isset($cache['properties'])) { @@ -1032,7 +1032,7 @@ public function enableAssociationCache($fieldName, array $cache) } if ( ! isset($cache['region'])) { - $cache['region'] = str_replace('\\', '', $this->rootEntityName) . '::' . $fieldName; + $cache['region'] = strtolower(str_replace('\\', '.', $this->rootEntityName)) . '::' . $fieldName; } if ( ! isset($cache['properties'])) { diff --git a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php index bef12522b9d..611087a1c09 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php +++ b/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php @@ -65,4 +65,4 @@ require_once __DIR__.'/../AttributeOverride.php'; require_once __DIR__.'/../AttributeOverrides.php'; require_once __DIR__.'/../EntityListeners.php'; -require_once __DIR__.'/../Cache.php'; \ No newline at end of file +require_once __DIR__.'/../Cache.php'; diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 631fb85f74c..7edeb2266bc 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -23,6 +23,7 @@ use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\ConcurrentRegionAccess; use Doctrine\ORM\Cache\CollectionEntryStructure; /** @@ -80,10 +81,10 @@ abstract class AbstractCollectionPersister /** * @var boolean */ - protected $isTransactionalRegionAccess = false; + protected $isConcurrentRegion = false; /** - * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\TransactionalRegionAccess + * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess */ protected $cacheRegionAccess; @@ -114,8 +115,8 @@ public function __construct(EntityManager $em, array $association) ->getSecondLevelCacheAccessProvider() ->buildCollectioRegionAccessStrategy($sourceClass, $association['fieldName']); - $this->cacheEntryStructure = new CollectionEntryStructure($em); - $this->isTransactionalRegionAccess = ($this->cacheRegionAccess instanceof TransactionalRegionAccess); + $this->cacheEntryStructure = new CollectionEntryStructure($em); + $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); } } @@ -262,7 +263,7 @@ public function saveLoadedCollection(PersistentCollection $collection, Collectio foreach ($listData as $index => $identifier) { $entity = $list[$index]; - $entityKey = new EntityCacheKey($identifier, $targetClass); + $entityKey = new EntityCacheKey($targetClass, $identifier); $entityEntry = $targetPersister->getCacheEntryStructure()->buildCacheEntry($metadata, $entityKey, $entity); $targetRegionAcess->put($entityKey, $entityEntry); diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index baeb9e7b295..9095f1dd6a6 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -39,7 +39,7 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityEntryStructure; -use Doctrine\ORM\Cache\TransactionalRegionAccess; +use Doctrine\ORM\Cache\ConcurrentRegionAccess; /** * A BasicEntityPersister maps an entity to a single table in a relational database. @@ -224,10 +224,10 @@ class BasicEntityPersister /** * @var boolean */ - protected $isTransactionalRegionAccess = false; + private $isConcurrentRegion = false; /** - * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\TransactionalRegionAccess + * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess */ protected $cacheRegionAccess; @@ -257,8 +257,8 @@ public function __construct(EntityManager $em, ClassMetadata $class) ->getSecondLevelCacheAccessProvider() ->buildEntityRegionAccessStrategy($this->class); - $this->cacheEntryStructure = new EntityEntryStructure($em); - $this->isTransactionalRegionAccess = ($this->cacheRegionAccess instanceof TransactionalRegionAccess); + $this->cacheEntryStructure = new EntityEntryStructure($em); + $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); } } @@ -431,9 +431,9 @@ public function update($entity) $cacheKey = null; $cacheLock = null; - if ($this->isTransactionalRegionAccess) { - $cacheKey = new EntityCacheKey($this->em->getUnitOfWork()->getEntityIdentifier($entity), $this->class->rootEntityName); - $cacheLock = $this->cacheRegionAccess->lockItem(); + if ($this->isConcurrentRegion) { + $cacheKey = new EntityCacheKey($this->class->rootEntityName, $this->em->getUnitOfWork()->getEntityIdentifier($entity)); + $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); } $tableName = $this->class->getTableName(); @@ -679,10 +679,10 @@ public function delete($entity) $cacheLock = null; if ($this->hasCache) { - $cacheKey = new EntityCacheKey($identifier, $this->class->rootEntityName); + $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); } - if ($this->isTransactionalRegionAccess) { + if ($this->isConcurrentRegion) { $cacheLock = $this->cacheRegionAccess->lockItem(); } @@ -861,9 +861,8 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint $hydrator = $this->em->newHydrator($this->selectJoinSql ? Query::HYDRATE_OBJECT : Query::HYDRATE_SIMPLEOBJECT); $entities = $hydrator->hydrateAll($stmt, $this->rsm, $hints); - $entity = $entities ? $entities[0] : null; - return $entity; + return $entities ? $entities[0] : null; } /** @@ -882,7 +881,7 @@ public function loadById(array $identifier, $entity = null, $lockMode = 0) $cacheKey = null; if ($this->hasCache) { - $cacheKey = new EntityCacheKey($identifier, $this->class->rootEntityName); + $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); $cacheEntry = $this->cacheRegionAccess->get($cacheKey); if ($cacheEntry !== null) { @@ -893,7 +892,9 @@ public function loadById(array $identifier, $entity = null, $lockMode = 0) $entity = $this->load($identifier, $entity, null, array(), $lockMode); if ($this->hasCache && $entity !== null) { - $this->cacheRegionAccess->put($cacheKey, $this->cacheEntryStructure->buildCacheEntry($this->class, $cacheKey, $entity)); + $cacheEntry = $this->cacheEntryStructure->buildCacheEntry($this->class, $cacheKey, $entity); + + $this->cacheRegionAccess->put($cacheKey, $cacheEntry); } return $entity; @@ -1873,13 +1874,13 @@ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = nu */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { - $key = null; $collPersister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); $hasCache = $collPersister->hasCache(); + $key = null; if ($hasCache) { $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); - $key = new CollectionCacheKey($this->class->name, $assoc['fieldName'], $ownerId); + $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); $list = $collPersister->loadCachedCollection($coll, $key); if ($list !== null) { @@ -2135,36 +2136,14 @@ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targ return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" } - /** - * @param boolean $wasCommitted - */ - public function afterTransactionComplete($wasCommitted) + public function afterTransactionComplete() { - if ( ! $wasCommitted) { - - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - $this->queuedCache = array(); - - return; - } - $uow = $this->em->getUnitOfWork(); if (isset($this->queuedCache['insert'])) { foreach ($this->queuedCache['insert'] as $item) { - $key = new EntityCacheKey($uow->getEntityIdentifier($item['entity']), $this->class->rootEntityName); + $key = new EntityCacheKey($this->class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); $entry = $this->cacheEntryStructure->buildCacheEntry($this->class, $key, $item['entity']); $this->cacheRegionAccess->afterInsert($key, $entry); @@ -2174,12 +2153,12 @@ public function afterTransactionComplete($wasCommitted) if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { - $key = $item['key'] ?: new EntityCacheKey($uow->getEntityIdentifier($item['entity']), $this->class->rootEntityName); + $key = $item['key'] ?: new EntityCacheKey($this->class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); $entry = $this->cacheEntryStructure->buildCacheEntry($this->class, $key, $item['entity']); $this->cacheRegionAccess->afterUpdate($key, $entry); - if ($this->isTransactionalRegionAccess && $item['lock'] !== null) { + if ($this->isConcurrentRegion && $item['lock'] !== null) { $this->cacheRegionAccess->unlockItem($key, $item['lock']); } } @@ -2193,4 +2172,27 @@ public function afterTransactionComplete($wasCommitted) $this->queuedCache = array();; } + + public function afterTransactionRolledBack() + { + if ( ! $this->isConcurrentRegion) { + $this->queuedCache = array(); + + return; + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + $this->queuedCache = array(); + } } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 7f5449e0e09..66ad154969f 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -381,7 +381,7 @@ public function commit($entity = null) $conn->rollback(); foreach ($this->cachedPersisters as $persister) { - $persister->afterTransactionComplete(false); + $persister->afterTransactionRolledBack(); } throw $e; @@ -395,7 +395,7 @@ public function commit($entity = null) $this->dispatchPostFlushEvent(); foreach ($this->cachedPersisters as $persister) { - $persister->afterTransactionComplete(true); + $persister->afterTransactionComplete(); } // Clear up diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php index 7c2523c2a25..ba1709cdcbf 100644 --- a/tests/Doctrine/Tests/Models/Cache/City.php +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -22,7 +22,7 @@ class City protected $name; /** - * @Cache() + * @Cache * @ManyToOne(targetEntity="State", inversedBy="cities") * @JoinColumn(name="state_id", referencedColumnName="id") */ diff --git a/tests/Doctrine/Tests/Models/Cache/State.php b/tests/Doctrine/Tests/Models/Cache/State.php index 0456ba6a2f6..32eae11563e 100644 --- a/tests/Doctrine/Tests/Models/Cache/State.php +++ b/tests/Doctrine/Tests/Models/Cache/State.php @@ -24,16 +24,15 @@ class State protected $name; /** - * @Cache() + * @Cache * @ManyToOne(targetEntity="Country") * @JoinColumn(name="country_id", referencedColumnName="id") */ protected $country; /** - * @Cache() + * @Cache * @OneToMany(targetEntity="City", mappedBy="state") - * @JoinColumn(name="city_id", referencedColumnName="id") */ protected $cities; @@ -41,6 +40,7 @@ public function __construct($name, Country $country) { $this->name = $name; $this->country = $country; + $this->cities = new ArrayCollection(); } public function getId() diff --git a/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php b/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php new file mode 100644 index 00000000000..c48140ac539 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php @@ -0,0 +1,70 @@ +1)); + $key2 = new EntityCacheKey('Bar', array('id'=>1)); + + $this->assertNotEquals($key1->hash(), $key2->hash()); + } + + public function testEntityCacheKeyIdentifierType() + { + $key1 = new EntityCacheKey('Foo', array('id'=>1)); + $key2 = new EntityCacheKey('Foo', array('id'=>'1')); + + $this->assertEquals($key1->hash(), $key2->hash()); + } + + public function testEntityCacheKeyIdentifierOrder() + { + $key1 = new EntityCacheKey('Foo', array('foo_bar'=>1, 'bar_foo'=> 2)); + $key2 = new EntityCacheKey('Foo', array('bar_foo'=>2, 'foo_bar'=> 1)); + + $this->assertEquals($key1->hash(), $key2->hash()); + } + + public function testCollectionCacheKeyIdentifierType() + { + $key1 = new CollectionCacheKey('Foo', 'assoc', array('id'=>1)); + $key2 = new CollectionCacheKey('Foo', 'assoc', array('id'=>'1')); + + $this->assertEquals($key1->hash(), $key2->hash()); + } + + public function testCollectionCacheKeyIdentifierOrder() + { + $key1 = new CollectionCacheKey('Foo', 'assoc', array('foo_bar'=>1, 'bar_foo'=> 2)); + $key2 = new CollectionCacheKey('Foo', 'assoc', array('bar_foo'=>2, 'foo_bar'=> 1)); + + $this->assertEquals($key1->hash(), $key2->hash()); + } + + public function testCollectionCacheKeyIdentifierCollision() + { + $key1 = new CollectionCacheKey('Foo', 'assoc', array('id'=>1)); + $key2 = new CollectionCacheKey('Bar', 'assoc', array('id'=>1)); + + $this->assertNotEquals($key1->hash(), $key2->hash()); + } + + public function testCollectionCacheKeyAssociationCollision() + { + $key1 = new CollectionCacheKey('Foo', 'assoc1', array('id'=>1)); + $key2 = new CollectionCacheKey('Foo', 'assoc2', array('id'=>1)); + + $this->assertNotEquals($key1->hash(), $key2->hash()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php new file mode 100644 index 00000000000..c5069690703 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -0,0 +1,148 @@ +cache = new ArrayCache(); + $this->region = new DefaultRegion('default.region.test', $this->cache); + } + + public function testGetters() + { + $this->assertEquals('default.region.test', $this->region->getName()); + $this->assertSame($this->cache, $this->region->getCache()); + } + + static public function dataProviderCacheValues() + { + return array( + array(new DefaultRegionTestKey('key.1'), array('id'=>1, 'name' => 'bar')), + array(new DefaultRegionTestKey('key.2'), array('id'=>2, 'name' => 'foo')), + ); + } + + /** + * @dataProvider dataProviderCacheValues + */ + public function testPutGetContainsEvict($key, $value) + { + $this->assertFalse($this->region->contains($key)); + + $this->region->put($key, $value); + + $this->assertTrue($this->region->contains($key)); + + $actual = $this->region->get($key); + + $this->assertEquals($value, $actual); + + $this->region->evict($key); + + $this->assertFalse($this->region->contains($key)); + } + + public function testCount() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + $key3 = new DefaultRegionTestKey('key.3'); + + $this->assertCount(0, $this->region); + $this->assertInstanceOf('Countable', $this->region); + + $this->region->put($key1, array('value' => 'foo')); + $this->assertCount(1, $this->region); + + $this->region->put($key2, array('value' => 'bar')); + $this->assertCount(2, $this->region); + + $this->region->put($key2, array('value' => 'bar1')); + $this->assertCount(2, $this->region); + + $this->region->put($key3, array('value' => 'baz')); + $this->assertCount(3, $this->region); + } + + public function testEvictAll() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertCount(0, $this->region); + $this->assertFalse($this->region->contains($key1)); + $this->assertFalse($this->region->contains($key2)); + + $this->region->put($key1, array('value' => 'foo')); + $this->region->put($key2, array('value' => 'bar')); + + $this->assertTrue($this->region->contains($key1)); + $this->assertTrue($this->region->contains($key2)); + $this->assertCount(2, $this->region); + + $this->region->evictAll(); + + $this->assertCount(0, $this->region); + $this->assertFalse($this->region->contains($key1)); + $this->assertFalse($this->region->contains($key2)); + } + + public function testToArray() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertCount(0, $this->region); + $this->assertEquals(array(), $this->region->toArray()); + + $this->region->put($key1, array('value' => 'foo')); + $this->region->put($key2, array('value' => 'bar')); + + $array = $this->region->toArray(); + + $this->assertCount(2, $array); + + $this->assertArrayHasKey('value', $array[0]); + $this->assertArrayHasKey('value', $array[1]); + + $this->assertEquals('foo', $array[0]['value']); + $this->assertEquals('bar', $array[1]['value']); + } +} + +class DefaultRegionTestKey implements \Doctrine\ORM\Cache\CacheKey +{ + + function __construct($hash) + { + $this->hash = $hash; + } + + public function hash() + { + return $this->hash; + } +} + diff --git a/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php new file mode 100644 index 00000000000..b5852a77baa --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php @@ -0,0 +1,133 @@ +cache = new ArrayCache(); + $this->region = $this->createRegion(); + $this->regionAccess = $this->createRegionAccess(); + } + + protected function createRegionAccess() + { + return new NonStrictReadWriteRegionAccessStrategy($this->region); + } + + protected function createRegion() + { + $name = strtolower(str_replace('\\', '.', get_called_class())); + + return new DefaultRegion($name, $this->cache); + } + + public function testGetRegion() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->regionAccess->getRegion()); + } + + static public function dataProviderCacheValues() + { + $entityName = '\Doctrine\Tests\Models\Cache\Country'; + + return array( + array(new EntityCacheKey($entityName, array('id'=>1)), array('id'=>1, 'name' => 'bar')), + array(new EntityCacheKey($entityName, array('id'=>2)), array('id'=>2, 'name' => 'foo')), + ); + } + + /** + * @dataProvider dataProviderCacheValues + */ + public function testPutGetAndEvict($key, $value) + { + $this->assertNull($this->regionAccess->get($key)); + + $this->regionAccess->put($key, $value); + + $this->assertEquals($value, $this->regionAccess->get($key)); + + $this->regionAccess->evict($key); + + $this->assertNull($this->regionAccess->get($key)); + } + + public function testEvictAll() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->put($key1, array('value' => 'foo')); + $this->regionAccess->put($key2, array('value' => 'bar')); + + $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); + $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + + $this->regionAccess->evictAll(); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + } + + public function testAfterInsert() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->afterInsert($key1, array('value' => 'foo')); + $this->regionAccess->afterInsert($key2, array('value' => 'bar')); + + $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); + $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + } + + public function testAfterUpdate() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->afterUpdate($key1, array('value' => 'foo')); + $this->regionAccess->afterUpdate($key2, array('value' => 'bar')); + + $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); + $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + } +} diff --git a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php index ebb5a53cd14..841ba6579d2 100644 --- a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php @@ -2,10 +2,6 @@ namespace Doctrine\Tests\ORM\Cache; -use Doctrine\ORM\Cache\Region; -use Doctrine\Common\Cache\ArrayCache; -use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess; require_once __DIR__ . '/../../TestInit.php'; @@ -13,56 +9,19 @@ /** * @group DDC-2183 */ -class ReadOnlyRegionAccessTest extends \Doctrine\Tests\OrmTestCase +class ReadOnlyRegionAccessTest extends NonStrictReadWriteRegionAccessTest { - /** - * @var \Doctrine\ORM\Cache\Region - */ - private $region; - - /** - * @var \Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess - */ - private $regionAccess; - - protected function setUp() - { - parent::setUp(); - - $this->region = new DefaultRegion('DoctrineTestsModelsCacheCountry', new ArrayCache()); - $this->regionAccess = $this->createRegionAccess(); - } - - protected function createRegionAccess(Region $region = null) + protected function createRegionAccess() { - return new ReadOnlyRegionAccess($region ?: $this->region); - } - - public function testGetRegion() - { - $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->regionAccess->getRegion()); - } - - static public function dataPutAndGet() - { - $entityName = '\Doctrine\Tests\Models\Cache\Country'; - - return array( - array(new EntityCacheKey(array('id'=>1), $entityName), array('id'=>1, 'name' => 'bar')), - array(new EntityCacheKey(array('id'=>2), $entityName), array('id'=>2, 'name' => 'foo')), - ); + return new ReadOnlyRegionAccess($this->region); } /** - * @dataProvider dataPutAndGet + * @expectedException \Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Can't update a readonly object */ - public function testPutAndGet($key, $value) + public function testAfterUpdate() { - $this->regionAccess->put($key, $value); - - $actual = $this->regionAccess->get($key); - - $this->assertEquals($value, $actual); + $this->regionAccess->afterUpdate(new DefaultRegionTestKey('key'), array('value' => 'foo')); } - } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index 6aca74caed8..b06b7066352 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -67,6 +67,11 @@ private function loadFixturesCities() $berlin = new City("Berlin", $this->states[1]); $munich = new City("Munich", $this->states[1]); + $this->states[0]->addCity($saopaulo); + $this->states[0]->addCity($rio); + $this->states[1]->addCity($berlin); + $this->states[1]->addCity($berlin); + $this->cities[] = $saopaulo; $this->cities[] = $rio; $this->cities[] = $munich; @@ -90,22 +95,22 @@ public function testPutAndLoadEntities() $this->loadFixturesCountries(); $this->_em->clear(); - $countryClass = 'Doctrine\Tests\Models\Cache\Country'; - $countryMetadata = $this->_em->getClassMetadata($countryClass); - $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($countryMetadata); - $region = $cacheAccess->getRegion(); - - $cacheAccess->evictAll(); + $cache = $this->_em->getCache(); + $entityClass = 'Doctrine\Tests\Models\Cache\Country'; - $this->assertCount(0, $region); + $cache->evictEntityRegion($entityClass); - $c1 = $this->_em->find($countryClass, $this->countries[0]->getId()); - $c2 = $this->_em->find($countryClass, $this->countries[1]->getId()); + $this->assertFalse($cache->containsEntity($entityClass, $this->countries[0]->getId())); + $this->assertFalse($cache->containsEntity($entityClass, $this->countries[1]->getId())); - $this->assertCount(2, $region); + $c1 = $this->_em->find($entityClass, $this->countries[0]->getId()); + $c2 = $this->_em->find($entityClass, $this->countries[1]->getId()); - $this->assertInstanceOf($countryClass, $c1); - $this->assertInstanceOf($countryClass, $c2); + $this->assertTrue($cache->containsEntity($entityClass, $this->countries[0]->getId())); + $this->assertTrue($cache->containsEntity($entityClass, $this->countries[1]->getId())); + + $this->assertInstanceOf($entityClass, $c1); + $this->assertInstanceOf($entityClass, $c2); $this->assertEquals($this->countries[0]->getId(), $c1->getId()); $this->assertEquals($this->countries[0]->getName(), $c1->getName()); @@ -117,14 +122,13 @@ public function testPutAndLoadEntities() $queryCount = $this->getQueryCount(); - $c3 = $this->_em->find($countryClass, $this->countries[0]->getId()); - $c4 = $this->_em->find($countryClass, $this->countries[1]->getId()); + $c3 = $this->_em->find($entityClass, $this->countries[0]->getId()); + $c4 = $this->_em->find($entityClass, $this->countries[1]->getId()); - $this->assertCount(2, $region); $this->assertEquals($queryCount, $this->getQueryCount()); - $this->assertInstanceOf($countryClass, $c3); - $this->assertInstanceOf($countryClass, $c4); + $this->assertInstanceOf($entityClass, $c3); + $this->assertInstanceOf($entityClass, $c4); $this->assertEquals($c1->getId(), $c3->getId()); $this->assertEquals($c1->getName(), $c3->getName()); @@ -138,22 +142,22 @@ public function testRemoveEntities() $this->loadFixturesCountries(); $this->_em->clear(); - $countryClass = 'Doctrine\Tests\Models\Cache\Country'; - $countryMetadata = $this->_em->getClassMetadata($countryClass); - $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($countryMetadata); - $region = $cacheAccess->getRegion(); + $cache = $this->_em->getCache(); + $entityClass = 'Doctrine\Tests\Models\Cache\Country'; - $cacheAccess->evictAll(); + $cache->evictEntityRegion($entityClass); - $this->assertCount(0, $region); + $this->assertFalse($cache->containsEntity($entityClass, $this->countries[0]->getId())); + $this->assertFalse($cache->containsEntity($entityClass, $this->countries[1]->getId())); - $c1 = $this->_em->find($countryClass, $this->countries[0]->getId()); - $c2 = $this->_em->find($countryClass, $this->countries[1]->getId()); + $c1 = $this->_em->find($entityClass, $this->countries[0]->getId()); + $c2 = $this->_em->find($entityClass, $this->countries[1]->getId()); - $this->assertCount(2, $region); + $this->assertTrue($cache->containsEntity($entityClass, $this->countries[0]->getId())); + $this->assertTrue($cache->containsEntity($entityClass, $this->countries[1]->getId())); - $this->assertInstanceOf($countryClass, $c1); - $this->assertInstanceOf($countryClass, $c2); + $this->assertInstanceOf($entityClass, $c1); + $this->assertInstanceOf($entityClass, $c2); $this->assertEquals($this->countries[0]->getId(), $c1->getId()); $this->assertEquals($this->countries[0]->getName(), $c1->getName()); @@ -166,9 +170,11 @@ public function testRemoveEntities() $this->_em->flush(); $this->_em->clear(); - $this->assertCount(0, $region); - $this->assertNull($this->_em->find($countryClass, $this->countries[0]->getId())); - $this->assertNull($this->_em->find($countryClass, $this->countries[1]->getId())); + $this->assertFalse($cache->containsEntity($entityClass, $this->countries[0]->getId())); + $this->assertFalse($cache->containsEntity($entityClass, $this->countries[1]->getId())); + + $this->assertNull($this->_em->find($entityClass, $this->countries[0]->getId())); + $this->assertNull($this->_em->find($entityClass, $this->countries[1]->getId())); } public function testUpdateEntities() @@ -177,22 +183,22 @@ public function testUpdateEntities() $this->loadFixturesStates(); $this->_em->clear(); - $stateClass = 'Doctrine\Tests\Models\Cache\State'; - $stateMetadata = $this->_em->getClassMetadata($stateClass); - $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($stateMetadata); - $region = $cacheAccess->getRegion(); + $cache = $this->_em->getCache(); + $entityClass = 'Doctrine\Tests\Models\Cache\State'; - $cacheAccess->evictAll(); + $cache->evictEntityRegion($entityClass); - $this->assertCount(0, $region); + $this->assertFalse($cache->containsEntity($entityClass, $this->states[0]->getId())); + $this->assertFalse($cache->containsEntity($entityClass, $this->states[1]->getId())); - $s1 = $this->_em->find($stateClass, $this->states[0]->getId()); - $s2 = $this->_em->find($stateClass, $this->states[1]->getId()); + $s1 = $this->_em->find($entityClass, $this->states[0]->getId()); + $s2 = $this->_em->find($entityClass, $this->states[1]->getId()); - $this->assertCount(2, $region); + $this->assertTrue($cache->containsEntity($entityClass, $this->states[0]->getId())); + $this->assertTrue($cache->containsEntity($entityClass, $this->states[1]->getId())); - $this->assertInstanceOf($stateClass, $s1); - $this->assertInstanceOf($stateClass, $s2); + $this->assertInstanceOf($entityClass, $s1); + $this->assertInstanceOf($entityClass, $s2); $this->assertEquals($this->states[0]->getId(), $s1->getId()); $this->assertEquals($this->states[0]->getName(), $s1->getName()); @@ -208,17 +214,21 @@ public function testUpdateEntities() $this->_em->flush(); $this->_em->clear(); - $this->assertCount(2, $region); + $this->assertTrue($cache->containsEntity($entityClass, $this->states[0]->getId())); + $this->assertTrue($cache->containsEntity($entityClass, $this->states[1]->getId())); + $queryCount = $this->getQueryCount(); - $c3 = $this->_em->find($stateClass, $this->states[0]->getId()); - $c4 = $this->_em->find($stateClass, $this->states[1]->getId()); + $c3 = $this->_em->find($entityClass, $this->states[0]->getId()); + $c4 = $this->_em->find($entityClass, $this->states[1]->getId()); - $this->assertCount(2, $region); $this->assertEquals($queryCount, $this->getQueryCount()); - $this->assertInstanceOf($stateClass, $c3); - $this->assertInstanceOf($stateClass, $c4); + $this->assertTrue($cache->containsEntity($entityClass, $this->states[0]->getId())); + $this->assertTrue($cache->containsEntity($entityClass, $this->states[1]->getId())); + + $this->assertInstanceOf($entityClass, $c3); + $this->assertInstanceOf($entityClass, $c4); $this->assertEquals($s1->getId(), $c3->getId()); $this->assertEquals("NEW NAME 1", $c3->getName()); @@ -233,25 +243,34 @@ public function testPutAndLoadManyToOneRelation() $this->loadFixturesStates(); $this->_em->clear(); - $stateClass = 'Doctrine\Tests\Models\Cache\State'; - $countryClass = 'Doctrine\Tests\Models\Cache\Country'; - $stateMetadata = $this->_em->getClassMetadata($stateClass); - $cacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($stateMetadata); - $region = $cacheAccess->getRegion(); + $cache = $this->_em->getCache(); + $sourceClass = 'Doctrine\Tests\Models\Cache\State'; + $targetClass = 'Doctrine\Tests\Models\Cache\Country'; - $cacheAccess->evictAll(); + $cache->evictEntityRegion($sourceClass); + $cache->evictEntityRegion($targetClass); - $this->assertCount(0, $region); + $this->assertFalse($cache->containsEntity($sourceClass, $this->states[0]->getId())); + $this->assertFalse($cache->containsEntity($sourceClass, $this->states[1]->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->states[0]->getCountry()->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->states[1]->getCountry()->getId())); - $c1 = $this->_em->find($stateClass, $this->states[0]->getId()); - $c2 = $this->_em->find($stateClass, $this->states[1]->getId()); + $c1 = $this->_em->find($sourceClass, $this->states[0]->getId()); + $c2 = $this->_em->find($sourceClass, $this->states[1]->getId()); + + //trigger lazy load + $this->assertNotNull($c1->getCountry()->getName()); + $this->assertNotNull($c2->getCountry()->getName()); - $this->assertCount(2, $region); + $this->assertTrue($cache->containsEntity($targetClass, $this->states[0]->getCountry()->getId())); + $this->assertTrue($cache->containsEntity($targetClass, $this->states[1]->getCountry()->getId())); + $this->assertTrue($cache->containsEntity($sourceClass, $this->states[0]->getId())); + $this->assertTrue($cache->containsEntity($sourceClass, $this->states[1]->getId())); - $this->assertInstanceOf($stateClass, $c1); - $this->assertInstanceOf($stateClass, $c2); - $this->assertInstanceOf($countryClass, $c1->getCountry()); - $this->assertInstanceOf($countryClass, $c2->getCountry()); + $this->assertInstanceOf($sourceClass, $c1); + $this->assertInstanceOf($sourceClass, $c2); + $this->assertInstanceOf($targetClass, $c1->getCountry()); + $this->assertInstanceOf($targetClass, $c2->getCountry()); $this->assertEquals($this->states[0]->getId(), $c1->getId()); $this->assertEquals($this->states[0]->getName(), $c1->getName()); @@ -267,16 +286,19 @@ public function testPutAndLoadManyToOneRelation() $queryCount = $this->getQueryCount(); - $c3 = $this->_em->find($stateClass, $this->states[0]->getId()); - $c4 = $this->_em->find($stateClass, $this->states[1]->getId()); + $c3 = $this->_em->find($sourceClass, $this->states[0]->getId()); + $c4 = $this->_em->find($sourceClass, $this->states[1]->getId()); - $this->assertCount(2, $region); $this->assertEquals($queryCount, $this->getQueryCount()); - $this->assertInstanceOf($stateClass, $c3); - $this->assertInstanceOf($stateClass, $c4); - $this->assertInstanceOf($countryClass, $c3->getCountry()); - $this->assertInstanceOf($countryClass, $c4->getCountry()); + //trigger lazy load from cache + $this->assertNotNull($c3->getCountry()->getName()); + $this->assertNotNull($c4->getCountry()->getName()); + + $this->assertInstanceOf($sourceClass, $c3); + $this->assertInstanceOf($sourceClass, $c4); + $this->assertInstanceOf($targetClass, $c3->getCountry()); + $this->assertInstanceOf($targetClass, $c4->getCountry()); $this->assertEquals($c1->getId(), $c3->getId()); $this->assertEquals($c1->getName(), $c3->getName()); @@ -298,57 +320,64 @@ public function testPutAndLoadOneToManyRelation() $this->loadFixturesCities(); $this->_em->clear(); - $targetMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Cache\City'); - $entityMetadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Cache\State'); + $cache = $this->_em->getCache(); + $targetClass = 'Doctrine\Tests\Models\Cache\City'; + $sourceClass = 'Doctrine\Tests\Models\Cache\State'; - $collCacheAccess = $this->_em->getCache()->getCollectionCacheRegionAcess($entityMetadata, 'cities'); - $entityCacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($entityMetadata); - $targetCacheAccess = $this->_em->getCache()->getEntityCacheRegionAcess($targetMetadata); + $cache->evictEntityRegion($sourceClass); + $cache->evictEntityRegion($targetClass); + $cache->evictCollectionRegion($sourceClass, 'cities'); - $collRegion = $collCacheAccess->getRegion(); - $entityRegion = $entityCacheAccess->getRegion(); - $targetRegion = $targetCacheAccess->getRegion(); + $this->assertFalse($cache->containsEntity($sourceClass, $this->states[0]->getId())); + $this->assertFalse($cache->containsEntity($sourceClass, $this->states[1]->getId())); - $collRegion->evictAll(); - $targetRegion->evictAll(); - $entityRegion->evictAll(); + $this->assertFalse($cache->containsCollection($sourceClass, 'cities', $this->states[0]->getId())); + $this->assertFalse($cache->containsCollection($sourceClass, 'cities', $this->states[1]->getId())); - $this->assertCount(0, $targetRegion); - $this->assertCount(0, $entityRegion); - $this->assertCount(0, $collRegion); + $this->assertFalse($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(0)->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(1)->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(0)->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(1)->getId())); - $c1 = $this->_em->find($entityMetadata->name, $this->states[0]->getId()); - $c2 = $this->_em->find($entityMetadata->name, $this->states[1]->getId()); + $c1 = $this->_em->find($sourceClass, $this->states[0]->getId()); + $c2 = $this->_em->find($sourceClass, $this->states[1]->getId()); //trigger lazy load $this->assertCount(2, $c1->getCities()); $this->assertCount(2, $c2->getCities()); - - $this->assertInstanceOf($targetMetadata->name, $c1->getCities()->get(0)); - $this->assertInstanceOf($targetMetadata->name, $c1->getCities()->get(1)); - $this->assertInstanceOf($targetMetadata->name, $c2->getCities()->get(0)); - $this->assertInstanceOf($targetMetadata->name, $c2->getCities()->get(1)); + $this->assertInstanceOf($targetClass, $c1->getCities()->get(0)); + $this->assertInstanceOf($targetClass, $c1->getCities()->get(1)); + + $this->assertInstanceOf($targetClass, $c2->getCities()->get(0)); + $this->assertInstanceOf($targetClass, $c2->getCities()->get(1)); - $this->assertCount(4, $targetRegion); - $this->assertCount(2, $entityRegion); - $this->assertCount(2, $collRegion); + $this->assertTrue($cache->containsEntity($sourceClass, $this->states[0]->getId())); + $this->assertTrue($cache->containsEntity($sourceClass, $this->states[1]->getId())); + + $this->assertTrue($cache->containsCollection($sourceClass, 'cities', $this->states[0]->getId())); + $this->assertTrue($cache->containsCollection($sourceClass, 'cities', $this->states[1]->getId())); + + $this->assertTrue($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(0)->getId())); + $this->assertTrue($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(1)->getId())); + $this->assertTrue($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(0)->getId())); + $this->assertTrue($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(1)->getId())); $this->_em->clear(); $queryCount = $this->getQueryCount(); - $c3 = $this->_em->find($entityMetadata->name, $this->states[0]->getId()); - $c4 = $this->_em->find($entityMetadata->name, $this->states[1]->getId()); + $c3 = $this->_em->find($sourceClass, $this->states[0]->getId()); + $c4 = $this->_em->find($sourceClass, $this->states[1]->getId()); //trigger lazy load from cache $this->assertCount(2, $c3->getCities()); $this->assertCount(2, $c4->getCities()); - $this->assertInstanceOf($targetMetadata->name, $c3->getCities()->get(0)); - $this->assertInstanceOf($targetMetadata->name, $c3->getCities()->get(1)); - $this->assertInstanceOf($targetMetadata->name, $c4->getCities()->get(0)); - $this->assertInstanceOf($targetMetadata->name, $c4->getCities()->get(1)); + $this->assertInstanceOf($targetClass, $c3->getCities()->get(0)); + $this->assertInstanceOf($targetClass, $c3->getCities()->get(1)); + $this->assertInstanceOf($targetClass, $c4->getCities()->get(0)); + $this->assertInstanceOf($targetClass, $c4->getCities()->get(1)); $this->assertEquals($c1->getCities()->get(0)->getId(), $c3->getCities()->get(0)->getId()); $this->assertEquals($c1->getCities()->get(0)->getName(), $c3->getCities()->get(0)->getName()); @@ -356,10 +385,6 @@ public function testPutAndLoadOneToManyRelation() $this->assertEquals($c2->getCities()->get(1)->getId(), $c4->getCities()->get(1)->getId()); $this->assertEquals($c2->getCities()->get(1)->getName(), $c4->getCities()->get(1)->getName()); - $this->assertCount(4, $targetRegion); - $this->assertCount(2, $entityRegion); - $this->assertCount(2, $collRegion); - $this->assertEquals($queryCount, $this->getQueryCount()); } } diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 6b7a7232e37..e50b63cea43 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests; use Doctrine\Common\Cache\Cache; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Cache\CacheAccessProvider; /** * Base testcase class for all functional ORM testcases. @@ -474,7 +474,7 @@ protected function _getEntityManager($config = null, $eventManager = null) { protected function enableSecondLevelCache(Cache $cache = null) { $cache = $cache ?: self::getSharedSecondLevelCacheDriverImpl(); - $provider = new \Doctrine\ORM\Cache\CacheAccessProvider($cache); + $provider = new CacheAccessProvider($cache); $this->secondLevelCacheAccessProvider = $provider; $this->isSecondLevelCacheEnabled = true; From 55146a357a881cc61711e25046c3c1ec392543c9 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Fri, 15 Mar 2013 10:22:29 -0300 Subject: [PATCH 03/71] Fix factory method name --- lib/Doctrine/ORM/Cache.php | 2 +- lib/Doctrine/ORM/Cache/AccessProvider.php | 2 +- lib/Doctrine/ORM/Cache/CacheAccessProvider.php | 2 +- lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index 63f00e41d6a..8af45b1820d 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -250,7 +250,7 @@ public function evictQueryRegions() * * @param regionName Query cache region name. * - * @return Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. + * @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. */ public function getQueryCache($regionName) { diff --git a/lib/Doctrine/ORM/Cache/AccessProvider.php b/lib/Doctrine/ORM/Cache/AccessProvider.php index 4d834434238..89203bab7d5 100644 --- a/lib/Doctrine/ORM/Cache/AccessProvider.php +++ b/lib/Doctrine/ORM/Cache/AccessProvider.php @@ -49,5 +49,5 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata); * * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. */ - public function buildCollectioRegionAccessStrategy(ClassMetadata $metadata, $fieldName); + public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName); } diff --git a/lib/Doctrine/ORM/Cache/CacheAccessProvider.php b/lib/Doctrine/ORM/Cache/CacheAccessProvider.php index 78e9283377a..ace25f0cc78 100644 --- a/lib/Doctrine/ORM/Cache/CacheAccessProvider.php +++ b/lib/Doctrine/ORM/Cache/CacheAccessProvider.php @@ -65,7 +65,7 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) /** * {@inheritdoc} */ - public function buildCollectioRegionAccessStrategy(ClassMetadata $metadata, $fieldName) + public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) { $mapping = $metadata->getAssociationMapping($fieldName); $properties = $mapping['cache']['properties']; diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 7edeb2266bc..0dd1efe6140 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -113,7 +113,7 @@ public function __construct(EntityManager $em, array $association) $sourceClass = $em->getClassMetadata($association['sourceEntity']); $this->cacheRegionAccess = $em->getConfiguration() ->getSecondLevelCacheAccessProvider() - ->buildCollectioRegionAccessStrategy($sourceClass, $association['fieldName']); + ->buildCollectionRegionAccessStrategy($sourceClass, $association['fieldName']); $this->cacheEntryStructure = new CollectionEntryStructure($em); $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); From 0731254558cac5477a970ecdb060b8c0c5482a34 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Thu, 28 Mar 2013 17:40:05 -0300 Subject: [PATCH 04/71] refactoring region access tests --- .../ORM/Cache/AbstractRegionAccessTest.php | 145 ++++++++++++++++++ .../Doctrine/Tests/ORM/Cache/CacheKeyTest.php | 2 - .../Tests/ORM/Cache/DefaultRegionTest.php | 2 - .../NonStrictReadWriteRegionAccessTest.php | 126 +-------------- .../ORM/Cache/ReadOnlyRegionAccessTest.php | 9 +- 5 files changed, 154 insertions(+), 130 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php new file mode 100644 index 00000000000..533aa2e16ab --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php @@ -0,0 +1,145 @@ +cache = $this->createCache(); + $this->region = $this->createRegion($this->cache); + $this->regionAccess = $this->createRegionAccess($this->region); + } + + /** + * @return \Doctrine\ORM\Cache\RegionAccess + */ + abstract protected function createRegionAccess(Region $region); + + /** + * @param \Doctrine\Common\Cache\Cache $cache + * + * @return \Doctrine\ORM\Cache\Region + */ + protected function createRegion(Cache $cache) + { + $name = strtolower(str_replace('\\', '.', get_called_class())); + + return new DefaultRegion($name, $cache); + } + + /** + * @return \Doctrine\Common\Cache\Cache + */ + protected function createCache() + { + return new ArrayCache(); + } + + public function testGetRegion() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->regionAccess->getRegion()); + } + + static public function dataProviderCacheValues() + { + $entityName = '\Doctrine\Tests\Models\Cache\Country'; + + return array( + array(new EntityCacheKey($entityName, array('id'=>1)), array('id'=>1, 'name' => 'bar')), + array(new EntityCacheKey($entityName, array('id'=>2)), array('id'=>2, 'name' => 'foo')), + ); + } + + /** + * @dataProvider dataProviderCacheValues + */ + public function testPutGetAndEvict($key, $value) + { + $this->assertNull($this->regionAccess->get($key)); + + $this->regionAccess->put($key, $value); + + $this->assertEquals($value, $this->regionAccess->get($key)); + + $this->regionAccess->evict($key); + + $this->assertNull($this->regionAccess->get($key)); + } + + public function testEvictAll() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->put($key1, array('value' => 'foo')); + $this->regionAccess->put($key2, array('value' => 'bar')); + + $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); + $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + + $this->regionAccess->evictAll(); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + } + + public function testAfterInsert() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->afterInsert($key1, array('value' => 'foo')); + $this->regionAccess->afterInsert($key2, array('value' => 'bar')); + + $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); + $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + } + + public function testAfterUpdate() + { + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->afterUpdate($key1, array('value' => 'foo')); + $this->regionAccess->afterUpdate($key2, array('value' => 'bar')); + + $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); + $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + } +} diff --git a/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php b/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php index c48140ac539..49dca59ca90 100644 --- a/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php @@ -5,8 +5,6 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; -require_once __DIR__ . '/../../TestInit.php'; - /** * @group DDC-2183 */ diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php index c5069690703..d9788acc675 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -5,8 +5,6 @@ use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\Common\Cache\ArrayCache; -require_once __DIR__ . '/../../TestInit.php'; - /** * @group DDC-2183 */ diff --git a/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php index b5852a77baa..974ad0472b7 100644 --- a/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php @@ -2,132 +2,16 @@ namespace Doctrine\Tests\ORM\Cache; -use Doctrine\Common\Cache\ArrayCache; -use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\Region\DefaultRegion; +use Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy; -require_once __DIR__ . '/../../TestInit.php'; - /** * @group DDC-2183 */ -class NonStrictReadWriteRegionAccessTest extends \Doctrine\Tests\OrmTestCase +class NonStrictReadWriteRegionAccessTest extends AbstractRegionAccessTest { - /** - * @var \Doctrine\Common\Cache\Cache - */ - protected $cache; - - /** - * @var \Doctrine\ORM\Cache\Region - */ - protected $region; - - /** - * @var \Doctrine\ORM\Cache\RegionAccess - */ - protected $regionAccess; - - protected function setUp() - { - parent::setUp(); - - $this->cache = new ArrayCache(); - $this->region = $this->createRegion(); - $this->regionAccess = $this->createRegionAccess(); - } - - protected function createRegionAccess() - { - return new NonStrictReadWriteRegionAccessStrategy($this->region); - } - - protected function createRegion() - { - $name = strtolower(str_replace('\\', '.', get_called_class())); - - return new DefaultRegion($name, $this->cache); - } - - public function testGetRegion() - { - $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->regionAccess->getRegion()); - } - - static public function dataProviderCacheValues() - { - $entityName = '\Doctrine\Tests\Models\Cache\Country'; - - return array( - array(new EntityCacheKey($entityName, array('id'=>1)), array('id'=>1, 'name' => 'bar')), - array(new EntityCacheKey($entityName, array('id'=>2)), array('id'=>2, 'name' => 'foo')), - ); - } - - /** - * @dataProvider dataProviderCacheValues - */ - public function testPutGetAndEvict($key, $value) - { - $this->assertNull($this->regionAccess->get($key)); - - $this->regionAccess->put($key, $value); - - $this->assertEquals($value, $this->regionAccess->get($key)); - - $this->regionAccess->evict($key); - - $this->assertNull($this->regionAccess->get($key)); - } - - public function testEvictAll() - { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->put($key1, array('value' => 'foo')); - $this->regionAccess->put($key2, array('value' => 'bar')); - - $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); - $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); - - $this->regionAccess->evictAll(); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - } - - public function testAfterInsert() + protected function createRegionAccess(Region $region) { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->afterInsert($key1, array('value' => 'foo')); - $this->regionAccess->afterInsert($key2, array('value' => 'bar')); - - $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); - $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); - } - - public function testAfterUpdate() - { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->afterUpdate($key1, array('value' => 'foo')); - $this->regionAccess->afterUpdate($key2, array('value' => 'bar')); - - $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); - $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + return new NonStrictReadWriteRegionAccessStrategy($region); } -} +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php index 841ba6579d2..3a20653fc32 100644 --- a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php @@ -2,18 +2,17 @@ namespace Doctrine\Tests\ORM\Cache; +use Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess; -require_once __DIR__ . '/../../TestInit.php'; - /** * @group DDC-2183 */ -class ReadOnlyRegionAccessTest extends NonStrictReadWriteRegionAccessTest +class ReadOnlyRegionAccessTest extends AbstractRegionAccessTest { - protected function createRegionAccess() + protected function createRegionAccess(Region $region) { - return new ReadOnlyRegionAccess($this->region); + return new ReadOnlyRegionAccess($region); } /** From 9466a44a9dd75a69420185fee9449542ee88456e Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Thu, 28 Mar 2013 21:40:18 -0300 Subject: [PATCH 05/71] basically support for many to many collection --- .../ORM/Cache/EntityEntryStructure.php | 10 +- .../ORM/Persisters/BasicEntityPersister.php | 32 +++- tests/Doctrine/Tests/Models/Cache/City.php | 22 ++- tests/Doctrine/Tests/Models/Cache/Travel.php | 88 +++++++++++ .../Doctrine/Tests/Models/Cache/Traveler.php | 61 ++++++++ .../ORM/Cache/AbstractRegionAccessTest.php | 2 + .../ORM/Functional/SecondLevelCacheTest.php | 146 ++++++++++++++++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 5 + 8 files changed, 351 insertions(+), 15 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/Cache/Travel.php create mode 100644 tests/Doctrine/Tests/Models/Cache/Traveler.php diff --git a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php index e2762f92bd9..cdc7883d02d 100644 --- a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php @@ -66,17 +66,15 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e foreach ($metadata->associationMappings as $name => $assoc) { - if (! $assoc['isOwningSide'] || ! $assoc['type'] & ClassMetadata::TO_ONE) { - unset($data[$name]); - + if ( ! isset($data[$name]) || $data[$name] === null) { continue; } - if ( ! isset($data[$name]) || $data[$name] === null) { - continue; + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { + $data[$name] = $this->uow->getEntityIdentifier($data[$name]); } - $data[$name] = $this->uow->getEntityIdentifier($data[$name]); + unset($data[$name]); } return $data; diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 9095f1dd6a6..e9ac84cc982 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -430,12 +430,6 @@ public function update($entity) { $cacheKey = null; $cacheLock = null; - - if ($this->isConcurrentRegion) { - $cacheKey = new EntityCacheKey($this->class->rootEntityName, $this->em->getUnitOfWork()->getEntityIdentifier($entity)); - $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); - } - $tableName = $this->class->getTableName(); $updateData = $this->prepareUpdateData($entity); @@ -443,6 +437,11 @@ public function update($entity) return; } + if ($this->isConcurrentRegion) { + $cacheKey = new EntityCacheKey($this->class->rootEntityName, $this->em->getUnitOfWork()->getEntityIdentifier($entity)); + $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); + } + $isVersioned = $this->class->isVersioned; $quotedTableName = $this->quoteStrategy->getTableName($this->class, $this->platform); @@ -1150,9 +1149,28 @@ private function loadCollectionFromStatement($assoc, $stmt, $coll) */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { + $collPersister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); + $hasCache = $collPersister->hasCache(); + $key = null; + + if ($hasCache) { + $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); + $list = $collPersister->loadCachedCollection($coll, $key); + + if ($list !== null) { + return $list; + } + } + $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); + $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); - return $this->loadCollectionFromStatement($assoc, $stmt, $coll); + if ($hasCache && ! empty($list)) { + $collPersister->saveLoadedCollection($coll, $key, $list); + } + + return $list; } /** diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php index ba1709cdcbf..9727e403b83 100644 --- a/tests/Doctrine/Tests/Models/Cache/City.php +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -2,6 +2,8 @@ namespace Doctrine\Tests\Models\Cache; +use Doctrine\Common\Collections\ArrayCollection; + /** * @Cache * @Entity @@ -28,10 +30,16 @@ class City */ protected $state; + /** + * @ManyToMany(targetEntity="Travel", mappedBy="visitedCities") + */ + public $travels; + public function __construct($name, State $state) { - $this->name = $name; - $this->state = $state; + $this->name = $name; + $this->state = $state; + $this->travels = new ArrayCollection(); } public function getId() @@ -63,4 +71,14 @@ public function setState(State $state) { $this->state = $state; } + + public function addTravel(Travel $travel) + { + $this->travels[] = $travel; + } + + public function getTravels() + { + return $this->travels; + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/Travel.php b/tests/Doctrine/Tests/Models/Cache/Travel.php new file mode 100644 index 00000000000..589a57bc098 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/Travel.php @@ -0,0 +1,88 @@ +traveler = $traveler; + $this->visitedCities = new ArrayCollection(); + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * @return \Doctrine\Tests\Models\Cache\Traveler + */ + public function getTraveler() + { + return $this->traveler; + } + + /** + * @param \Doctrine\Tests\Models\Cache\Traveler $traveler + */ + public function setTraveler(Traveler $traveler) + { + $this->traveler = $traveler; + } + + /** + * @return \Doctrine\Common\Collections\ArrayCollection + */ + public function getVisitedCities() + { + return $this->visitedCities; + } + + /** + * @param \Doctrine\Tests\Models\Cache\City $city + */ + public function addVisitedCity(City $city) + { + $this->visitedCities[] = $city; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/Traveler.php b/tests/Doctrine/Tests/Models/Cache/Traveler.php new file mode 100644 index 00000000000..013ddf371f2 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/Traveler.php @@ -0,0 +1,61 @@ +name = $name; + } + + public function getId() + { + return $this->id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getTravels() + { + return $this->travels; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php index 533aa2e16ab..6fd30284fe1 100644 --- a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php @@ -8,6 +8,8 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\Region\DefaultRegion; +require_once __DIR__ . '/../../TestInit.php'; + /** * @group DDC-2183 */ diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index b06b7066352..7c550be029d 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -6,6 +6,9 @@ use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\City; +use Doctrine\Tests\Models\Cache\Traveler; +use Doctrine\Tests\Models\Cache\Travel; + require_once __DIR__ . '/../../TestInit.php'; /** @@ -17,6 +20,9 @@ class SecondLevelCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase private $states = array(); private $cities = array(); + private $travels = array(); + private $travelers = array(); + protected function setUp() { @@ -85,6 +91,41 @@ private function loadFixturesCities() $this->_em->flush(); } + private function loadFixturesTraveler() + { + $t1 = new Traveler("Fabio Silva"); + $t2 = new Traveler("Doctrine Bot"); + + $this->_em->persist($t1); + $this->_em->persist($t2); + + $this->travelers[] = $t1; + $this->travelers[] = $t2; + + $this->_em->flush(); + } + + private function loadFixturesTravels() + { + $t1 = new Travel($this->travelers[0]); + $t2 = new Travel($this->travelers[1]); + + $t1->addVisitedCity($this->cities[0]); + $t1->addVisitedCity($this->cities[1]); + $t1->addVisitedCity($this->cities[2]); + + $t2->addVisitedCity($this->cities[1]); + $t2->addVisitedCity($this->cities[3]); + + $this->_em->persist($t1); + $this->_em->persist($t2); + + $this->travels[] = $t1; + $this->travels[] = $t2; + + $this->_em->flush(); + } + private function getQueryCount() { return count($this->_sqlLoggerStack->queries); @@ -379,13 +420,118 @@ public function testPutAndLoadOneToManyRelation() $this->assertInstanceOf($targetClass, $c4->getCities()->get(0)); $this->assertInstanceOf($targetClass, $c4->getCities()->get(1)); + $this->assertNotSame($c1->getCities()->get(0), $c3->getCities()->get(0)); $this->assertEquals($c1->getCities()->get(0)->getId(), $c3->getCities()->get(0)->getId()); $this->assertEquals($c1->getCities()->get(0)->getName(), $c3->getCities()->get(0)->getName()); + $this->assertNotSame($c1->getCities()->get(1), $c3->getCities()->get(1)); + $this->assertEquals($c1->getCities()->get(1)->getId(), $c3->getCities()->get(1)->getId()); + $this->assertEquals($c1->getCities()->get(1)->getName(), $c3->getCities()->get(1)->getName()); + + $this->assertNotSame($c2->getCities()->get(0), $c4->getCities()->get(0)); + $this->assertEquals($c2->getCities()->get(0)->getId(), $c4->getCities()->get(0)->getId()); + $this->assertEquals($c2->getCities()->get(0)->getName(), $c4->getCities()->get(0)->getName()); + + $this->assertNotSame($c2->getCities()->get(1), $c4->getCities()->get(1)); $this->assertEquals($c2->getCities()->get(1)->getId(), $c4->getCities()->get(1)->getId()); $this->assertEquals($c2->getCities()->get(1)->getName(), $c4->getCities()->get(1)->getName()); $this->assertEquals($queryCount, $this->getQueryCount()); } + + public function testPutAndLoadManyToManyRelation() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesTraveler(); + $this->loadFixturesTravels(); + + $this->_em->clear(); + + $cache = $this->_em->getCache(); + $targetClass = 'Doctrine\Tests\Models\Cache\City'; + $sourceClass = 'Doctrine\Tests\Models\Cache\Travel'; + + $cache->evictEntityRegion($sourceClass); + $cache->evictEntityRegion($targetClass); + $cache->evictCollectionRegion($sourceClass, 'visitedCities'); + + $this->assertFalse($cache->containsEntity($sourceClass, $this->travels[0]->getId())); + $this->assertFalse($cache->containsEntity($sourceClass, $this->travels[1]->getId())); + + $this->assertFalse($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[0]->getId())); + $this->assertFalse($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[1]->getId())); + + $this->assertFalse($cache->containsEntity($targetClass, $this->cities[0]->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->cities[1]->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->cities[2]->getId())); + $this->assertFalse($cache->containsEntity($targetClass, $this->cities[3]->getId())); + + $t1 = $this->_em->find($sourceClass, $this->travels[0]->getId()); + $t2 = $this->_em->find($sourceClass, $this->travels[1]->getId()); + + //trigger lazy load + $this->assertCount(3, $t1->getVisitedCities()); + $this->assertCount(2, $t2->getVisitedCities()); + + $this->assertInstanceOf($targetClass, $t1->getVisitedCities()->get(0)); + $this->assertInstanceOf($targetClass, $t1->getVisitedCities()->get(1)); + $this->assertInstanceOf($targetClass, $t1->getVisitedCities()->get(2)); + + $this->assertInstanceOf($targetClass, $t2->getVisitedCities()->get(0)); + $this->assertInstanceOf($targetClass, $t2->getVisitedCities()->get(1)); + + $this->assertTrue($cache->containsEntity($sourceClass, $this->travels[0]->getId())); + $this->assertTrue($cache->containsEntity($sourceClass, $this->travels[1]->getId())); + + $this->assertTrue($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[0]->getId())); + $this->assertTrue($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[1]->getId())); + + $this->assertTrue($cache->containsEntity($targetClass, $this->cities[0]->getId())); + $this->assertTrue($cache->containsEntity($targetClass, $this->cities[1]->getId())); + $this->assertTrue($cache->containsEntity($targetClass, $this->cities[2]->getId())); + $this->assertTrue($cache->containsEntity($targetClass, $this->cities[3]->getId())); + + $this->_em->clear(); + + $queryCount = $this->getQueryCount(); + + $t3 = $this->_em->find($sourceClass, $this->travels[0]->getId()); + $t4 = $this->_em->find($sourceClass, $this->travels[1]->getId()); + + //trigger lazy load from cache + $this->assertCount(3, $t3->getVisitedCities()); + $this->assertCount(2, $t4->getVisitedCities()); + + $this->assertInstanceOf($targetClass, $t3->getVisitedCities()->get(0)); + $this->assertInstanceOf($targetClass, $t3->getVisitedCities()->get(1)); + $this->assertInstanceOf($targetClass, $t3->getVisitedCities()->get(2)); + + $this->assertInstanceOf($targetClass, $t4->getVisitedCities()->get(0)); + $this->assertInstanceOf($targetClass, $t4->getVisitedCities()->get(1)); + + $this->assertNotSame($t1->getVisitedCities()->get(0), $t3->getVisitedCities()->get(0)); + $this->assertEquals($t1->getVisitedCities()->get(0)->getId(), $t3->getVisitedCities()->get(0)->getId()); + $this->assertEquals($t1->getVisitedCities()->get(0)->getName(), $t3->getVisitedCities()->get(0)->getName()); + + $this->assertNotSame($t1->getVisitedCities()->get(1), $t3->getVisitedCities()->get(1)); + $this->assertEquals($t1->getVisitedCities()->get(1)->getId(), $t3->getVisitedCities()->get(1)->getId()); + $this->assertEquals($t1->getVisitedCities()->get(1)->getName(), $t3->getVisitedCities()->get(1)->getName()); + + $this->assertNotSame($t1->getVisitedCities()->get(2), $t3->getVisitedCities()->get(2)); + $this->assertEquals($t1->getVisitedCities()->get(2)->getId(), $t3->getVisitedCities()->get(2)->getId()); + $this->assertEquals($t1->getVisitedCities()->get(2)->getName(), $t3->getVisitedCities()->get(2)->getName()); + + $this->assertNotSame($t2->getVisitedCities()->get(0), $t4->getVisitedCities()->get(0)); + $this->assertEquals($t2->getVisitedCities()->get(0)->getId(), $t4->getVisitedCities()->get(0)->getId()); + $this->assertEquals($t2->getVisitedCities()->get(0)->getName(), $t4->getVisitedCities()->get(0)->getName()); + + $this->assertNotSame($t2->getVisitedCities()->get(1), $t4->getVisitedCities()->get(1)); + $this->assertEquals($t2->getVisitedCities()->get(1)->getId(), $t4->getVisitedCities()->get(1)->getId()); + $this->assertEquals($t2->getVisitedCities()->get(1)->getName(), $t4->getVisitedCities()->get(1)->getName()); + + $this->assertEquals($queryCount, $this->getQueryCount()); + } } diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index e50b63cea43..bc4766f2dbf 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -179,6 +179,8 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\Cache\Country', 'Doctrine\Tests\Models\Cache\State', 'Doctrine\Tests\Models\Cache\City', + 'Doctrine\Tests\Models\Cache\Traveler', + 'Doctrine\Tests\Models\Cache\Travel', ), ); @@ -316,6 +318,9 @@ protected function tearDown() } if (isset($this->_usedModelSets['cache_country'])) { + $conn->executeUpdate('DELETE FROM cache_trip_city'); + $conn->executeUpdate('DELETE FROM cache_traveler'); + $conn->executeUpdate('DELETE FROM cache_travel'); $conn->executeUpdate('DELETE FROM cache_country'); $conn->executeUpdate('DELETE FROM cache_state'); $conn->executeUpdate('DELETE FROM cache_city'); From d532fdf04dc6044f390f64543deda95dd0f8b827 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Fri, 29 Mar 2013 00:36:19 -0300 Subject: [PATCH 06/71] Refactoring tests --- .../ORM/Cache/EntityEntryStructure.php | 2 + tests/Doctrine/Tests/Models/Cache/City.php | 2 + tests/Doctrine/Tests/Models/Cache/Country.php | 2 + tests/Doctrine/Tests/Models/Cache/State.php | 2 + tests/Doctrine/Tests/Models/Cache/Travel.php | 2 + .../Doctrine/Tests/Models/Cache/Traveler.php | 31 +- .../Cache/CollectionEntryStructureTest.php | 70 +++ .../ORM/Cache/EntityEntryStructureTest.php | 113 ++++ .../SecondLevelCacheAbstractTest.php | 135 +++++ .../SecondLevelCacheManyToManyTest.php | 108 ++++ .../SecondLevelCacheManyToOneTest.php | 89 ++++ .../SecondLevelCacheOneToManyTest.php | 98 ++++ .../ORM/Functional/SecondLevelCacheTest.php | 485 ++---------------- 13 files changed, 702 insertions(+), 437 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/EntityEntryStructureTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php diff --git a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php index cdc7883d02d..f855ac4d6cd 100644 --- a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php @@ -72,6 +72,8 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { $data[$name] = $this->uow->getEntityIdentifier($data[$name]); + + continue; } unset($data[$name]); diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php index 9727e403b83..87b721bbf53 100644 --- a/tests/Doctrine/Tests/Models/Cache/City.php +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -11,6 +11,8 @@ */ class City { + const CLASSNAME = __CLASS__; + /** * @Id * @GeneratedValue diff --git a/tests/Doctrine/Tests/Models/Cache/Country.php b/tests/Doctrine/Tests/Models/Cache/Country.php index e0aba68323d..c147f26cbf2 100644 --- a/tests/Doctrine/Tests/Models/Cache/Country.php +++ b/tests/Doctrine/Tests/Models/Cache/Country.php @@ -9,6 +9,8 @@ */ class Country { + const CLASSNAME = __CLASS__; + /** * @Id * @GeneratedValue diff --git a/tests/Doctrine/Tests/Models/Cache/State.php b/tests/Doctrine/Tests/Models/Cache/State.php index 32eae11563e..eaab569e9a7 100644 --- a/tests/Doctrine/Tests/Models/Cache/State.php +++ b/tests/Doctrine/Tests/Models/Cache/State.php @@ -11,6 +11,8 @@ */ class State { + const CLASSNAME = __CLASS__; + /** * @Id * @GeneratedValue diff --git a/tests/Doctrine/Tests/Models/Cache/Travel.php b/tests/Doctrine/Tests/Models/Cache/Travel.php index 589a57bc098..03eb485a0f3 100644 --- a/tests/Doctrine/Tests/Models/Cache/Travel.php +++ b/tests/Doctrine/Tests/Models/Cache/Travel.php @@ -11,6 +11,8 @@ */ class Travel { + const CLASSNAME = __CLASS__; + /** * @Id * @GeneratedValue diff --git a/tests/Doctrine/Tests/Models/Cache/Traveler.php b/tests/Doctrine/Tests/Models/Cache/Traveler.php index 013ddf371f2..693d2710443 100644 --- a/tests/Doctrine/Tests/Models/Cache/Traveler.php +++ b/tests/Doctrine/Tests/Models/Cache/Traveler.php @@ -2,6 +2,8 @@ namespace Doctrine\Tests\Models\Cache; +use Doctrine\Common\Collections\ArrayCollection; + /** * @Cache * @Entity @@ -9,6 +11,8 @@ */ class Traveler { + const CLASSNAME = __CLASS__; + /** * @Id * @GeneratedValue @@ -22,6 +26,8 @@ class Traveler protected $name; /** + * @var \Doctrine\Common\Collections\Collection + * * @OneToMany(targetEntity="Travel", mappedBy="traveler") */ public $travels; @@ -31,7 +37,8 @@ class Traveler */ public function __construct($name) { - $this->name = $name; + $this->name = $name; + $this->travels = new ArrayCollection(); } public function getId() @@ -58,4 +65,26 @@ public function getTravels() { return $this->travels; } + + /** + * @param \Doctrine\Tests\Models\Cache\Travel $item + */ + public function addTravel(Travel $item) + { + if ( ! $this->travels->contains($item)) { + $this->travels->add($item); + } + + if ($item->getTraveler() !== $this) { + $item->setTraveler($this); + } + } + + /** + * @param \Doctrine\Tests\Models\Cache\Travel $item + */ + public function removeTravel(Travel $item) + { + $this->travels->removeElement($item); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php new file mode 100644 index 00000000000..62f5c7b157d --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php @@ -0,0 +1,70 @@ +em = $this->_getTestEntityManager(); + $this->structure = new CollectionEntryStructure($this->em); + } + + public function testLoadCacheCollection() + { + $cache = array( + array('id'=>31), + array('id'=>32), + ); + + $sourceClass = $this->em->getClassMetadata(State::CLASSNAME); + $targetClass = $this->em->getClassMetadata(City::CLASSNAME); + $key = new CollectionCacheKey($sourceClass->name, 'cities', array('id'=>21)); + $collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); + $list = $this->structure->loadCacheEntry($sourceClass, $key, $cache, $collection); + + $this->assertCount(2, $list); + $this->assertCount(2, $collection); + + $this->assertInstanceOf($sourceClass->name, $list[0]); + $this->assertInstanceOf($sourceClass->name, $list[1]); + $this->assertInstanceOf($sourceClass->name, $collection[0]); + $this->assertInstanceOf($sourceClass->name, $collection[1]); + + $this->assertSame($list[0], $collection[0]); + $this->assertSame($list[1], $collection[1]); + + $this->assertEquals(31, $list[0]->getId()); + $this->assertEquals(32, $list[1]->getId()); + $this->assertEquals($list[0]->getId(), $collection[0]->getId()); + $this->assertEquals($list[1]->getId(), $collection[1]->getId()); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($collection[0])); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($collection[1])); + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/EntityEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/EntityEntryStructureTest.php new file mode 100644 index 00000000000..a8c1e7b27be --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/EntityEntryStructureTest.php @@ -0,0 +1,113 @@ +em = $this->_getTestEntityManager(); + $this->structure = new EntityEntryStructure($this->em); + } + + public function testCreateEntity() + { + $metadata = $this->em->getClassMetadata(Country::CLASSNAME); + $key = new EntityCacheKey($metadata->name, array('id'=>1)); + $cache = array('id'=>1, 'name'=>'Foo'); + $entity = $this->structure->loadCacheEntry($metadata, $key, $cache); + + $this->assertInstanceOf($metadata->name, $entity); + + $this->assertEquals(1, $entity->getId()); + $this->assertEquals('Foo', $entity->getName()); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($entity)); + } + + public function testLoadProxy() + { + $metadata = $this->em->getClassMetadata(Country::CLASSNAME); + $key = new EntityCacheKey($metadata->name, array('id'=>1)); + $proxy = $this->em->getReference($metadata->name, $key->identifier); + $cache = array('id'=>1, 'name'=>'Foo'); + $entity = $this->structure->loadCacheEntry($metadata, $key, $cache, $proxy); + + $this->assertInstanceOf($metadata->name, $entity); + $this->assertSame($proxy, $entity); + + $this->assertEquals(1, $entity->getId()); + $this->assertEquals('Foo', $entity->getName()); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($proxy)); + } + + public function testBuildCacheEntry() + { + $entity = new Country('Foo'); + $uow = $this->em->getUnitOfWork(); + $data = array('id'=>1, 'name'=>'Foo'); + $metadata = $this->em->getClassMetadata(Country::CLASSNAME); + $key = new EntityCacheKey($metadata->name, array('id'=>1)); + + $entity->setId(1); + $uow->registerManaged($entity, $key->identifier, $data); + + $cache = $this->structure->buildCacheEntry($metadata, $key, $entity); + + $this->assertArrayHasKey('id', $cache); + $this->assertArrayHasKey('name', $cache); + $this->assertEquals(array( + 'id' => 1, + 'name' => 'Foo', + ), $cache); + } + + public function testBuildCacheEntryOwningSide() + { + $country = new Country('Foo'); + $state = new State('Bat', $country); + $uow = $this->em->getUnitOfWork(); + $countryData = array('id'=>11, 'name'=>'Foo'); + $stateData = array('id'=>12, 'name'=>'Bar', 'country' => $country); + $metadata = $this->em->getClassMetadata(State::CLASSNAME); + $key = new EntityCacheKey($metadata->name, array('id'=>11)); + + $country->setId(11); + $state->setId(12); + + $uow->registerManaged($country, array('id'=>11), $countryData); + $uow->registerManaged($state, array('id'=>12), $stateData); + + $cache = $this->structure->buildCacheEntry($metadata, $key, $state); + + $this->assertArrayHasKey('id', $cache); + $this->assertArrayHasKey('name', $cache); + $this->assertArrayHasKey('country', $cache); + $this->assertEquals(array( + 'id' => 11, + 'name' => 'Bar', + 'country' => array ('id' => 11), + ), $cache); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php new file mode 100644 index 00000000000..70339053b34 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -0,0 +1,135 @@ +enableSecondLevelCache(); + + $this->useModelSet('cache'); + + parent::setUp(); + + $this->cache = $this->_em->getCache(); + } + + protected function loadFixturesCountries() + { + $brazil = new Country("Brazil"); + $germany = new Country("Germany"); + + $this->countries[] = $brazil; + $this->countries[] = $germany; + + $this->_em->persist($brazil); + $this->_em->persist($germany); + $this->_em->flush(); + } + + protected function loadFixturesStates() + { + $saopaulo = new State("São Paulo", $this->countries[0]); + $rio = new State("Rio de janeiro", $this->countries[0]); + $berlin = new State("Berlin", $this->countries[1]); + $bavaria = new State("Bavaria", $this->countries[1]); + + $this->states[] = $saopaulo; + $this->states[] = $rio; + $this->states[] = $bavaria; + $this->states[] = $berlin; + + $this->_em->persist($saopaulo); + $this->_em->persist($rio); + $this->_em->persist($bavaria); + $this->_em->persist($berlin); + + $this->_em->flush(); + } + + protected function loadFixturesCities() + { + $saopaulo = new City("São Paulo", $this->states[0]); + $rio = new City("Rio de janeiro", $this->states[0]); + $berlin = new City("Berlin", $this->states[1]); + $munich = new City("Munich", $this->states[1]); + + $this->states[0]->addCity($saopaulo); + $this->states[0]->addCity($rio); + $this->states[1]->addCity($berlin); + $this->states[1]->addCity($berlin); + + $this->cities[] = $saopaulo; + $this->cities[] = $rio; + $this->cities[] = $munich; + $this->cities[] = $berlin; + + $this->_em->persist($saopaulo); + $this->_em->persist($rio); + $this->_em->persist($munich); + $this->_em->persist($berlin); + + $this->_em->flush(); + } + + protected function loadFixturesTraveler() + { + $t1 = new Traveler("Fabio Silva"); + $t2 = new Traveler("Doctrine Bot"); + + $this->_em->persist($t1); + $this->_em->persist($t2); + + $this->travelers[] = $t1; + $this->travelers[] = $t2; + + $this->_em->flush(); + } + + protected function loadFixturesTravels() + { + $t1 = new Travel($this->travelers[0]); + $t2 = new Travel($this->travelers[1]); + + $t1->addVisitedCity($this->cities[0]); + $t1->addVisitedCity($this->cities[1]); + $t1->addVisitedCity($this->cities[2]); + + $t2->addVisitedCity($this->cities[1]); + $t2->addVisitedCity($this->cities[3]); + + $this->_em->persist($t1); + $this->_em->persist($t2); + + $this->travels[] = $t1; + $this->travels[] = $t2; + + $this->_em->flush(); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php new file mode 100644 index 00000000000..7a42aa1b20e --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -0,0 +1,108 @@ +loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesTraveler(); + $this->loadFixturesTravels(); + + $this->_em->clear(); + } + + public function testPutAndLoadManyToManyRelation() + { + $this->cache->evictEntityRegion(Travel::CLASSNAME); + $this->cache->evictEntityRegion(City::CLASSNAME); + $this->cache->evictCollectionRegion(Travel::CLASSNAME, 'visitedCities'); + + $this->assertFalse($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[0]->getId())); + $this->assertFalse($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[1]->getId())); + + $this->assertFalse($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $this->travels[0]->getId())); + $this->assertFalse($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $this->travels[1]->getId())); + + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->cities[2]->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->cities[3]->getId())); + + $t1 = $this->_em->find(Travel::CLASSNAME, $this->travels[0]->getId()); + $t2 = $this->_em->find(Travel::CLASSNAME, $this->travels[1]->getId()); + + //trigger lazy load + $this->assertCount(3, $t1->getVisitedCities()); + $this->assertCount(2, $t2->getVisitedCities()); + + $this->assertInstanceOf(City::CLASSNAME, $t1->getVisitedCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $t1->getVisitedCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $t1->getVisitedCities()->get(2)); + + $this->assertInstanceOf(City::CLASSNAME, $t2->getVisitedCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $t2->getVisitedCities()->get(1)); + + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[1]->getId())); + + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $this->travels[0]->getId())); + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $this->travels[1]->getId())); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[2]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[3]->getId())); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + + $t3 = $this->_em->find(Travel::CLASSNAME, $this->travels[0]->getId()); + $t4 = $this->_em->find(Travel::CLASSNAME, $this->travels[1]->getId()); + + //trigger lazy load from cache + $this->assertCount(3, $t3->getVisitedCities()); + $this->assertCount(2, $t4->getVisitedCities()); + + $this->assertInstanceOf(City::CLASSNAME, $t3->getVisitedCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $t3->getVisitedCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $t3->getVisitedCities()->get(2)); + + $this->assertInstanceOf(City::CLASSNAME, $t4->getVisitedCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $t4->getVisitedCities()->get(1)); + + $this->assertNotSame($t1->getVisitedCities()->get(0), $t3->getVisitedCities()->get(0)); + $this->assertEquals($t1->getVisitedCities()->get(0)->getId(), $t3->getVisitedCities()->get(0)->getId()); + $this->assertEquals($t1->getVisitedCities()->get(0)->getName(), $t3->getVisitedCities()->get(0)->getName()); + + $this->assertNotSame($t1->getVisitedCities()->get(1), $t3->getVisitedCities()->get(1)); + $this->assertEquals($t1->getVisitedCities()->get(1)->getId(), $t3->getVisitedCities()->get(1)->getId()); + $this->assertEquals($t1->getVisitedCities()->get(1)->getName(), $t3->getVisitedCities()->get(1)->getName()); + + $this->assertNotSame($t1->getVisitedCities()->get(2), $t3->getVisitedCities()->get(2)); + $this->assertEquals($t1->getVisitedCities()->get(2)->getId(), $t3->getVisitedCities()->get(2)->getId()); + $this->assertEquals($t1->getVisitedCities()->get(2)->getName(), $t3->getVisitedCities()->get(2)->getName()); + + $this->assertNotSame($t2->getVisitedCities()->get(0), $t4->getVisitedCities()->get(0)); + $this->assertEquals($t2->getVisitedCities()->get(0)->getId(), $t4->getVisitedCities()->get(0)->getId()); + $this->assertEquals($t2->getVisitedCities()->get(0)->getName(), $t4->getVisitedCities()->get(0)->getName()); + + $this->assertNotSame($t2->getVisitedCities()->get(1), $t4->getVisitedCities()->get(1)); + $this->assertEquals($t2->getVisitedCities()->get(1)->getId(), $t4->getVisitedCities()->get(1)->getId()); + $this->assertEquals($t2->getVisitedCities()->get(1)->getName(), $t4->getVisitedCities()->get(1)->getName()); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php new file mode 100644 index 00000000000..c396a4ef42f --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php @@ -0,0 +1,89 @@ +loadFixturesCountries(); + $this->loadFixturesStates(); + $this->_em->clear(); + } + public function testPutAndLoadManyToOneRelation() + { + $this->cache->evictEntityRegion(State::CLASSNAME); + $this->cache->evictEntityRegion(Country::CLASSNAME); + + $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->states[0]->getCountry()->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->states[1]->getCountry()->getId())); + + $c1 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $c2 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + + //trigger lazy load + $this->assertNotNull($c1->getCountry()->getName()); + $this->assertNotNull($c2->getCountry()->getName()); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->states[0]->getCountry()->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->states[1]->getCountry()->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); + + $this->assertInstanceOf(State::CLASSNAME, $c1); + $this->assertInstanceOf(State::CLASSNAME, $c2); + $this->assertInstanceOf(Country::CLASSNAME, $c1->getCountry()); + $this->assertInstanceOf(Country::CLASSNAME, $c2->getCountry()); + + $this->assertEquals($this->states[0]->getId(), $c1->getId()); + $this->assertEquals($this->states[0]->getName(), $c1->getName()); + $this->assertEquals($this->states[0]->getCountry()->getId(), $c1->getCountry()->getId()); + $this->assertEquals($this->states[0]->getCountry()->getName(), $c1->getCountry()->getName()); + + $this->assertEquals($this->states[1]->getId(), $c2->getId()); + $this->assertEquals($this->states[1]->getName(), $c2->getName()); + $this->assertEquals($this->states[1]->getCountry()->getId(), $c2->getCountry()->getId()); + $this->assertEquals($this->states[1]->getCountry()->getName(), $c2->getCountry()->getName()); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + + $c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $c4 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + //trigger lazy load from cache + $this->assertNotNull($c3->getCountry()->getName()); + $this->assertNotNull($c4->getCountry()->getName()); + + $this->assertInstanceOf(State::CLASSNAME, $c3); + $this->assertInstanceOf(State::CLASSNAME, $c4); + $this->assertInstanceOf(Country::CLASSNAME, $c3->getCountry()); + $this->assertInstanceOf(Country::CLASSNAME, $c4->getCountry()); + + $this->assertEquals($c1->getId(), $c3->getId()); + $this->assertEquals($c1->getName(), $c3->getName()); + + $this->assertEquals($c2->getId(), $c4->getId()); + $this->assertEquals($c2->getName(), $c4->getName()); + + $this->assertEquals($this->states[0]->getCountry()->getId(), $c3->getCountry()->getId()); + $this->assertEquals($this->states[0]->getCountry()->getName(), $c3->getCountry()->getName()); + + $this->assertEquals($this->states[1]->getCountry()->getId(), $c4->getCountry()->getId()); + $this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php new file mode 100644 index 00000000000..42a80bcb733 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -0,0 +1,98 @@ +loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + } + + public function testPutAndLoadOneToManyRelation() + { + $this->cache->evictEntityRegion(State::CLASSNAME); + $this->cache->evictEntityRegion(City::CLASSNAME); + $this->cache->evictCollectionRegion(State::CLASSNAME, 'cities'); + + $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); + + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId())); + + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(0)->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(1)->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(0)->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(1)->getId())); + + $c1 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $c2 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + + //trigger lazy load + $this->assertCount(2, $c1->getCities()); + $this->assertCount(2, $c2->getCities()); + + $this->assertInstanceOf(City::CLASSNAME, $c1->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $c1->getCities()->get(1)); + + $this->assertInstanceOf(City::CLASSNAME, $c2->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $c2->getCities()->get(1)); + + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId())); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(0)->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(1)->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(0)->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(1)->getId())); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + + $c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $c4 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + + //trigger lazy load from cache + $this->assertCount(2, $c3->getCities()); + $this->assertCount(2, $c4->getCities()); + + $this->assertInstanceOf(City::CLASSNAME, $c3->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $c3->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $c4->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $c4->getCities()->get(1)); + + $this->assertNotSame($c1->getCities()->get(0), $c3->getCities()->get(0)); + $this->assertEquals($c1->getCities()->get(0)->getId(), $c3->getCities()->get(0)->getId()); + $this->assertEquals($c1->getCities()->get(0)->getName(), $c3->getCities()->get(0)->getName()); + + $this->assertNotSame($c1->getCities()->get(1), $c3->getCities()->get(1)); + $this->assertEquals($c1->getCities()->get(1)->getId(), $c3->getCities()->get(1)->getId()); + $this->assertEquals($c1->getCities()->get(1)->getName(), $c3->getCities()->get(1)->getName()); + + $this->assertNotSame($c2->getCities()->get(0), $c4->getCities()->get(0)); + $this->assertEquals($c2->getCities()->get(0)->getId(), $c4->getCities()->get(0)->getId()); + $this->assertEquals($c2->getCities()->get(0)->getName(), $c4->getCities()->get(0)->getName()); + + $this->assertNotSame($c2->getCities()->get(1), $c4->getCities()->get(1)); + $this->assertEquals($c2->getCities()->get(1)->getId(), $c4->getCities()->get(1)->getId()); + $this->assertEquals($c2->getCities()->get(1)->getName(), $c4->getCities()->get(1)->getName()); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index 7c550be029d..82a9efa8bf4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -4,154 +4,30 @@ use Doctrine\Tests\Models\Cache\Country; use Doctrine\Tests\Models\Cache\State; -use Doctrine\Tests\Models\Cache\City; - -use Doctrine\Tests\Models\Cache\Traveler; -use Doctrine\Tests\Models\Cache\Travel; - -require_once __DIR__ . '/../../TestInit.php'; /** * @group DDC-2183 */ -class SecondLevelCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase +class SecondLevelCacheTest extends SecondLevelCacheAbstractTest { - private $countries = array(); - private $states = array(); - private $cities = array(); - - private $travels = array(); - private $travelers = array(); - - protected function setUp() - { - - $this->enableSecondLevelCache(); - - $this->useModelSet('cache'); - - parent::setUp(); - } - - private function loadFixturesCountries() - { - $brazil = new Country("Brazil"); - $germany = new Country("Germany"); - - $this->countries[] = $brazil; - $this->countries[] = $germany; - - $this->_em->persist($brazil); - $this->_em->persist($germany); - $this->_em->flush(); - } - - private function loadFixturesStates() - { - $saopaulo = new State("São Paulo", $this->countries[0]); - $rio = new State("Rio de janeiro", $this->countries[0]); - $berlin = new State("Berlin", $this->countries[1]); - $bavaria = new State("Bavaria", $this->countries[1]); - - $this->states[] = $saopaulo; - $this->states[] = $rio; - $this->states[] = $bavaria; - $this->states[] = $berlin; - - $this->_em->persist($saopaulo); - $this->_em->persist($rio); - $this->_em->persist($bavaria); - $this->_em->persist($berlin); - - $this->_em->flush(); - } - - private function loadFixturesCities() - { - $saopaulo = new City("São Paulo", $this->states[0]); - $rio = new City("Rio de janeiro", $this->states[0]); - $berlin = new City("Berlin", $this->states[1]); - $munich = new City("Munich", $this->states[1]); - - $this->states[0]->addCity($saopaulo); - $this->states[0]->addCity($rio); - $this->states[1]->addCity($berlin); - $this->states[1]->addCity($berlin); - - $this->cities[] = $saopaulo; - $this->cities[] = $rio; - $this->cities[] = $munich; - $this->cities[] = $berlin; - - $this->_em->persist($saopaulo); - $this->_em->persist($rio); - $this->_em->persist($munich); - $this->_em->persist($berlin); - - $this->_em->flush(); - } - - private function loadFixturesTraveler() - { - $t1 = new Traveler("Fabio Silva"); - $t2 = new Traveler("Doctrine Bot"); - - $this->_em->persist($t1); - $this->_em->persist($t2); - - $this->travelers[] = $t1; - $this->travelers[] = $t2; - - $this->_em->flush(); - } - - private function loadFixturesTravels() - { - $t1 = new Travel($this->travelers[0]); - $t2 = new Travel($this->travelers[1]); - - $t1->addVisitedCity($this->cities[0]); - $t1->addVisitedCity($this->cities[1]); - $t1->addVisitedCity($this->cities[2]); - - $t2->addVisitedCity($this->cities[1]); - $t2->addVisitedCity($this->cities[3]); - - $this->_em->persist($t1); - $this->_em->persist($t2); - - $this->travels[] = $t1; - $this->travels[] = $t2; - - $this->_em->flush(); - } - - private function getQueryCount() - { - return count($this->_sqlLoggerStack->queries); - } - public function testPutAndLoadEntities() { $this->loadFixturesCountries(); $this->_em->clear(); - $cache = $this->_em->getCache(); - $entityClass = 'Doctrine\Tests\Models\Cache\Country'; - - $cache->evictEntityRegion($entityClass); + $this->cache->evictEntityRegion(Country::CLASSNAME); - $this->assertFalse($cache->containsEntity($entityClass, $this->countries[0]->getId())); - $this->assertFalse($cache->containsEntity($entityClass, $this->countries[1]->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $c1 = $this->_em->find($entityClass, $this->countries[0]->getId()); - $c2 = $this->_em->find($entityClass, $this->countries[1]->getId()); + $c1 = $this->_em->find(Country::CLASSNAME, $this->countries[0]->getId()); + $c2 = $this->_em->find(Country::CLASSNAME, $this->countries[1]->getId()); - $this->assertTrue($cache->containsEntity($entityClass, $this->countries[0]->getId())); - $this->assertTrue($cache->containsEntity($entityClass, $this->countries[1]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $this->assertInstanceOf($entityClass, $c1); - $this->assertInstanceOf($entityClass, $c2); + $this->assertInstanceOf(Country::CLASSNAME, $c1); + $this->assertInstanceOf(Country::CLASSNAME, $c2); $this->assertEquals($this->countries[0]->getId(), $c1->getId()); $this->assertEquals($this->countries[0]->getName(), $c1->getName()); @@ -161,15 +37,15 @@ public function testPutAndLoadEntities() $this->_em->clear(); - $queryCount = $this->getQueryCount(); + $queryCount = $this->getCurrentQueryCount(); - $c3 = $this->_em->find($entityClass, $this->countries[0]->getId()); - $c4 = $this->_em->find($entityClass, $this->countries[1]->getId()); + $c3 = $this->_em->find(Country::CLASSNAME, $this->countries[0]->getId()); + $c4 = $this->_em->find(Country::CLASSNAME, $this->countries[1]->getId()); - $this->assertEquals($queryCount, $this->getQueryCount()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); - $this->assertInstanceOf($entityClass, $c3); - $this->assertInstanceOf($entityClass, $c4); + $this->assertInstanceOf(Country::CLASSNAME, $c3); + $this->assertInstanceOf(Country::CLASSNAME, $c4); $this->assertEquals($c1->getId(), $c3->getId()); $this->assertEquals($c1->getName(), $c3->getName()); @@ -183,22 +59,19 @@ public function testRemoveEntities() $this->loadFixturesCountries(); $this->_em->clear(); - $cache = $this->_em->getCache(); - $entityClass = 'Doctrine\Tests\Models\Cache\Country'; + $this->cache->evictEntityRegion(Country::CLASSNAME); - $cache->evictEntityRegion($entityClass); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $this->assertFalse($cache->containsEntity($entityClass, $this->countries[0]->getId())); - $this->assertFalse($cache->containsEntity($entityClass, $this->countries[1]->getId())); + $c1 = $this->_em->find(Country::CLASSNAME, $this->countries[0]->getId()); + $c2 = $this->_em->find(Country::CLASSNAME, $this->countries[1]->getId()); - $c1 = $this->_em->find($entityClass, $this->countries[0]->getId()); - $c2 = $this->_em->find($entityClass, $this->countries[1]->getId()); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $this->assertTrue($cache->containsEntity($entityClass, $this->countries[0]->getId())); - $this->assertTrue($cache->containsEntity($entityClass, $this->countries[1]->getId())); - - $this->assertInstanceOf($entityClass, $c1); - $this->assertInstanceOf($entityClass, $c2); + $this->assertInstanceOf(Country::CLASSNAME, $c1); + $this->assertInstanceOf(Country::CLASSNAME, $c2); $this->assertEquals($this->countries[0]->getId(), $c1->getId()); $this->assertEquals($this->countries[0]->getName(), $c1->getName()); @@ -211,11 +84,11 @@ public function testRemoveEntities() $this->_em->flush(); $this->_em->clear(); - $this->assertFalse($cache->containsEntity($entityClass, $this->countries[0]->getId())); - $this->assertFalse($cache->containsEntity($entityClass, $this->countries[1]->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $this->assertNull($this->_em->find($entityClass, $this->countries[0]->getId())); - $this->assertNull($this->_em->find($entityClass, $this->countries[1]->getId())); + $this->assertNull($this->_em->find(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertNull($this->_em->find(Country::CLASSNAME, $this->countries[1]->getId())); } public function testUpdateEntities() @@ -224,22 +97,19 @@ public function testUpdateEntities() $this->loadFixturesStates(); $this->_em->clear(); - $cache = $this->_em->getCache(); - $entityClass = 'Doctrine\Tests\Models\Cache\State'; - - $cache->evictEntityRegion($entityClass); + $this->cache->evictEntityRegion(State::CLASSNAME); - $this->assertFalse($cache->containsEntity($entityClass, $this->states[0]->getId())); - $this->assertFalse($cache->containsEntity($entityClass, $this->states[1]->getId())); + $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); - $s1 = $this->_em->find($entityClass, $this->states[0]->getId()); - $s2 = $this->_em->find($entityClass, $this->states[1]->getId()); + $s1 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $s2 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); - $this->assertTrue($cache->containsEntity($entityClass, $this->states[0]->getId())); - $this->assertTrue($cache->containsEntity($entityClass, $this->states[1]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); - $this->assertInstanceOf($entityClass, $s1); - $this->assertInstanceOf($entityClass, $s2); + $this->assertInstanceOf(State::CLASSNAME, $s1); + $this->assertInstanceOf(State::CLASSNAME, $s2); $this->assertEquals($this->states[0]->getId(), $s1->getId()); $this->assertEquals($this->states[0]->getName(), $s1->getName()); @@ -255,21 +125,21 @@ public function testUpdateEntities() $this->_em->flush(); $this->_em->clear(); - $this->assertTrue($cache->containsEntity($entityClass, $this->states[0]->getId())); - $this->assertTrue($cache->containsEntity($entityClass, $this->states[1]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); - $queryCount = $this->getQueryCount(); + $queryCount = $this->getCurrentQueryCount(); - $c3 = $this->_em->find($entityClass, $this->states[0]->getId()); - $c4 = $this->_em->find($entityClass, $this->states[1]->getId()); + $c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $c4 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); - $this->assertEquals($queryCount, $this->getQueryCount()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); - $this->assertTrue($cache->containsEntity($entityClass, $this->states[0]->getId())); - $this->assertTrue($cache->containsEntity($entityClass, $this->states[1]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); - $this->assertInstanceOf($entityClass, $c3); - $this->assertInstanceOf($entityClass, $c4); + $this->assertInstanceOf(State::CLASSNAME, $c3); + $this->assertInstanceOf(State::CLASSNAME, $c4); $this->assertEquals($s1->getId(), $c3->getId()); $this->assertEquals("NEW NAME 1", $c3->getName()); @@ -277,261 +147,4 @@ public function testUpdateEntities() $this->assertEquals($s2->getId(), $c4->getId()); $this->assertEquals("NEW NAME 2", $c4->getName()); } - - public function testPutAndLoadManyToOneRelation() - { - $this->loadFixturesCountries(); - $this->loadFixturesStates(); - $this->_em->clear(); - - $cache = $this->_em->getCache(); - $sourceClass = 'Doctrine\Tests\Models\Cache\State'; - $targetClass = 'Doctrine\Tests\Models\Cache\Country'; - - $cache->evictEntityRegion($sourceClass); - $cache->evictEntityRegion($targetClass); - - $this->assertFalse($cache->containsEntity($sourceClass, $this->states[0]->getId())); - $this->assertFalse($cache->containsEntity($sourceClass, $this->states[1]->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->states[0]->getCountry()->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->states[1]->getCountry()->getId())); - - $c1 = $this->_em->find($sourceClass, $this->states[0]->getId()); - $c2 = $this->_em->find($sourceClass, $this->states[1]->getId()); - - //trigger lazy load - $this->assertNotNull($c1->getCountry()->getName()); - $this->assertNotNull($c2->getCountry()->getName()); - - $this->assertTrue($cache->containsEntity($targetClass, $this->states[0]->getCountry()->getId())); - $this->assertTrue($cache->containsEntity($targetClass, $this->states[1]->getCountry()->getId())); - $this->assertTrue($cache->containsEntity($sourceClass, $this->states[0]->getId())); - $this->assertTrue($cache->containsEntity($sourceClass, $this->states[1]->getId())); - - $this->assertInstanceOf($sourceClass, $c1); - $this->assertInstanceOf($sourceClass, $c2); - $this->assertInstanceOf($targetClass, $c1->getCountry()); - $this->assertInstanceOf($targetClass, $c2->getCountry()); - - $this->assertEquals($this->states[0]->getId(), $c1->getId()); - $this->assertEquals($this->states[0]->getName(), $c1->getName()); - $this->assertEquals($this->states[0]->getCountry()->getId(), $c1->getCountry()->getId()); - $this->assertEquals($this->states[0]->getCountry()->getName(), $c1->getCountry()->getName()); - - $this->assertEquals($this->states[1]->getId(), $c2->getId()); - $this->assertEquals($this->states[1]->getName(), $c2->getName()); - $this->assertEquals($this->states[1]->getCountry()->getId(), $c2->getCountry()->getId()); - $this->assertEquals($this->states[1]->getCountry()->getName(), $c2->getCountry()->getName()); - - $this->_em->clear(); - - $queryCount = $this->getQueryCount(); - - $c3 = $this->_em->find($sourceClass, $this->states[0]->getId()); - $c4 = $this->_em->find($sourceClass, $this->states[1]->getId()); - - $this->assertEquals($queryCount, $this->getQueryCount()); - - //trigger lazy load from cache - $this->assertNotNull($c3->getCountry()->getName()); - $this->assertNotNull($c4->getCountry()->getName()); - - $this->assertInstanceOf($sourceClass, $c3); - $this->assertInstanceOf($sourceClass, $c4); - $this->assertInstanceOf($targetClass, $c3->getCountry()); - $this->assertInstanceOf($targetClass, $c4->getCountry()); - - $this->assertEquals($c1->getId(), $c3->getId()); - $this->assertEquals($c1->getName(), $c3->getName()); - - $this->assertEquals($c2->getId(), $c4->getId()); - $this->assertEquals($c2->getName(), $c4->getName()); - - $this->assertEquals($this->states[0]->getCountry()->getId(), $c3->getCountry()->getId()); - $this->assertEquals($this->states[0]->getCountry()->getName(), $c3->getCountry()->getName()); - - $this->assertEquals($this->states[1]->getCountry()->getId(), $c4->getCountry()->getId()); - $this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName()); - } - - public function testPutAndLoadOneToManyRelation() - { - $this->loadFixturesCountries(); - $this->loadFixturesStates(); - $this->loadFixturesCities(); - $this->_em->clear(); - - $cache = $this->_em->getCache(); - $targetClass = 'Doctrine\Tests\Models\Cache\City'; - $sourceClass = 'Doctrine\Tests\Models\Cache\State'; - - $cache->evictEntityRegion($sourceClass); - $cache->evictEntityRegion($targetClass); - $cache->evictCollectionRegion($sourceClass, 'cities'); - - $this->assertFalse($cache->containsEntity($sourceClass, $this->states[0]->getId())); - $this->assertFalse($cache->containsEntity($sourceClass, $this->states[1]->getId())); - - $this->assertFalse($cache->containsCollection($sourceClass, 'cities', $this->states[0]->getId())); - $this->assertFalse($cache->containsCollection($sourceClass, 'cities', $this->states[1]->getId())); - - $this->assertFalse($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(0)->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(1)->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(0)->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(1)->getId())); - - $c1 = $this->_em->find($sourceClass, $this->states[0]->getId()); - $c2 = $this->_em->find($sourceClass, $this->states[1]->getId()); - - //trigger lazy load - $this->assertCount(2, $c1->getCities()); - $this->assertCount(2, $c2->getCities()); - - $this->assertInstanceOf($targetClass, $c1->getCities()->get(0)); - $this->assertInstanceOf($targetClass, $c1->getCities()->get(1)); - - $this->assertInstanceOf($targetClass, $c2->getCities()->get(0)); - $this->assertInstanceOf($targetClass, $c2->getCities()->get(1)); - - $this->assertTrue($cache->containsEntity($sourceClass, $this->states[0]->getId())); - $this->assertTrue($cache->containsEntity($sourceClass, $this->states[1]->getId())); - - $this->assertTrue($cache->containsCollection($sourceClass, 'cities', $this->states[0]->getId())); - $this->assertTrue($cache->containsCollection($sourceClass, 'cities', $this->states[1]->getId())); - - $this->assertTrue($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(0)->getId())); - $this->assertTrue($cache->containsEntity($targetClass, $this->states[0]->getCities()->get(1)->getId())); - $this->assertTrue($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(0)->getId())); - $this->assertTrue($cache->containsEntity($targetClass, $this->states[1]->getCities()->get(1)->getId())); - - $this->_em->clear(); - - $queryCount = $this->getQueryCount(); - - $c3 = $this->_em->find($sourceClass, $this->states[0]->getId()); - $c4 = $this->_em->find($sourceClass, $this->states[1]->getId()); - - //trigger lazy load from cache - $this->assertCount(2, $c3->getCities()); - $this->assertCount(2, $c4->getCities()); - - $this->assertInstanceOf($targetClass, $c3->getCities()->get(0)); - $this->assertInstanceOf($targetClass, $c3->getCities()->get(1)); - $this->assertInstanceOf($targetClass, $c4->getCities()->get(0)); - $this->assertInstanceOf($targetClass, $c4->getCities()->get(1)); - - $this->assertNotSame($c1->getCities()->get(0), $c3->getCities()->get(0)); - $this->assertEquals($c1->getCities()->get(0)->getId(), $c3->getCities()->get(0)->getId()); - $this->assertEquals($c1->getCities()->get(0)->getName(), $c3->getCities()->get(0)->getName()); - - $this->assertNotSame($c1->getCities()->get(1), $c3->getCities()->get(1)); - $this->assertEquals($c1->getCities()->get(1)->getId(), $c3->getCities()->get(1)->getId()); - $this->assertEquals($c1->getCities()->get(1)->getName(), $c3->getCities()->get(1)->getName()); - - $this->assertNotSame($c2->getCities()->get(0), $c4->getCities()->get(0)); - $this->assertEquals($c2->getCities()->get(0)->getId(), $c4->getCities()->get(0)->getId()); - $this->assertEquals($c2->getCities()->get(0)->getName(), $c4->getCities()->get(0)->getName()); - - $this->assertNotSame($c2->getCities()->get(1), $c4->getCities()->get(1)); - $this->assertEquals($c2->getCities()->get(1)->getId(), $c4->getCities()->get(1)->getId()); - $this->assertEquals($c2->getCities()->get(1)->getName(), $c4->getCities()->get(1)->getName()); - - $this->assertEquals($queryCount, $this->getQueryCount()); - } - - public function testPutAndLoadManyToManyRelation() - { - $this->loadFixturesCountries(); - $this->loadFixturesStates(); - $this->loadFixturesCities(); - $this->loadFixturesTraveler(); - $this->loadFixturesTravels(); - - $this->_em->clear(); - - $cache = $this->_em->getCache(); - $targetClass = 'Doctrine\Tests\Models\Cache\City'; - $sourceClass = 'Doctrine\Tests\Models\Cache\Travel'; - - $cache->evictEntityRegion($sourceClass); - $cache->evictEntityRegion($targetClass); - $cache->evictCollectionRegion($sourceClass, 'visitedCities'); - - $this->assertFalse($cache->containsEntity($sourceClass, $this->travels[0]->getId())); - $this->assertFalse($cache->containsEntity($sourceClass, $this->travels[1]->getId())); - - $this->assertFalse($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[0]->getId())); - $this->assertFalse($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[1]->getId())); - - $this->assertFalse($cache->containsEntity($targetClass, $this->cities[0]->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->cities[1]->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->cities[2]->getId())); - $this->assertFalse($cache->containsEntity($targetClass, $this->cities[3]->getId())); - - $t1 = $this->_em->find($sourceClass, $this->travels[0]->getId()); - $t2 = $this->_em->find($sourceClass, $this->travels[1]->getId()); - - //trigger lazy load - $this->assertCount(3, $t1->getVisitedCities()); - $this->assertCount(2, $t2->getVisitedCities()); - - $this->assertInstanceOf($targetClass, $t1->getVisitedCities()->get(0)); - $this->assertInstanceOf($targetClass, $t1->getVisitedCities()->get(1)); - $this->assertInstanceOf($targetClass, $t1->getVisitedCities()->get(2)); - - $this->assertInstanceOf($targetClass, $t2->getVisitedCities()->get(0)); - $this->assertInstanceOf($targetClass, $t2->getVisitedCities()->get(1)); - - $this->assertTrue($cache->containsEntity($sourceClass, $this->travels[0]->getId())); - $this->assertTrue($cache->containsEntity($sourceClass, $this->travels[1]->getId())); - - $this->assertTrue($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[0]->getId())); - $this->assertTrue($cache->containsCollection($sourceClass, 'visitedCities', $this->travels[1]->getId())); - - $this->assertTrue($cache->containsEntity($targetClass, $this->cities[0]->getId())); - $this->assertTrue($cache->containsEntity($targetClass, $this->cities[1]->getId())); - $this->assertTrue($cache->containsEntity($targetClass, $this->cities[2]->getId())); - $this->assertTrue($cache->containsEntity($targetClass, $this->cities[3]->getId())); - - $this->_em->clear(); - - $queryCount = $this->getQueryCount(); - - $t3 = $this->_em->find($sourceClass, $this->travels[0]->getId()); - $t4 = $this->_em->find($sourceClass, $this->travels[1]->getId()); - - //trigger lazy load from cache - $this->assertCount(3, $t3->getVisitedCities()); - $this->assertCount(2, $t4->getVisitedCities()); - - $this->assertInstanceOf($targetClass, $t3->getVisitedCities()->get(0)); - $this->assertInstanceOf($targetClass, $t3->getVisitedCities()->get(1)); - $this->assertInstanceOf($targetClass, $t3->getVisitedCities()->get(2)); - - $this->assertInstanceOf($targetClass, $t4->getVisitedCities()->get(0)); - $this->assertInstanceOf($targetClass, $t4->getVisitedCities()->get(1)); - - $this->assertNotSame($t1->getVisitedCities()->get(0), $t3->getVisitedCities()->get(0)); - $this->assertEquals($t1->getVisitedCities()->get(0)->getId(), $t3->getVisitedCities()->get(0)->getId()); - $this->assertEquals($t1->getVisitedCities()->get(0)->getName(), $t3->getVisitedCities()->get(0)->getName()); - - $this->assertNotSame($t1->getVisitedCities()->get(1), $t3->getVisitedCities()->get(1)); - $this->assertEquals($t1->getVisitedCities()->get(1)->getId(), $t3->getVisitedCities()->get(1)->getId()); - $this->assertEquals($t1->getVisitedCities()->get(1)->getName(), $t3->getVisitedCities()->get(1)->getName()); - - $this->assertNotSame($t1->getVisitedCities()->get(2), $t3->getVisitedCities()->get(2)); - $this->assertEquals($t1->getVisitedCities()->get(2)->getId(), $t3->getVisitedCities()->get(2)->getId()); - $this->assertEquals($t1->getVisitedCities()->get(2)->getName(), $t3->getVisitedCities()->get(2)->getName()); - - $this->assertNotSame($t2->getVisitedCities()->get(0), $t4->getVisitedCities()->get(0)); - $this->assertEquals($t2->getVisitedCities()->get(0)->getId(), $t4->getVisitedCities()->get(0)->getId()); - $this->assertEquals($t2->getVisitedCities()->get(0)->getName(), $t4->getVisitedCities()->get(0)->getName()); - - $this->assertNotSame($t2->getVisitedCities()->get(1), $t4->getVisitedCities()->get(1)); - $this->assertEquals($t2->getVisitedCities()->get(1)->getId(), $t4->getVisitedCities()->get(1)->getId()); - $this->assertEquals($t2->getVisitedCities()->get(1)->getName(), $t4->getVisitedCities()->get(1)->getName()); - - $this->assertEquals($queryCount, $this->getQueryCount()); - } -} - +} \ No newline at end of file From 3468ed724423f1382c8bcfa417909a66bc30bb74 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 30 Mar 2013 22:26:40 -0300 Subject: [PATCH 07/71] store cascade collections --- lib/Doctrine/ORM/Cache.php | 49 ++++++++++++ .../AbstractCollectionPersister.php | 79 ++++++++++++++++++- .../ORM/Persisters/BasicEntityPersister.php | 12 ++- lib/Doctrine/ORM/UnitOfWork.php | 17 +++- tests/Doctrine/Tests/Models/Cache/Travel.php | 26 +++++- .../Doctrine/Tests/Models/Cache/Traveler.php | 5 +- .../SecondLevelCacheManyToManyTest.php | 68 ++++++++++++++-- .../SecondLevelCacheOneToManyTest.php | 43 ++++++++-- 8 files changed, 279 insertions(+), 20 deletions(-) diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index 8af45b1820d..bebd3184c35 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -151,6 +151,26 @@ public function evictEntityRegion($className) $persister->getCacheRegionAcess()->evictAll(); } + /** + * Evict data from all entity regions. + * + * @return void + */ + public function evictEntityRegions() + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if ( ! $persister->hasCache()) { + continue; + } + + $persister->getCacheRegionAcess()->evictAll(); + } + } + /** * Determine whether the cache contains data for the given collection. * @@ -215,6 +235,33 @@ public function evictCollectionRegion($className, $association) $persister->getCacheRegionAcess()->evictAll(); } + /** + * Evict data from all collection regions. + * + * @return void + */ + public function evictCollectionRegions() + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + + foreach ($metadata->associationMappings as $association) { + if ( ! $association['type'] & ClassMetadata::TO_MANY) { + continue; + } + + $persister = $this->uow->getCollectionPersister($association); + + if ( ! $persister->hasCache()) { + return; + } + + $persister->getCacheRegionAcess()->evictAll(); + } + } + } + /** * Determine whether the cache contains data for the given query. * @@ -239,6 +286,8 @@ public function evictQueryRegion($regionName) /** * Evict data from all query regions. + * + * @return void */ public function evictQueryRegions() { diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 0dd1efe6140..d1ab3884c43 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -153,6 +153,14 @@ public function delete(PersistentCollection $coll) $sql = $this->getDeleteSQL($coll); + if ($this->hasCache) { + $this->queuedCache['delete'][] = array( + 'collection'=> $coll, + 'lock' => null, + 'key' => null + ); + } + $this->conn->executeUpdate($sql, $this->getDeleteSQLParameters($coll)); } @@ -193,6 +201,14 @@ public function update(PersistentCollection $coll) $this->deleteRows($coll); $this->insertRows($coll); + + if ($this->hasCache) { + $this->queuedCache['update'][] = array( + 'collection'=> $coll, + 'lock' => null, + 'key' => null + ); + } } /** @@ -262,8 +278,13 @@ public function saveLoadedCollection(PersistentCollection $collection, Collectio $listData = $this->cacheEntryStructure->buildCacheEntry($metadata, $key, $list); foreach ($listData as $index => $identifier) { + $entityKey = new EntityCacheKey($targetClass, $identifier); + + if ($targetRegionAcess->getRegion()->contains($entityKey)) { + continue; + } + $entity = $list[$index]; - $entityKey = new EntityCacheKey($targetClass, $identifier); $entityEntry = $targetPersister->getCacheEntryStructure()->buildCacheEntry($metadata, $entityKey, $entity); $targetRegionAcess->put($entityKey, $entityEntry); @@ -272,6 +293,62 @@ public function saveLoadedCollection(PersistentCollection $collection, Collectio $this->cacheRegionAccess->put($key, $listData); } + /** + * Execute operations after transaction complete + * + * @return void + */ + public function afterTransactionComplete() + { + // @TODO - handle locks + + $uow = $this->em->getUnitOfWork(); + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + + $coll = $item['collection']; + $mapping = $coll->getMapping(); + $list = $coll->toArray(); + $ownerId = $uow->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($mapping['sourceEntity'], $mapping['fieldName'], $ownerId); + + $this->saveLoadedCollection($coll, $key, $list); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + + $coll = $item['collection']; + $mapping = $coll->getMapping(); + $list = $coll->toArray(); + $ownerId = $uow->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($mapping['sourceEntity'], $mapping['fieldName'], $ownerId); + + $this->saveLoadedCollection($coll, $key, $list); + } + } + + $this->queuedCache = array(); + } + + /** + * Execute operations after transaction rollback + * + * @return void + */ + public function afterTransactionRolledBack() + { + if ( ! $this->isConcurrentRegion) { + $this->queuedCache = array(); + + return; + } + + // @TODO - handle locks + } + /** * Counts the size of this persistent collection. * diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index e9ac84cc982..aa37ba59abd 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -2154,6 +2154,11 @@ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targ return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" } + /** + * Execute operations after transaction complete + * + * @return void + */ public function afterTransactionComplete() { $uow = $this->em->getUnitOfWork(); @@ -2188,9 +2193,14 @@ public function afterTransactionComplete() } } - $this->queuedCache = array();; + $this->queuedCache = array(); } + /** + * Execute operations after transaction rollback + * + * @return void + */ public function afterTransactionRolledBack() { if ( ! $this->isConcurrentRegion) { diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 66ad154969f..ede57a05032 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -361,11 +361,24 @@ public function commit($entity = null) // Collection deletions (deletions of complete collections) foreach ($this->collectionDeletions as $collectionToDelete) { - $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); + $collectionPersister = $this->getCollectionPersister($collectionToDelete->getMapping()); + + $collectionPersister->delete($collectionToDelete); + + if ($collectionPersister->hasCache()) { + $this->cachedPersisters[] = $collectionPersister; + } } + // Collection updates (deleteRows, updateRows, insertRows) foreach ($this->collectionUpdates as $collectionToUpdate) { - $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); + $collectionPersister = $this->getCollectionPersister($collectionToUpdate->getMapping()); + + $collectionPersister->update($collectionToUpdate); + + if ($collectionPersister->hasCache()) { + $this->cachedPersisters[] = $collectionPersister; + } } // Entity deletions come last and need to be in reverse commit order diff --git a/tests/Doctrine/Tests/Models/Cache/Travel.php b/tests/Doctrine/Tests/Models/Cache/Travel.php index 03eb485a0f3..3fa6444d42b 100644 --- a/tests/Doctrine/Tests/Models/Cache/Travel.php +++ b/tests/Doctrine/Tests/Models/Cache/Travel.php @@ -20,6 +20,11 @@ class Travel */ protected $id; + /** + * @Column(type="date") + */ + protected $createdAt; + /** * @Cache * @ManyToOne(targetEntity="Traveler", inversedBy="travels") @@ -30,7 +35,7 @@ class Travel /** * @Cache * - * @ManyToMany(targetEntity="City", inversedBy="travels") + * @ManyToMany(targetEntity="City", inversedBy="travels", cascade={"persist", "remove"}) * @JoinTable(name="cache_visited_cities", * joinColumns={ * @JoinColumn(name="travel_id", referencedColumnName="id") @@ -45,6 +50,7 @@ class Travel public function __construct(Traveler $traveler) { $this->traveler = $traveler; + $this->createdAt = new \DateTime('now'); $this->visitedCities = new ArrayCollection(); } @@ -85,6 +91,22 @@ public function getVisitedCities() */ public function addVisitedCity(City $city) { - $this->visitedCities[] = $city; + $this->visitedCities->add($city); + } + + /** + * @param \Doctrine\Tests\Models\Cache\City $city + */ + public function removeVisitedCity(City $city) + { + $this->visitedCities->removeElement($city); + } + + /** + * @return \DateTime + */ + public function getCreatedAt() + { + return $this->createdAt; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/Traveler.php b/tests/Doctrine/Tests/Models/Cache/Traveler.php index 693d2710443..8c3eada3951 100644 --- a/tests/Doctrine/Tests/Models/Cache/Traveler.php +++ b/tests/Doctrine/Tests/Models/Cache/Traveler.php @@ -26,9 +26,10 @@ class Traveler protected $name; /** + * @Cache() + * @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}) + * * @var \Doctrine\Common\Collections\Collection - * - * @OneToMany(targetEntity="Travel", mappedBy="traveler") */ public $travels; diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php index 7a42aa1b20e..fe3c3a69973 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -3,17 +3,17 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\Tests\Models\Cache\City; +use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Travel; +use Doctrine\Tests\Models\Cache\Traveler; /** * @group DDC-2183 */ class SecondLevelCacheManyToManyTest extends SecondLevelCacheAbstractTest { - protected function setUp() + public function testPutAndLoadManyToManyRelation() { - parent::setUp(); - $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); @@ -21,10 +21,7 @@ protected function setUp() $this->loadFixturesTravels(); $this->_em->clear(); - } - public function testPutAndLoadManyToManyRelation() - { $this->cache->evictEntityRegion(Travel::CLASSNAME); $this->cache->evictEntityRegion(City::CLASSNAME); $this->cache->evictCollectionRegion(Travel::CLASSNAME, 'visitedCities'); @@ -105,4 +102,63 @@ public function testPutAndLoadManyToManyRelation() $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } + + public function testStoreManyToManyAssociationWhitCascade() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->cache->evictEntityRegion(City::CLASSNAME); + $this->cache->evictEntityRegion(Traveler::CLASSNAME); + $this->cache->evictEntityRegion(Travel::CLASSNAME); + $this->cache->evictCollectionRegion(State::CLASSNAME, 'cities'); + $this->cache->evictCollectionRegion(Traveler::CLASSNAME, 'travels'); + + $traveler = new Traveler('Doctrine Bot'); + $travel = new Travel($traveler); + + $travel->addVisitedCity($this->cities[0]); + $travel->addVisitedCity($this->cities[1]); + $travel->addVisitedCity($this->cities[3]); + + $this->_em->persist($traveler); + $this->_em->persist($travel); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $travel->getId())); + $this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $traveler->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[3]->getId())); + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $travel->getId())); + + $queryCount1 = $this->getCurrentQueryCount(); + $t1 = $this->_em->find(Travel::CLASSNAME, $travel->getId()); + + $this->assertInstanceOf(Travel::CLASSNAME, $t1); + $this->assertCount(3, $t1->getVisitedCities()); + $this->assertEquals($queryCount1, $this->getCurrentQueryCount()); + + $t1->removeVisitedCity($t1->getVisitedCities()->get(1)); + + $this->_em->persist($t1); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $travel->getId())); + $this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $traveler->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[3]->getId())); + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $travel->getId())); + + $queryCount2 = $this->getCurrentQueryCount(); + $t2 = $this->_em->find(Travel::CLASSNAME, $travel->getId()); + + $this->assertInstanceOf(Travel::CLASSNAME, $t1); + $this->assertCount(2, $t2->getVisitedCities()); + $this->assertEquals($queryCount2, $this->getCurrentQueryCount()); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 42a80bcb733..56f7299f74d 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -4,24 +4,22 @@ use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\City; +use Doctrine\Tests\Models\Cache\Travel; +use Doctrine\Tests\Models\Cache\Traveler; /** * @group DDC-2183 */ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest { - protected function setUp() - { - parent::setUp(); + public function testPutAndLoadOneToManyRelation() + { $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); $this->_em->clear(); - } - public function testPutAndLoadOneToManyRelation() - { $this->cache->evictEntityRegion(State::CLASSNAME); $this->cache->evictEntityRegion(City::CLASSNAME); $this->cache->evictCollectionRegion(State::CLASSNAME, 'cities'); @@ -95,4 +93,37 @@ public function testPutAndLoadOneToManyRelation() $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } + + public function testStoreOneToManyAssociationWhitCascade() + { + $this->markTestIncomplete(); + + $this->cache->evictCollectionRegion(Traveler::CLASSNAME, 'travels'); + $this->cache->evictEntityRegion(Traveler::CLASSNAME); + $this->cache->evictEntityRegion(Travel::CLASSNAME); + + $traveler = new Traveler('Doctrine Bot'); + + $traveler->addTravel(new Travel($traveler)); + $traveler->addTravel(new Travel($traveler)); + + $this->_em->persist($traveler); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsCollection(Traveler::CLASSNAME, 'travels', $traveler->getId())); + + $queryCount = $this->getCurrentQueryCount(); + $t1 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); + + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); + + $this->assertInstanceOf(Traveler::CLASSNAME, $t1); + $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(0)); + $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(1)); + $this->assertCount(2, $t1->getTravels()); + $this->assertNotSame($traveler, $t1); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + } } \ No newline at end of file From b0623406a43f9d8e8994161242d3735296b1af23 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 31 Mar 2013 00:31:15 -0300 Subject: [PATCH 08/71] refactoring collections store --- .../AbstractCollectionPersister.php | 74 +++++++++---------- .../ORM/Persisters/BasicEntityPersister.php | 4 +- .../SecondLevelCacheOneToManyTest.php | 9 ++- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index d1ab3884c43..5c62e2073f2 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -21,6 +21,7 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\ConcurrentRegionAccess; @@ -153,15 +154,11 @@ public function delete(PersistentCollection $coll) $sql = $this->getDeleteSQL($coll); + $this->conn->executeUpdate($sql, $this->getDeleteSQLParameters($coll)); + if ($this->hasCache) { - $this->queuedCache['delete'][] = array( - 'collection'=> $coll, - 'lock' => null, - 'key' => null - ); + $this->queuedCachedCollectionChange($coll->getOwner(), $coll->toArray()); } - - $this->conn->executeUpdate($sql, $this->getDeleteSQLParameters($coll)); } /** @@ -203,14 +200,22 @@ public function update(PersistentCollection $coll) $this->insertRows($coll); if ($this->hasCache) { - $this->queuedCache['update'][] = array( - 'collection'=> $coll, - 'lock' => null, - 'key' => null - ); + $this->queuedCachedCollectionChange($coll->getOwner(), $coll->toArray()); } } + /** + * @param object $owner + * @param array $elements + */ + public function queuedCachedCollectionChange($owner, array $elements) + { + $this->queuedCache[] = array( + 'owner' => $owner, + 'elements' => $elements, + ); + } + /** * Deletes rows. * @@ -264,18 +269,17 @@ public function loadCachedCollection(PersistentCollection $collection, Collectio } /** - * @param \Doctrine\ORM\PersistentCollection $collection - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * @param \Doctrine\ORM\Mapping\ClassMetadata $targetMetadata + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key * * @return void */ - public function saveLoadedCollection(PersistentCollection $collection, CollectionCacheKey $key, array $list) + public function saveLoadedCollection(ClassMetadata $targetMetadata, CollectionCacheKey $key, array $list) { - $metadata = $collection->getTypeClass(); $targetClass = $this->association['targetEntity']; $targetPersister = $this->uow->getEntityPersister($targetClass); $targetRegionAcess = $targetPersister->getCacheRegionAcess(); - $listData = $this->cacheEntryStructure->buildCacheEntry($metadata, $key, $list); + $listData = $this->cacheEntryStructure->buildCacheEntry($targetMetadata, $key, $list); foreach ($listData as $index => $identifier) { $entityKey = new EntityCacheKey($targetClass, $identifier); @@ -285,7 +289,7 @@ public function saveLoadedCollection(PersistentCollection $collection, Collectio } $entity = $list[$index]; - $entityEntry = $targetPersister->getCacheEntryStructure()->buildCacheEntry($metadata, $entityKey, $entity); + $entityEntry = $targetPersister->getCacheEntryStructure()->buildCacheEntry($targetMetadata, $entityKey, $entity); $targetRegionAcess->put($entityKey, $entityEntry); } @@ -300,33 +304,21 @@ public function saveLoadedCollection(PersistentCollection $collection, Collectio */ public function afterTransactionComplete() { - // @TODO - handle locks + // @TODO - handle locks ? $uow = $this->em->getUnitOfWork(); - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - - $coll = $item['collection']; - $mapping = $coll->getMapping(); - $list = $coll->toArray(); - $ownerId = $uow->getEntityIdentifier($coll->getOwner()); - $key = new CollectionCacheKey($mapping['sourceEntity'], $mapping['fieldName'], $ownerId); - - $this->saveLoadedCollection($coll, $key, $list); - } - } + if ( ! empty($this->queuedCache)) { - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { + $association = $this->association['fieldName']; + $mapping = $this->em->getClassMetadata($this->association['sourceEntity']); - $coll = $item['collection']; - $mapping = $coll->getMapping(); - $list = $coll->toArray(); - $ownerId = $uow->getEntityIdentifier($coll->getOwner()); - $key = new CollectionCacheKey($mapping['sourceEntity'], $mapping['fieldName'], $ownerId); + foreach ($this->queuedCache as $item) { + $elements = $item['elements']; + $ownerId = $uow->getEntityIdentifier($item['owner']); + $key = new CollectionCacheKey($mapping->rootEntityName, $association, $ownerId); - $this->saveLoadedCollection($coll, $key, $list); + $this->saveLoadedCollection($mapping, $key, $elements); } } @@ -340,13 +332,15 @@ public function afterTransactionComplete() */ public function afterTransactionRolledBack() { + // @TODO - handle locks ? + if ( ! $this->isConcurrentRegion) { $this->queuedCache = array(); return; } - // @TODO - handle locks + $this->queuedCache = array(); } /** diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index aa37ba59abd..fc096ae80ab 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1167,7 +1167,7 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); if ($hasCache && ! empty($list)) { - $collPersister->saveLoadedCollection($coll, $key, $list); + $collPersister->saveLoadedCollection($coll->getTypeClass(), $key, $list); } return $list; @@ -1910,7 +1910,7 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); if ($hasCache && ! empty($list)) { - $collPersister->saveLoadedCollection($coll, $key, $list); + $collPersister->saveLoadedCollection($coll->getTypeClass(), $key, $list); } return $list; diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 56f7299f74d..d3a19e41df5 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -102,16 +102,23 @@ public function testStoreOneToManyAssociationWhitCascade() $this->cache->evictEntityRegion(Traveler::CLASSNAME); $this->cache->evictEntityRegion(Travel::CLASSNAME); - $traveler = new Traveler('Doctrine Bot'); + $traveler = new Traveler('Doctrine Bot'); $traveler->addTravel(new Travel($traveler)); $traveler->addTravel(new Travel($traveler)); + //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); + $this->_em->persist($traveler); $this->_em->flush(); $this->_em->clear(); + //print_r($this->cache->getEntityCacheRegionAcess(Travel::CLASSNAME)->getRegion()->getCache()); + + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getId())); $this->assertTrue($this->cache->containsCollection(Traveler::CLASSNAME, 'travels', $traveler->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(0)->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(1)->getId())); $queryCount = $this->getCurrentQueryCount(); $t1 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); From 2904ecfa29e80c61a7086fe161f56c3c984dfc6f Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 31 Mar 2013 20:45:25 -0300 Subject: [PATCH 09/71] handle one to many collections storage --- .../AbstractCollectionPersister.php | 16 ++++--- lib/Doctrine/ORM/UnitOfWork.php | 44 +++++++++++++------ .../SecondLevelCacheOneToManyTest.php | 8 ---- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 5c62e2073f2..e3d7dad4d3b 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -24,6 +24,7 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Cache\ConcurrentRegionAccess; use Doctrine\ORM\Cache\CollectionEntryStructure; @@ -206,9 +207,9 @@ public function update(PersistentCollection $coll) /** * @param object $owner - * @param array $elements + * @param array|\Doctrine\Common\Collections\Collection $elements */ - public function queuedCachedCollectionChange($owner, array $elements) + public function queuedCachedCollectionChange($owner, $elements) { $this->queuedCache[] = array( 'owner' => $owner, @@ -269,17 +270,18 @@ public function loadCachedCollection(PersistentCollection $collection, Collectio } /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $targetMetadata - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * @param \Doctrine\ORM\Mapping\ClassMetadata $targetMetadata + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * @param array|\Doctrine\Common\Collections\Collection $elements * * @return void */ - public function saveLoadedCollection(ClassMetadata $targetMetadata, CollectionCacheKey $key, array $list) + public function saveLoadedCollection(ClassMetadata $targetMetadata, CollectionCacheKey $key, $elements) { $targetClass = $this->association['targetEntity']; $targetPersister = $this->uow->getEntityPersister($targetClass); $targetRegionAcess = $targetPersister->getCacheRegionAcess(); - $listData = $this->cacheEntryStructure->buildCacheEntry($targetMetadata, $key, $list); + $listData = $this->cacheEntryStructure->buildCacheEntry($targetMetadata, $key, $elements); foreach ($listData as $index => $identifier) { $entityKey = new EntityCacheKey($targetClass, $identifier); @@ -288,7 +290,7 @@ public function saveLoadedCollection(ClassMetadata $targetMetadata, CollectionCa continue; } - $entity = $list[$index]; + $entity = $elements[$index]; $entityEntry = $targetPersister->getCacheEntryStructure()->buildCacheEntry($targetMetadata, $entityKey, $entity); $targetRegionAcess->put($entityKey, $entityEntry); diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index ede57a05032..a6407754413 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -709,17 +709,22 @@ public function computeChangeSet(ClassMetadata $class, $entity) // Look for changes in associations of the entity foreach ($class->associationMappings as $field => $assoc) { - if (($val = $class->reflFields[$field]->getValue($entity)) !== null) { - $this->computeAssociationChanges($assoc, $val); - if (!isset($this->entityChangeSets[$oid]) && - $assoc['isOwningSide'] && - $assoc['type'] == ClassMetadata::MANY_TO_MANY && - $val instanceof PersistentCollection && - $val->isDirty()) { - $this->entityChangeSets[$oid] = array(); - $this->originalEntityData[$oid] = $actualData; - $this->entityUpdates[$oid] = $entity; - } + + if (($val = $class->reflFields[$field]->getValue($entity)) === null) { + continue; + } + + $this->computeAssociationChanges($entity, $assoc, $val); + + if ( ! isset($this->entityChangeSets[$oid]) && + $assoc['isOwningSide'] && + $assoc['type'] == ClassMetadata::MANY_TO_MANY && + $val instanceof PersistentCollection && + $val->isDirty()) { + + $this->entityChangeSets[$oid] = array(); + $this->originalEntityData[$oid] = $actualData; + $this->entityUpdates[$oid] = $entity; } } } @@ -780,15 +785,16 @@ public function computeChangeSets() /** * Computes the changes of an association. * - * @param array $assoc - * @param mixed $value The value of the association. + * @param mixed $entity The owner entity. + * @param array $assoc The association mapping. + * @param mixed $value The value of the association. * * @throws ORMInvalidArgumentException * @throws ORMException * * @return void */ - private function computeAssociationChanges($assoc, $value) + private function computeAssociationChanges($entity, $assoc, $value) { if ($value instanceof Proxy && ! $value->__isInitialized__) { return; @@ -810,6 +816,16 @@ private function computeAssociationChanges($assoc, $value) $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); + // Handle colection cache + if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && isset($assoc['cache'])) { + + $collectionPersister = $this->getCollectionPersister($assoc); + + $collectionPersister->queuedCachedCollectionChange($entity, $value); + + $this->cachedPersisters[] = $collectionPersister; + } + foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index d3a19e41df5..1ce157cdd46 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -96,8 +96,6 @@ public function testPutAndLoadOneToManyRelation() public function testStoreOneToManyAssociationWhitCascade() { - $this->markTestIncomplete(); - $this->cache->evictCollectionRegion(Traveler::CLASSNAME, 'travels'); $this->cache->evictEntityRegion(Traveler::CLASSNAME); $this->cache->evictEntityRegion(Travel::CLASSNAME); @@ -107,14 +105,10 @@ public function testStoreOneToManyAssociationWhitCascade() $traveler->addTravel(new Travel($traveler)); $traveler->addTravel(new Travel($traveler)); - //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); - $this->_em->persist($traveler); $this->_em->flush(); $this->_em->clear(); - //print_r($this->cache->getEntityCacheRegionAcess(Travel::CLASSNAME)->getRegion()->getCache()); - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getId())); $this->assertTrue($this->cache->containsCollection(Traveler::CLASSNAME, 'travels', $traveler->getId())); $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(0)->getId())); @@ -123,8 +117,6 @@ public function testStoreOneToManyAssociationWhitCascade() $queryCount = $this->getCurrentQueryCount(); $t1 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); - //$this->_em->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger); - $this->assertInstanceOf(Traveler::CLASSNAME, $t1); $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(0)); $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(1)); From 0ee0833b7fa6a5e59adb0ab957126933f295e2dc Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 31 Mar 2013 21:29:19 -0300 Subject: [PATCH 10/71] tests put entities on persist --- .../SecondLevelCacheManyToManyTest.php | 21 +++++++++++++++++++ .../SecondLevelCacheManyToOneTest.php | 15 +++++++++---- .../SecondLevelCacheOneToManyTest.php | 18 ++++++++++++++++ .../ORM/Functional/SecondLevelCacheTest.php | 9 ++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php index fe3c3a69973..d4daf303a17 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -12,6 +12,27 @@ */ class SecondLevelCacheManyToManyTest extends SecondLevelCacheAbstractTest { + public function testPutOnPersist() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesTraveler(); + $this->loadFixturesTravels(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[1]->getId())); + + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $this->travels[0]->getId())); + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $this->travels[1]->getId())); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[2]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[3]->getId())); + } + public function testPutAndLoadManyToManyRelation() { $this->loadFixturesCountries(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php index c396a4ef42f..dfbe21f3cd2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php @@ -10,17 +10,24 @@ */ class SecondLevelCacheManyToOneTest extends SecondLevelCacheAbstractTest { - - protected function setUp() + public function testPutOnPersist() { - parent::setUp(); - $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->states[0]->getCountry()->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->states[1]->getCountry()->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); } + public function testPutAndLoadManyToOneRelation() { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->_em->clear(); + $this->cache->evictEntityRegion(State::CLASSNAME); $this->cache->evictEntityRegion(Country::CLASSNAME); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 1ce157cdd46..a09dadf8818 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -12,6 +12,24 @@ */ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest { + public function testPutOnPersist() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId())); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(0)->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(1)->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(0)->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(1)->getId())); + } public function testPutAndLoadOneToManyRelation() { diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index 82a9efa8bf4..e4aebae8dd4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -10,6 +10,15 @@ */ class SecondLevelCacheTest extends SecondLevelCacheAbstractTest { + public function testPutOnPersist() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + } + public function testPutAndLoadEntities() { $this->loadFixturesCountries(); From da0398ad0dee5147b9f98ac898694f1a3f170047 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 31 Mar 2013 21:46:44 -0300 Subject: [PATCH 11/71] test one to many orphan removal --- .../Doctrine/Tests/Models/Cache/Traveler.php | 2 +- .../SecondLevelCacheOneToManyTest.php | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/tests/Doctrine/Tests/Models/Cache/Traveler.php b/tests/Doctrine/Tests/Models/Cache/Traveler.php index 8c3eada3951..ebc5b239cf1 100644 --- a/tests/Doctrine/Tests/Models/Cache/Traveler.php +++ b/tests/Doctrine/Tests/Models/Cache/Traveler.php @@ -27,7 +27,7 @@ class Traveler /** * @Cache() - * @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}) + * @OneToMany(targetEntity="Travel", mappedBy="traveler", cascade={"persist", "remove"}, orphanRemoval=true) * * @var \Doctrine\Common\Collections\Collection */ diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index a09dadf8818..48746562e2b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -120,6 +120,7 @@ public function testStoreOneToManyAssociationWhitCascade() $traveler = new Traveler('Doctrine Bot'); + $traveler->addTravel(new Travel($traveler)); $traveler->addTravel(new Travel($traveler)); $traveler->addTravel(new Travel($traveler)); @@ -131,16 +132,46 @@ public function testStoreOneToManyAssociationWhitCascade() $this->assertTrue($this->cache->containsCollection(Traveler::CLASSNAME, 'travels', $traveler->getId())); $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(0)->getId())); $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(1)->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(2)->getId())); - $queryCount = $this->getCurrentQueryCount(); - $t1 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); + $queryCount1 = $this->getCurrentQueryCount(); + $t1 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); $this->assertInstanceOf(Traveler::CLASSNAME, $t1); $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(0)); $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(1)); + $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(2)); + $this->assertCount(3, $t1->getTravels()); + $this->assertNotSame($traveler, $t1); + + $this->assertEquals($queryCount1, $this->getCurrentQueryCount()); + + $t1->removeTravel($t1->getTravels()->get(1)); + + $this->_em->persist($t1); + $this->_em->flush(); + $this->_em->clear(); + + $queryCount2 = $this->getCurrentQueryCount(); + $t2 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); + + $this->assertInstanceOf(Traveler::CLASSNAME, $t2); + $this->assertInstanceOf(Travel::CLASSNAME, $t2->getTravels()->get(0)); + $this->assertInstanceOf(Travel::CLASSNAME, $t2->getTravels()->get(2)); $this->assertCount(2, $t1->getTravels()); $this->assertNotSame($traveler, $t1); - $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + $this->assertEquals($queryCount2, $this->getCurrentQueryCount()); + + $this->assertCount(2, $t1->getTravels()); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getId())); + $this->assertTrue($this->cache->containsCollection(Traveler::CLASSNAME, 'travels', $traveler->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(0)->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(2)->getId())); + + $this->assertFalse($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(1)->getId())); + $this->assertNull($this->_em->find(Travel::CLASSNAME, $traveler->getTravels()->get(1)->getId())); + + $this->assertEquals($queryCount2 + 1, $this->getCurrentQueryCount()); } } \ No newline at end of file From 3097ea79f10d4744fedb2f8058c1ac77e8d27629 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 2 Apr 2013 05:05:43 -0300 Subject: [PATCH 12/71] drop support for Region#toArray and Region#count --- lib/Doctrine/ORM/Cache/Region.php | 13 +---- .../ORM/Cache/Region/DefaultRegion.php | 37 +------------- .../Cache/CollectionEntryStructureTest.php | 1 - .../Tests/ORM/Cache/DefaultRegionTest.php | 50 +------------------ 4 files changed, 5 insertions(+), 96 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/Region.php b/lib/Doctrine/ORM/Cache/Region.php index c1c1d2fba62..05a7ee4ebe8 100644 --- a/lib/Doctrine/ORM/Cache/Region.php +++ b/lib/Doctrine/ORM/Cache/Region.php @@ -26,7 +26,7 @@ * @since 2.5 * @author Fabio B. Silva */ -interface Region extends \Countable +interface Region { /** * Retrieve the name of this region. @@ -80,13 +80,4 @@ public function evict(CacheKey $key); * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region. */ public function evictAll(); - - /** - * Get the contents of this region as an array. - * - * @return array The cached data. - * - * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region. - */ - public function toArray(); -} +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index 06586d71270..c57c24f7e1c 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -173,39 +173,4 @@ public function evictAll() return empty($entries); } - - /** - * {@inheritdoc} - */ - public function count() - { - $entriesKey = $this->entriesMapKey(); - $entries = $this->cache->fetch($entriesKey); - - if ( ! is_array($entries)) { - return 0; - } - - return count($entries); - } - - /** - * {@inheritdoc} - */ - public function toArray() - { - $data = array(); - $entriesKey = $this->entriesMapKey(); - $entries = $this->cache->fetch($entriesKey); - - if ( ! is_array($entries) || empty($entries)) { - return array(); - } - - foreach ($entries as $entryKey => $value) { - $data[] = $this->cache->fetch($entryKey); - } - - return $data; - } -} +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php index 62f5c7b157d..a98fd4e91fe 100644 --- a/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php @@ -7,7 +7,6 @@ use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\Tests\Models\Cache\State; use Doctrine\ORM\PersistentCollection; -use Doctrine\Tests\Models\Cache\Country; use Doctrine\Tests\Models\Cache\City; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Cache\CollectionEntryStructure; diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php index d9788acc675..b9bf347141c 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -2,13 +2,14 @@ namespace Doctrine\Tests\ORM\Cache; +use Doctrine\Tests\OrmFunctionalTestCase; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\Common\Cache\ArrayCache; /** * @group DDC-2183 */ -class DefaultRegionTest extends \Doctrine\Tests\OrmFunctionalTestCase +class DefaultRegionTest extends OrmFunctionalTestCase { /** * @var \Doctrine\ORM\Cache\Region\DefaultRegion @@ -62,34 +63,11 @@ public function testPutGetContainsEvict($key, $value) $this->assertFalse($this->region->contains($key)); } - public function testCount() - { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - $key3 = new DefaultRegionTestKey('key.3'); - - $this->assertCount(0, $this->region); - $this->assertInstanceOf('Countable', $this->region); - - $this->region->put($key1, array('value' => 'foo')); - $this->assertCount(1, $this->region); - - $this->region->put($key2, array('value' => 'bar')); - $this->assertCount(2, $this->region); - - $this->region->put($key2, array('value' => 'bar1')); - $this->assertCount(2, $this->region); - - $this->region->put($key3, array('value' => 'baz')); - $this->assertCount(3, $this->region); - } - public function testEvictAll() { $key1 = new DefaultRegionTestKey('key.1'); $key2 = new DefaultRegionTestKey('key.2'); - $this->assertCount(0, $this->region); $this->assertFalse($this->region->contains($key1)); $this->assertFalse($this->region->contains($key2)); @@ -98,36 +76,12 @@ public function testEvictAll() $this->assertTrue($this->region->contains($key1)); $this->assertTrue($this->region->contains($key2)); - $this->assertCount(2, $this->region); $this->region->evictAll(); - $this->assertCount(0, $this->region); $this->assertFalse($this->region->contains($key1)); $this->assertFalse($this->region->contains($key2)); } - - public function testToArray() - { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - - $this->assertCount(0, $this->region); - $this->assertEquals(array(), $this->region->toArray()); - - $this->region->put($key1, array('value' => 'foo')); - $this->region->put($key2, array('value' => 'bar')); - - $array = $this->region->toArray(); - - $this->assertCount(2, $array); - - $this->assertArrayHasKey('value', $array[0]); - $this->assertArrayHasKey('value', $array[1]); - - $this->assertEquals('foo', $array[0]['value']); - $this->assertEquals('bar', $array[1]['value']); - } } class DefaultRegionTestKey implements \Doctrine\ORM\Cache\CacheKey From ea84b4d76eb120835746b1531f73b38593c50a89 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 2 Apr 2013 05:25:08 -0300 Subject: [PATCH 13/71] Configurable cache interface --- lib/Doctrine/ORM/Cache.php | 222 +---------- lib/Doctrine/ORM/Cache/DefaultCache.php | 359 ++++++++++++++++++ lib/Doctrine/ORM/Configuration.php | 32 ++ lib/Doctrine/ORM/EntityManager.php | 3 +- lib/Doctrine/ORM/ORMException.php | 10 + .../Doctrine/Tests/ORM/ConfigurationTest.php | 15 + 6 files changed, 439 insertions(+), 202 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache/DefaultCache.php diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index bebd3184c35..2cf9a034c55 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -21,11 +21,7 @@ namespace Doctrine\ORM; use Doctrine\ORM\EntityManager; -use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\ORMInvalidArgumentException; /** * Provides an API for querying/managing the second level cache regions. @@ -33,44 +29,21 @@ * @since 2.5 * @author Fabio B. Silva */ -class Cache +interface Cache { - - /** - * @var \Doctrine\ORM\EntityManager - */ - private $em; - - /** - * @var \Doctrine\ORM\UnitOfWork - */ - private $uow; - /** + * Construct + * * @param \Doctrine\ORM\EntityManager $em */ - public function __construct(EntityManager $em) - { - $this->em = $em; - $this->uow = $this->em->getUnitOfWork(); - } + public function __construct(EntityManager $em); /** * @param string $className The entity class. * * @return \Doctrine\ORM\Cache\RegionAccess|null */ - public function getEntityCacheRegionAcess($className) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - - if ( ! $persister->hasCache()) { - return null; - } - - return $persister->getCacheRegionAcess(); - } + public function getEntityCacheRegionAcess($className); /** * @param string $className The entity class. @@ -78,17 +51,7 @@ public function getEntityCacheRegionAcess($className) * * @return \Doctrine\ORM\Cache\RegionAccess|null */ - public function getCollectionCacheRegionAcess($className, $association) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); - - if ( ! $persister->hasCache()) { - return null; - } - - return $persister->getCacheRegionAcess(); - } + public function getCollectionCacheRegionAcess($className, $association); /** * Determine whether the cache contains data for the given entity "instance". @@ -98,18 +61,7 @@ public function getCollectionCacheRegionAcess($className, $association) * * @return boolean true if the underlying cache contains corresponding data; false otherwise. */ - public function containsEntity($className, $identifier) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - $key = $this->buildEntityCacheKey($metadata, $identifier); - - if ( ! $persister->hasCache()) { - return false; - } - - return $persister->getCacheRegionAcess()->getRegion()->contains($key); - } + public function containsEntity($className, $identifier); /** * Evicts the entity data for a particular entity "instance". @@ -119,18 +71,7 @@ public function containsEntity($className, $identifier) * * @return void */ - public function evictEntity($className, $identifier) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - $key = $this->buildEntityCacheKey($metadata, $identifier); - - if ( ! $persister->hasCache()) { - return; - } - - $persister->getCacheRegionAcess()->evict($key); - } + public function evictEntity($className, $identifier); /** * Evicts all entity data from the given region. @@ -139,37 +80,14 @@ public function evictEntity($className, $identifier) * * @return void */ - public function evictEntityRegion($className) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - - if ( ! $persister->hasCache()) { - return; - } - - $persister->getCacheRegionAcess()->evictAll(); - } + public function evictEntityRegion($className); /** * Evict data from all entity regions. * * @return void */ - public function evictEntityRegions() - { - $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); - - foreach ($metadatas as $metadata) { - $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - - if ( ! $persister->hasCache()) { - continue; - } - - $persister->getCacheRegionAcess()->evictAll(); - } - } + public function evictEntityRegions(); /** * Determine whether the cache contains data for the given collection. @@ -180,18 +98,7 @@ public function evictEntityRegions() * * @return boolean true if the underlying cache contains corresponding data; false otherwise. */ - public function containsCollection($className, $association, $ownerIdentifier) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); - $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); - - if ( ! $persister->hasCache()) { - return false; - } - - return $persister->getCacheRegionAcess()->getRegion()->contains($key); - } + public function containsCollection($className, $association, $ownerIdentifier); /** * Evicts the cache data for the given identified collection instance. @@ -202,18 +109,7 @@ public function containsCollection($className, $association, $ownerIdentifier) * * @return void */ - public function evictCollection($className, $association, $ownerIdentifier) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); - $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); - - if ( ! $persister->hasCache()) { - return; - } - - $persister->getCacheRegionAcess()->evict($key); - } + public function evictCollection($className, $association, $ownerIdentifier); /** * Evicts all entity data from the given region. @@ -223,44 +119,14 @@ public function evictCollection($className, $association, $ownerIdentifier) * * @return void */ - public function evictCollectionRegion($className, $association) - { - $metadata = $this->em->getClassMetadata($className); - $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); - - if ( ! $persister->hasCache()) { - return; - } - - $persister->getCacheRegionAcess()->evictAll(); - } + public function evictCollectionRegion($className, $association); /** * Evict data from all collection regions. * * @return void */ - public function evictCollectionRegions() - { - $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); - - foreach ($metadatas as $metadata) { - - foreach ($metadata->associationMappings as $association) { - if ( ! $association['type'] & ClassMetadata::TO_MANY) { - continue; - } - - $persister = $this->uow->getCollectionPersister($association); - - if ( ! $persister->hasCache()) { - return; - } - - $persister->getCacheRegionAcess()->evictAll(); - } - } - } + public function evictCollectionRegions(); /** * Determine whether the cache contains data for the given query. @@ -269,30 +135,21 @@ public function evictCollectionRegions() * * @return boolean true if the underlying cache contains corresponding data; false otherwise. */ - public function containsQuery($regionName) - { - throw new \BadMethodCallException("Not implemented."); - } + public function containsQuery($regionName); /** * Evicts all cached query results under the given name. * * @param string $regionName The cache name associated to the queries being cached. */ - public function evictQueryRegion($regionName) - { - throw new \BadMethodCallException("Not implemented."); - } + public function evictQueryRegion($regionName); /** * Evict data from all query regions. * * @return void */ - public function evictQueryRegions() - { - throw new \BadMethodCallException("Not implemented."); - } + public function evictQueryRegions(); /** * Get query cache by region name or create a new one if none exist. @@ -301,10 +158,7 @@ public function evictQueryRegions() * * @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. */ - public function getQueryCache($regionName) - { - throw new \BadMethodCallException("Not implemented."); - } + public function getQueryCache($regionName); /** * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. @@ -312,14 +166,7 @@ public function getQueryCache($regionName) * * @return \Doctrine\ORM\Cache\EntityCacheKey */ - public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) - { - if ( ! is_array($identifier)) { - $identifier = $this->toIdentifierArray($metadata, $identifier); - } - - return new EntityCacheKey($metadata->rootEntityName, $identifier); - } + public function buildEntityCacheKey(ClassMetadata $metadata, $identifier); /** * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. @@ -328,32 +175,5 @@ public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) * * @return \Doctrine\ORM\Cache\CollectionCacheKey */ - public function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier) - { - if ( ! is_array($ownerIdentifier)) { - $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);; - } - - return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); - } - - /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param mixed $identifier The entity identifier. - * - * @return array - */ - private function toIdentifierArray(ClassMetadata $metadata, $identifier) - { - if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) { - $identifier = $this->unitOfWork->getSingleIdentifierValue($identifier); - - if ($identifier === null) { - throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); - } - } - - return array($metadata->identifier[0] => $identifier); - } - -} + public function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier); +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php new file mode 100644 index 00000000000..755576695e5 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -0,0 +1,359 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Cache; +use Doctrine\ORM\EntityManager; +use Doctrine\Common\Util\ClassUtils; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\ORMInvalidArgumentException; + +/** + * Provides an API for querying/managing the second level cache regions. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class DefaultCache implements Cache +{ + /** + * @var \Doctrine\ORM\EntityManager + */ + private $em; + + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @param \Doctrine\ORM\EntityManager $em + */ + public function __construct(EntityManager $em) + { + $this->em = $em; + $this->uow = $this->em->getUnitOfWork(); + } + + /** + * @param string $className The entity class. + * + * @return \Doctrine\ORM\Cache\RegionAccess|null + */ + public function getEntityCacheRegionAcess($className) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if ( ! $persister->hasCache()) { + return null; + } + + return $persister->getCacheRegionAcess(); + } + + /** + * @param string $className The entity class. + * @param string $association The field name that represents the association. + * + * @return \Doctrine\ORM\Cache\RegionAccess|null + */ + public function getCollectionCacheRegionAcess($className, $association) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if ( ! $persister->hasCache()) { + return null; + } + + return $persister->getCacheRegionAcess(); + } + + /** + * Determine whether the cache contains data for the given entity "instance". + * + * @param string $className The entity class. + * @param mixed $identifier The entity identifier + * + * @return boolean true if the underlying cache contains corresponding data; false otherwise. + */ + public function containsEntity($className, $identifier) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + $key = $this->buildEntityCacheKey($metadata, $identifier); + + if ( ! $persister->hasCache()) { + return false; + } + + return $persister->getCacheRegionAcess()->getRegion()->contains($key); + } + + /** + * Evicts the entity data for a particular entity "instance". + * + * @param string $className The entity class. + * @param mixed $identifier The entity identifier. + * + * @return void + */ + public function evictEntity($className, $identifier) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + $key = $this->buildEntityCacheKey($metadata, $identifier); + + if ( ! $persister->hasCache()) { + return; + } + + $persister->getCacheRegionAcess()->evict($key); + } + + /** + * Evicts all entity data from the given region. + * + * @param string $className The entity metadata. + * + * @return void + */ + public function evictEntityRegion($className) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if ( ! $persister->hasCache()) { + return; + } + + $persister->getCacheRegionAcess()->evictAll(); + } + + /** + * Evict data from all entity regions. + * + * @return void + */ + public function evictEntityRegions() + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + $persister = $this->uow->getEntityPersister($metadata->rootEntityName); + + if ( ! $persister->hasCache()) { + continue; + } + + $persister->getCacheRegionAcess()->evictAll(); + } + } + + /** + * Determine whether the cache contains data for the given collection. + * + * @param string $className The entity class. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + * + * @return boolean true if the underlying cache contains corresponding data; false otherwise. + */ + public function containsCollection($className, $association, $ownerIdentifier) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); + + if ( ! $persister->hasCache()) { + return false; + } + + return $persister->getCacheRegionAcess()->getRegion()->contains($key); + } + + /** + * Evicts the cache data for the given identified collection instance. + * + * @param string $className The entity class. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + * + * @return void + */ + public function evictCollection($className, $association, $ownerIdentifier) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); + + if ( ! $persister->hasCache()) { + return; + } + + $persister->getCacheRegionAcess()->evict($key); + } + + /** + * Evicts all entity data from the given region. + * + * @param string $className The entity class. + * @param string $association The field name that represents the association. + * + * @return void + */ + public function evictCollectionRegion($className, $association) + { + $metadata = $this->em->getClassMetadata($className); + $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); + + if ( ! $persister->hasCache()) { + return; + } + + $persister->getCacheRegionAcess()->evictAll(); + } + + /** + * Evict data from all collection regions. + * + * @return void + */ + public function evictCollectionRegions() + { + $metadatas = $this->em->getMetadataFactory()->getAllMetadata(); + + foreach ($metadatas as $metadata) { + + foreach ($metadata->associationMappings as $association) { + if ( ! $association['type'] & ClassMetadata::TO_MANY) { + continue; + } + + $persister = $this->uow->getCollectionPersister($association); + + if ( ! $persister->hasCache()) { + return; + } + + $persister->getCacheRegionAcess()->evictAll(); + } + } + } + + /** + * Determine whether the cache contains data for the given query. + * + * @param string $regionName The cache name given to the query. + * + * @return boolean true if the underlying cache contains corresponding data; false otherwise. + */ + public function containsQuery($regionName) + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * Evicts all cached query results under the given name. + * + * @param string $regionName The cache name associated to the queries being cached. + */ + public function evictQueryRegion($regionName) + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * Evict data from all query regions. + * + * @return void + */ + public function evictQueryRegions() + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * Get query cache by region name or create a new one if none exist. + * + * @param regionName Query cache region name. + * + * @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. + */ + public function getQueryCache($regionName) + { + throw new \BadMethodCallException("Not implemented."); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param mixed $identifier The entity identifier. + * + * @return \Doctrine\ORM\Cache\EntityCacheKey + */ + public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) + { + if ( ! is_array($identifier)) { + $identifier = $this->toIdentifierArray($metadata, $identifier); + } + + return new EntityCacheKey($metadata->rootEntityName, $identifier); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + * + * @return \Doctrine\ORM\Cache\CollectionCacheKey + */ + public function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier) + { + if ( ! is_array($ownerIdentifier)) { + $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);; + } + + return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); + } + + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param mixed $identifier The entity identifier. + * + * @return array + */ + private function toIdentifierArray(ClassMetadata $metadata, $identifier) + { + if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) { + $identifier = $this->unitOfWork->getSingleIdentifierValue($identifier); + + if ($identifier === null) { + throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); + } + } + + return array($metadata->identifier[0] => $identifier); + } + +} diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 968bdf20ba5..dc8598e647a 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -737,6 +737,38 @@ public function getDefaultRepositoryClassName() : 'Doctrine\ORM\EntityRepository'; } + /** + * @since 2.5 + * + * @param string $className + * + * @return void + * + * @throws ORMException If not is a \Doctrine\ORM\Cache + */ + public function setSecondLevelCacheClassName($className) + { + $reflectionClass = new \ReflectionClass($className); + + if ( ! $reflectionClass->implementsInterface('Doctrine\ORM\Cache')) { + throw ORMException::invalidSecondLevelCache($className); + } + + $this->_attributes['secondLevelCacheClassName'] = $className; + } + + /** + * @since 2.5 + * + * @return string A \Doctrine\ORM\Cache implementation + */ + public function getSecondLevelCacheClassName() + { + return isset($this->_attributes['secondLevelCacheClassName']) + ? $this->_attributes['secondLevelCacheClassName'] + : 'Doctrine\ORM\Cache\DefaultCache'; + } + /** * Sets naming strategy. * diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index af01229c1d8..675e168ad9d 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -169,7 +169,8 @@ protected function __construct(Connection $conn, Configuration $config, EventMan ); if ($config->isSecondLevelCacheEnabled()) { - $this->cache = new Cache($this); + $cacheClass = $config->getSecondLevelCacheClassName(); + $this->cache = new $cacheClass($this); } } diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index 99333f034dc..731c5baeb0c 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -248,6 +248,16 @@ public static function invalidEntityRepository($className) return new self("Invalid repository class '".$className."'. It must be a Doctrine\Common\Persistence\ObjectRepository."); } + /** + * @param string $className + * + * @return ORMException + */ + public static function invalidSecondLevelCache($className) + { + return new self(sprintf('Invalid repository class "%s". It must be a Doctrine\ORM\Cache.', $className)); + } + /** * @param string $className * @param string $fieldName diff --git a/tests/Doctrine/Tests/ORM/ConfigurationTest.php b/tests/Doctrine/Tests/ORM/ConfigurationTest.php index 8405c30358b..1530a05a356 100644 --- a/tests/Doctrine/Tests/ORM/ConfigurationTest.php +++ b/tests/Doctrine/Tests/ORM/ConfigurationTest.php @@ -272,6 +272,21 @@ public function testSetGetEntityListenerResolver() $this->configuration->setEntityListenerResolver($resolver); $this->assertSame($resolver, $this->configuration->getEntityListenerResolver()); } + + /** + * @group DDC-2183 + */ + public function testSetGetSecondLevelCacheClassName() + { + $mockClass = get_class($this->getMock('Doctrine\ORM\Cache')); + + $this->assertEquals('Doctrine\ORM\Cache\DefaultCache', $this->configuration->getSecondLevelCacheClassName()); + $this->configuration->setSecondLevelCacheClassName($mockClass); + $this->assertEquals($mockClass, $this->configuration->getSecondLevelCacheClassName()); + + $this->setExpectedException('Doctrine\ORM\ORMException'); + $this->configuration->setSecondLevelCacheClassName(__CLASS__); + } } class ConfigurationTestAnnotationReaderChecker From af77be3bbba70b6b14835a1234720d10646d8004 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 2 Apr 2013 06:44:38 -0300 Subject: [PATCH 14/71] test DefaultCache --- lib/Doctrine/ORM/Cache.php | 4 +- lib/Doctrine/ORM/Cache/DefaultCache.php | 97 ++------- .../Tests/ORM/Cache/DefaultCacheTest.php | 197 ++++++++++++++++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 4 +- 4 files changed, 221 insertions(+), 81 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index 2cf9a034c55..c56f5f49f99 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -43,7 +43,7 @@ public function __construct(EntityManager $em); * * @return \Doctrine\ORM\Cache\RegionAccess|null */ - public function getEntityCacheRegionAcess($className); + public function getEntityCacheRegionAccess($className); /** * @param string $className The entity class. @@ -51,7 +51,7 @@ public function getEntityCacheRegionAcess($className); * * @return \Doctrine\ORM\Cache\RegionAccess|null */ - public function getCollectionCacheRegionAcess($className, $association); + public function getCollectionCacheRegionAccess($className, $association); /** * Determine whether the cache contains data for the given entity "instance". diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 755576695e5..90c792548e9 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -47,7 +47,7 @@ class DefaultCache implements Cache private $uow; /** - * @param \Doctrine\ORM\EntityManager $em + * {@inheritdoc} */ public function __construct(EntityManager $em) { @@ -56,11 +56,9 @@ public function __construct(EntityManager $em) } /** - * @param string $className The entity class. - * - * @return \Doctrine\ORM\Cache\RegionAccess|null + * {@inheritdoc} */ - public function getEntityCacheRegionAcess($className) + public function getEntityCacheRegionAccess($className) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); @@ -73,12 +71,9 @@ public function getEntityCacheRegionAcess($className) } /** - * @param string $className The entity class. - * @param string $association The field name that represents the association. - * - * @return \Doctrine\ORM\Cache\RegionAccess|null + * {@inheritdoc} */ - public function getCollectionCacheRegionAcess($className, $association) + public function getCollectionCacheRegionAccess($className, $association) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); @@ -91,12 +86,7 @@ public function getCollectionCacheRegionAcess($className, $association) } /** - * Determine whether the cache contains data for the given entity "instance". - * - * @param string $className The entity class. - * @param mixed $identifier The entity identifier - * - * @return boolean true if the underlying cache contains corresponding data; false otherwise. + * {@inheritdoc} */ public function containsEntity($className, $identifier) { @@ -112,12 +102,7 @@ public function containsEntity($className, $identifier) } /** - * Evicts the entity data for a particular entity "instance". - * - * @param string $className The entity class. - * @param mixed $identifier The entity identifier. - * - * @return void + * {@inheritdoc} */ public function evictEntity($className, $identifier) { @@ -133,11 +118,7 @@ public function evictEntity($className, $identifier) } /** - * Evicts all entity data from the given region. - * - * @param string $className The entity metadata. - * - * @return void + * {@inheritdoc} */ public function evictEntityRegion($className) { @@ -152,9 +133,7 @@ public function evictEntityRegion($className) } /** - * Evict data from all entity regions. - * - * @return void + * {@inheritdoc} */ public function evictEntityRegions() { @@ -172,13 +151,7 @@ public function evictEntityRegions() } /** - * Determine whether the cache contains data for the given collection. - * - * @param string $className The entity class. - * @param string $association The field name that represents the association. - * @param mixed $ownerIdentifier The identifier of the owning entity. - * - * @return boolean true if the underlying cache contains corresponding data; false otherwise. + * {@inheritdoc} */ public function containsCollection($className, $association, $ownerIdentifier) { @@ -194,13 +167,7 @@ public function containsCollection($className, $association, $ownerIdentifier) } /** - * Evicts the cache data for the given identified collection instance. - * - * @param string $className The entity class. - * @param string $association The field name that represents the association. - * @param mixed $ownerIdentifier The identifier of the owning entity. - * - * @return void + * {@inheritdoc} */ public function evictCollection($className, $association, $ownerIdentifier) { @@ -216,12 +183,7 @@ public function evictCollection($className, $association, $ownerIdentifier) } /** - * Evicts all entity data from the given region. - * - * @param string $className The entity class. - * @param string $association The field name that represents the association. - * - * @return void + * {@inheritdoc} */ public function evictCollectionRegion($className, $association) { @@ -236,9 +198,7 @@ public function evictCollectionRegion($className, $association) } /** - * Evict data from all collection regions. - * - * @return void + * {@inheritdoc} */ public function evictCollectionRegions() { @@ -263,11 +223,7 @@ public function evictCollectionRegions() } /** - * Determine whether the cache contains data for the given query. - * - * @param string $regionName The cache name given to the query. - * - * @return boolean true if the underlying cache contains corresponding data; false otherwise. + * {@inheritdoc} */ public function containsQuery($regionName) { @@ -275,9 +231,7 @@ public function containsQuery($regionName) } /** - * Evicts all cached query results under the given name. - * - * @param string $regionName The cache name associated to the queries being cached. + * {@inheritdoc} */ public function evictQueryRegion($regionName) { @@ -285,9 +239,7 @@ public function evictQueryRegion($regionName) } /** - * Evict data from all query regions. - * - * @return void + * {@inheritdoc} */ public function evictQueryRegions() { @@ -295,11 +247,7 @@ public function evictQueryRegions() } /** - * Get query cache by region name or create a new one if none exist. - * - * @param regionName Query cache region name. - * - * @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. + * {@inheritdoc} */ public function getQueryCache($regionName) { @@ -307,10 +255,7 @@ public function getQueryCache($regionName) } /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param mixed $identifier The entity identifier. - * - * @return \Doctrine\ORM\Cache\EntityCacheKey + * {@inheritdoc} */ public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) { @@ -322,11 +267,7 @@ public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) } /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param string $association The field name that represents the association. - * @param mixed $ownerIdentifier The identifier of the owning entity. - * - * @return \Doctrine\ORM\Cache\CollectionCacheKey + * {@inheritdoc} */ public function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier) { diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php new file mode 100644 index 00000000000..f69d849cea6 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -0,0 +1,197 @@ +cache = new DefaultCache($this->_em); + } + + private function putEntityCache($className, array $identifier, array $cacheEntry) + { + $metadata = $this->_em->getClassMetadata($className); + $cacheKey = new EntityCacheKey($metadata->rootEntityName, $identifier); + + $this->cache->getEntityCacheRegionAccess($metadata->rootEntityName) + ->put($cacheKey, $cacheEntry); + } + + private function putCollectionCache($className, $association, array $ownerIdentifier, array $cacheEntry) + { + $metadata = $this->_em->getClassMetadata($className); + $cacheKey = new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); + + $this->cache->getCollectionCacheRegionAccess($className, $association) + ->put($cacheKey, $cacheEntry); + } + + public function testImplementsCache() + { + $this->assertInstanceOf('Doctrine\ORM\Cache', $this->cache); + } + + public function testGetEntityCacheRegionAccess() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccess', $this->cache->getEntityCacheRegionAccess(State::CLASSNAME)); + $this->assertNull($this->cache->getEntityCacheRegionAccess('Doctrine\Tests\Models\CMS\CmsUser')); + } + + public function testGetCollectionCacheRegionAccess() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccess', $this->cache->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')); + $this->assertNull($this->cache->getCollectionCacheRegionAccess('Doctrine\Tests\Models\CMS\CmsUser', 'phonenumbers')); + } + + public function testContainsEntity() + { + $identifier = array('id'=>1); + $className = Country::CLASSNAME; + $cacheEntry = array_merge($identifier, array('name' => 'Brazil')); + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1)); + + $this->putEntityCache($className, $identifier, $cacheEntry); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); + } + + public function testEvictEntity() + { + $identifier = array('id'=>1); + $className = Country::CLASSNAME; + $cacheEntry = array_merge($identifier, array('name' => 'Brazil')); + + $this->putEntityCache($className, $identifier, $cacheEntry); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); + + $this->cache->evictEntity(Country::CLASSNAME, 1); + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1)); + } + + public function testEvictEntityRegion() + { + $identifier = array('id'=>1); + $className = Country::CLASSNAME; + $cacheEntry = array_merge($identifier, array('name' => 'Brazil')); + + $this->putEntityCache($className, $identifier, $cacheEntry); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); + + $this->cache->evictEntityRegion(Country::CLASSNAME); + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1)); + } + + public function testEvictEntityRegions() + { + $identifier = array('id'=>1); + $className = Country::CLASSNAME; + $cacheEntry = array_merge($identifier, array('name' => 'Brazil')); + + $this->putEntityCache($className, $identifier, $cacheEntry); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); + + $this->cache->evictEntityRegions(); + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1)); + } + + public function testContainsCollection() + { + $ownerId = array('id'=>1); + $className = State::CLASSNAME; + $association = 'cities'; + $cacheEntry = array( + array('id' => 11), + array('id' => 12), + ); + + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + + $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + } + + public function testEvictCollection() + { + $ownerId = array('id'=>1); + $className = State::CLASSNAME; + $association = 'cities'; + $cacheEntry = array( + array('id' => 11), + array('id' => 12), + ); + + $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + + $this->cache->evictCollection($className, $association, $ownerId); + + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + } + + public function testEvictCollectionRegion() + { + $ownerId = array('id'=>1); + $className = State::CLASSNAME; + $association = 'cities'; + $cacheEntry = array( + array('id' => 11), + array('id' => 12), + ); + + $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + + $this->cache->evictCollectionRegion($className, $association); + + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + } + + public function testEvictCollectionRegions() + { + $ownerId = array('id'=>1); + $className = State::CLASSNAME; + $association = 'cities'; + $cacheEntry = array( + array('id' => 11), + array('id' => 12), + ); + + $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + + $this->cache->evictCollectionRegions(); + + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + } + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index bc4766f2dbf..1127928a0f5 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -445,7 +445,9 @@ protected function _getEntityManager($config = null, $eventManager = null) { $config->setSecondLevelCacheAccessProvider($this->secondLevelCacheAccessProvider); } - $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array(), true)); + $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array( + realpath(__DIR__ . '/Models/Cache') + ), true)); $conn = static::$_sharedConn; $conn->getConfiguration()->setSQLLogger($this->_sqlLoggerStack); From 85e23d6694ba94d8dbb1221c70963d47cb49e27c Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 2 Apr 2013 07:02:24 -0300 Subject: [PATCH 15/71] drop cache key api from cache interface --- lib/Doctrine/ORM/Cache.php | 18 ------------------ lib/Doctrine/ORM/Cache/DefaultCache.php | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index c56f5f49f99..8b539b0e0a4 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -21,7 +21,6 @@ namespace Doctrine\ORM; use Doctrine\ORM\EntityManager; -use Doctrine\ORM\Mapping\ClassMetadata; /** * Provides an API for querying/managing the second level cache regions. @@ -159,21 +158,4 @@ public function evictQueryRegions(); * @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. */ public function getQueryCache($regionName); - - /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param mixed $identifier The entity identifier. - * - * @return \Doctrine\ORM\Cache\EntityCacheKey - */ - public function buildEntityCacheKey(ClassMetadata $metadata, $identifier); - - /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param string $association The field name that represents the association. - * @param mixed $ownerIdentifier The identifier of the owning entity. - * - * @return \Doctrine\ORM\Cache\CollectionCacheKey - */ - public function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 90c792548e9..59b9deade5d 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -254,10 +254,13 @@ public function getQueryCache($regionName) throw new \BadMethodCallException("Not implemented."); } - /** - * {@inheritdoc} + /** + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param mixed $identifier The entity identifier. + * + * @return \Doctrine\ORM\Cache\EntityCacheKey */ - public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) + private function buildEntityCacheKey(ClassMetadata $metadata, $identifier) { if ( ! is_array($identifier)) { $identifier = $this->toIdentifierArray($metadata, $identifier); @@ -267,9 +270,13 @@ public function buildEntityCacheKey(ClassMetadata $metadata, $identifier) } /** - * {@inheritdoc} + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param string $association The field name that represents the association. + * @param mixed $ownerIdentifier The identifier of the owning entity. + * + * @return \Doctrine\ORM\Cache\CollectionCacheKey */ - public function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier) + private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier) { if ( ! is_array($ownerIdentifier)) { $ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);; From 29ef4e7ea4d8e90c6c18d8a1cacf73eff4767b1e Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Tue, 2 Apr 2013 07:14:51 -0300 Subject: [PATCH 16/71] refactoring default cache test --- .../Tests/ORM/Cache/DefaultCacheTest.php | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index f69d849cea6..2cef7b0d88b 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -27,22 +27,33 @@ protected function setUp() $this->cache = new DefaultCache($this->_em); } - private function putEntityCache($className, array $identifier, array $cacheEntry) + /** + * @param string $className + * @param array $identifier + * @param array $cacheEntry + */ + private function putEntityCacheEntry($className, array $identifier, array $cacheEntry) { $metadata = $this->_em->getClassMetadata($className); $cacheKey = new EntityCacheKey($metadata->rootEntityName, $identifier); + $persister = $this->_em->getUnitOfWork()->getEntityPersister($metadata->rootEntityName); - $this->cache->getEntityCacheRegionAccess($metadata->rootEntityName) - ->put($cacheKey, $cacheEntry); + $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); } - private function putCollectionCache($className, $association, array $ownerIdentifier, array $cacheEntry) + /** + * @param string $className + * @param string $association + * @param array $ownerIdentifier + * @param array $cacheEntry + */ + private function putCollectionCacheEntry($className, $association, array $ownerIdentifier, array $cacheEntry) { $metadata = $this->_em->getClassMetadata($className); $cacheKey = new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); + $persister = $this->_em->getUnitOfWork()->getCollectionPersister($metadata->getAssociationMapping($association)); - $this->cache->getCollectionCacheRegionAccess($className, $association) - ->put($cacheKey, $cacheEntry); + $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); } public function testImplementsCache() @@ -70,7 +81,7 @@ public function testContainsEntity() $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1)); - $this->putEntityCache($className, $identifier, $cacheEntry); + $this->putEntityCacheEntry($className, $identifier, $cacheEntry); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); } @@ -81,7 +92,7 @@ public function testEvictEntity() $className = Country::CLASSNAME; $cacheEntry = array_merge($identifier, array('name' => 'Brazil')); - $this->putEntityCache($className, $identifier, $cacheEntry); + $this->putEntityCacheEntry($className, $identifier, $cacheEntry); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); @@ -96,7 +107,7 @@ public function testEvictEntityRegion() $className = Country::CLASSNAME; $cacheEntry = array_merge($identifier, array('name' => 'Brazil')); - $this->putEntityCache($className, $identifier, $cacheEntry); + $this->putEntityCacheEntry($className, $identifier, $cacheEntry); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); @@ -111,7 +122,7 @@ public function testEvictEntityRegions() $className = Country::CLASSNAME; $cacheEntry = array_merge($identifier, array('name' => 'Brazil')); - $this->putEntityCache($className, $identifier, $cacheEntry); + $this->putEntityCacheEntry($className, $identifier, $cacheEntry); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); @@ -132,7 +143,7 @@ public function testContainsCollection() $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); - $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + $this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); } @@ -147,7 +158,7 @@ public function testEvictCollection() array('id' => 12), ); - $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + $this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); @@ -166,7 +177,7 @@ public function testEvictCollectionRegion() array('id' => 12), ); - $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + $this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); @@ -185,7 +196,7 @@ public function testEvictCollectionRegions() array('id' => 12), ); - $this->putCollectionCache($className, $association, $ownerId, $cacheEntry); + $this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); From 43c0d4457f3ae7a062a0c03df8263ea1221d9d58 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Wed, 10 Apr 2013 12:24:44 -0300 Subject: [PATCH 17/71] Basic interaction between query and second level cache --- lib/Doctrine/ORM/Cache/AccessProvider.php | 13 +- .../ORM/Cache/CacheAccessProvider.php | 11 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 114 ++++++++++++++++++ lib/Doctrine/ORM/Cache/QueryCache.php | 14 ++- lib/Doctrine/ORM/Cache/QueryCacheKey.php | 20 ++- .../ORM/Decorator/EntityManagerDecorator.php | 8 ++ lib/Doctrine/ORM/EntityManagerInterface.php | 1 + lib/Doctrine/ORM/Query.php | 112 ++++++++++++++++- .../SecondLevelCacheQueryCacheTest.php | 45 +++++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 10 +- 10 files changed, 332 insertions(+), 16 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache/DefaultQueryCache.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php diff --git a/lib/Doctrine/ORM/Cache/AccessProvider.php b/lib/Doctrine/ORM/Cache/AccessProvider.php index 89203bab7d5..73af5f4781b 100644 --- a/lib/Doctrine/ORM/Cache/AccessProvider.php +++ b/lib/Doctrine/ORM/Cache/AccessProvider.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManager; /** * @since 2.5 @@ -50,4 +51,14 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata); * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. */ public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName); -} + + /** + * @param \Doctrine\ORM\EntityManager $em The Entity manager. + * @param string $regionName The region name. + * + * @return \Doctrine\ORM\Cache\QueryCache The built query cache. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + */ + public function buildQueryCache(EntityManager $em, $regionName); +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/CacheAccessProvider.php b/lib/Doctrine/ORM/Cache/CacheAccessProvider.php index ace25f0cc78..49e47574877 100644 --- a/lib/Doctrine/ORM/Cache/CacheAccessProvider.php +++ b/lib/Doctrine/ORM/Cache/CacheAccessProvider.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Cache; +use Doctrine\ORM\EntityManager; use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\Region\DefaultRegion; @@ -82,4 +83,12 @@ public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fi throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); } -} + + /** + * {@inheritdoc} + */ + public function buildQueryCache(EntityManager $em, $regionName) + { + return new DefaultQueryCache($em, new DefaultRegion($regionName ?: 'query.cache.region', $this->cache)); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php new file mode 100644 index 00000000000..fb1970a25f8 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\EntityManager; + +/** + * Default query cache implementation. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class DefaultQueryCache implements QueryCache +{ + /** + * @var \Doctrine\ORM\EntityManager + */ + private $em; + + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @var \Doctrine\ORM\Cache\Region + */ + private $region; + + /** + * @param \Doctrine\ORM\EntityManager $em The entity manager. + * @param \Doctrine\ORM\Cache\Region $region + */ + public function __construct(EntityManager $em, Region $region) + { + $this->em = $em; + $this->region = $region; + $this->uow = $em->getUnitOfWork(); + } + + /** + * {@inheritdoc} + */ + public function get(QueryCacheKey $key, ResultSetMapping $rsm) + { + $data = $this->region->get($key); + + if (empty($data)) { + return null; + } + + $entityName = reset($rsm->aliasMap); //@TODO find root entity + $result = array(); + + foreach ($data as $index => $value) { + $result[$index] = $this->em->getReference($entityName, $value); + + //@TODO - handle associations ? + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) + { + $data = array(); + + foreach ($result as $index => $value) { + $data[$index] = $this->uow->getEntityIdentifier($value); + + //@TODO - handle associations ? + } + + return $this->region->put($key, $data);; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->region->evictAll(); + } + + /** + * {@inheritdoc} + */ + public function getRegion() + { + return $this->region; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/QueryCache.php b/lib/Doctrine/ORM/Cache/QueryCache.php index 79176fae930..f90d8dfa607 100644 --- a/lib/Doctrine/ORM/Cache/QueryCache.php +++ b/lib/Doctrine/ORM/Cache/QueryCache.php @@ -20,6 +20,8 @@ namespace Doctrine\ORM\Cache; +use Doctrine\ORM\Query\ResultSetMapping; + /** * Defines the contract for caches capable of storing query results. * These caches should only concern themselves with storing the matching result ids. @@ -35,17 +37,19 @@ interface QueryCache public function clear(); /** - * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param array $result + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\ResultSetMapping $rsm + * @param array $result * * @return boolean */ - public function put(QueryCacheKey $key, array $result); + public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result); /** - * @return array + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\ResultSetMapping $rsm */ - public function get(QueryCacheKey $key); + public function get(QueryCacheKey $key, ResultSetMapping $rsm); /** * @return \Doctrine\ORM\Cache\Region diff --git a/lib/Doctrine/ORM/Cache/QueryCacheKey.php b/lib/Doctrine/ORM/Cache/QueryCacheKey.php index 654f5e27ca8..b1a4c3b365c 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheKey.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheKey.php @@ -29,8 +29,24 @@ */ class QueryCacheKey implements CacheKey { + /** + * @var string + */ + private $hash; + + /** + * @param string $hash The result cache id + */ + public function __construct($hash) + { + $this->hash = $hash; + } + + /** + * {@inheritdoc} + */ public function hash() { - throw new \BadMethodCallException("Not implemented."); + return $this->hash; } -} +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php index dc123118f52..1a768f95330 100644 --- a/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php +++ b/lib/Doctrine/ORM/Decorator/EntityManagerDecorator.php @@ -268,4 +268,12 @@ public function hasFilters() { return $this->wrapped->hasFilters(); } + + /** + * {@inheritdoc} + */ + public function getCache() + { + return $this->wrapped->getCache(); + } } diff --git a/lib/Doctrine/ORM/EntityManagerInterface.php b/lib/Doctrine/ORM/EntityManagerInterface.php index d72f7cd0cf8..9599f1c5b36 100644 --- a/lib/Doctrine/ORM/EntityManagerInterface.php +++ b/lib/Doctrine/ORM/EntityManagerInterface.php @@ -31,6 +31,7 @@ */ interface EntityManagerInterface extends ObjectManager { + public function getCache(); public function getConnection(); public function getExpressionBuilder(); public function beginTransaction(); diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 71f5f5550ce..8b30ce86533 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -20,9 +20,8 @@ namespace Doctrine\ORM; use Doctrine\Common\Collections\ArrayCollection; - use Doctrine\DBAL\LockMode; - +use Doctrine\ORM\Cache\QueryCacheKey; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; @@ -178,6 +177,71 @@ final class Query extends AbstractQuery */ private $_useQueryCache = true; + /** + * Whether to use second level cache, if available. Defaults to TRUE. + * + * @var boolean + */ + protected $cacheable; + + /** + * Second level cache region name. + * + * @var string + */ + protected $cacheRegion; + + /** + * + * Enable/disable second level query (result) caching for this query. + * + * @param boolean $cacheable + * @return \Doctrine\ORM\Query + */ + public function setCacheable($cacheable) + { + $this->cacheable = (boolean) $cacheable; + + return $this; + } + + /** + * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise. + */ + public function isCacheable() + { + return $this->cacheable; + } + + /** + * @param string $cacheRegion + * @return \Doctrine\ORM\Query + */ + public function setCacheRegion($cacheRegion) + { + $this->cacheRegion = $cacheRegion; + + return $this; + } + + /** + * Obtain the name of the second level query cache region in which query results will be stored + * + * @return The cache region name; NULL indicates the default region. + */ + public function getCacheRegion() + { + return $this->cacheRegion; + } + + /** + * @return boolean TRUE if the query cache and second level cache are anabled, FALSE otherwise. + */ + protected function isCacheEnabled() + { + return $this->cacheable && $this->_em->getConfiguration()->isSecondLevelCacheEnabled(); + } + /** * Initializes a new Query instance. * @@ -259,6 +323,50 @@ private function _parse() return $this->_parserResult; } + /** + * {@inheritdoc} + */ + public function execute($parameters = null, $hydrationMode = null) + { + if ($this->cacheable && $this->isCacheEnabled()) { + return $this->executeUsingQueryCache($parameters, $hydrationMode); + } + + return parent::execute($parameters, $hydrationMode); + } + + /** + * Load from second level cache or executes the query and put into cache. + * + * @param ArrayCollection|array|null $parameters + * @param integer|null $hydrationMode + * + * @return mixed + */ + private function executeUsingQueryCache($parameters = null, $hydrationMode = null) + { + // parse query or load from cache + if ($this->_resultSetMapping === null) { + $this->_resultSetMapping = $this->_parse()->getResultSetMapping(); + } + + if ($this->_resultSetMapping->isMixed) { + throw new ORMException("Second level cache does not suport mixed results"); + } + + $queryCache = $this->_em->getConfiguration()->getSecondLevelCacheAccessProvider()->buildQueryCache($this->_em, $this->cacheRegion); + $querykey = new QueryCacheKey($this->_getQueryCacheId()); + $result = $queryCache->get($querykey, $this->_resultSetMapping); + + if ($result === null) { + $result = parent::execute($parameters, $hydrationMode); + + $queryCache->put($querykey, $this->_resultSetMapping, $result); + } + + return $result; + } + /** * {@inheritdoc} */ diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php new file mode 100644 index 00000000000..0d986b23a26 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -0,0 +1,45 @@ +loadFixturesCountries(); + $this->_em->clear(); + + $this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId()); + $this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId()); + + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $query1 = $this->_em->createQuery($dql)->setCacheable(true); + + $result1 = $query1->getResult(); + $queryCount = $this->getCurrentQueryCount(); + + $this->_em->clear(); + + $query2 = $this->_em->createQuery($dql)->setCacheable(true); + $result2 = $query2->getResult(); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + $this->assertCount(count($this->countries), $result2); + + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); + + $this->assertEquals($result1[0]->getId(), $result2[0]->getId()); + $this->assertEquals($result1[1]->getId(), $result2[1]->getId()); + + $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); + $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 1127928a0f5..5f0d4f9ca90 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -317,13 +317,13 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM taxi_driver'); } - if (isset($this->_usedModelSets['cache_country'])) { - $conn->executeUpdate('DELETE FROM cache_trip_city'); - $conn->executeUpdate('DELETE FROM cache_traveler'); + if (isset($this->_usedModelSets['cache'])) { + $conn->executeUpdate('DELETE FROM cache_visited_cities'); $conn->executeUpdate('DELETE FROM cache_travel'); - $conn->executeUpdate('DELETE FROM cache_country'); - $conn->executeUpdate('DELETE FROM cache_state'); + $conn->executeUpdate('DELETE FROM cache_traveler'); $conn->executeUpdate('DELETE FROM cache_city'); + $conn->executeUpdate('DELETE FROM cache_state'); + $conn->executeUpdate('DELETE FROM cache_country'); } $this->_em->clear(); From 84f670c9737d6a9cd5c594250a2176a1f4c380c4 Mon Sep 17 00:00:00 2001 From: fabios Date: Fri, 14 Jun 2013 18:20:28 -0400 Subject: [PATCH 18/71] add cache entry and configurable region lifetime --- lib/Doctrine/ORM/Cache.php | 14 +-- ...NonStrictReadWriteRegionAccessStrategy.php | 13 +-- .../ORM/Cache/Access/ReadOnlyRegionAccess.php | 3 +- lib/Doctrine/ORM/Cache/CacheEntry.php | 32 +++++++ .../{AccessProvider.php => CacheFactory.php} | 29 ++++-- .../ORM/Cache/CollectionCacheEntry.php | 43 +++++++++ .../ORM/Cache/CollectionEntryStructure.php | 60 ++---------- lib/Doctrine/ORM/Cache/DefaultCache.php | 63 ++++++++++--- ...ssProvider.php => DefaultCacheFactory.php} | 57 ++++++++--- .../Cache/DefaultCollectionEntryStructure.php | 86 +++++++++++++++++ .../ORM/Cache/DefaultEntityEntryStructure.php | 94 +++++++++++++++++++ lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 9 +- lib/Doctrine/ORM/Cache/EntityCacheEntry.php | 43 +++++++++ .../ORM/Cache/EntityEntryStructure.php | 69 ++------------ lib/Doctrine/ORM/Cache/QueryCacheEntry.php | 46 +++++++++ lib/Doctrine/ORM/Cache/Region.php | 8 +- .../ORM/Cache/Region/DefaultRegion.php | 15 +-- lib/Doctrine/ORM/Cache/RegionAccess.php | 26 ++--- lib/Doctrine/ORM/Configuration.php | 57 +++++++++-- lib/Doctrine/ORM/Mapping/Cache.php | 5 - .../ORM/Mapping/Driver/AnnotationDriver.php | 2 - .../AbstractCollectionPersister.php | 17 ++-- .../ORM/Persisters/BasicEntityPersister.php | 10 +- lib/Doctrine/ORM/Query.php | 2 +- .../ORM/Cache/AbstractRegionAccessTest.php | 35 +++---- .../Tests/ORM/Cache/DefaultCacheTest.php | 12 ++- ...> DefaultCollectionEntryStructureTest.php} | 20 ++-- ...hp => DefaultEntityEntryStructureTest.php} | 38 +++++--- .../Tests/ORM/Cache/DefaultRegionTest.php | 16 +++- .../ORM/Cache/ReadOnlyRegionAccessTest.php | 2 +- .../Doctrine/Tests/OrmFunctionalTestCase.php | 26 +++-- 31 files changed, 678 insertions(+), 274 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache/CacheEntry.php rename lib/Doctrine/ORM/Cache/{AccessProvider.php => CacheFactory.php} (68%) create mode 100644 lib/Doctrine/ORM/Cache/CollectionCacheEntry.php rename lib/Doctrine/ORM/Cache/{CacheAccessProvider.php => DefaultCacheFactory.php} (61%) create mode 100644 lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php create mode 100644 lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php create mode 100644 lib/Doctrine/ORM/Cache/EntityCacheEntry.php create mode 100644 lib/Doctrine/ORM/Cache/QueryCacheEntry.php rename tests/Doctrine/Tests/ORM/Cache/{CollectionEntryStructureTest.php => DefaultCollectionEntryStructureTest.php} (82%) rename tests/Doctrine/Tests/ORM/Cache/{EntityEntryStructureTest.php => DefaultEntityEntryStructureTest.php} (74%) diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index 8b539b0e0a4..f1c50189f79 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -20,7 +20,7 @@ namespace Doctrine\ORM; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; /** * Provides an API for querying/managing the second level cache regions. @@ -33,9 +33,9 @@ interface Cache /** * Construct * - * @param \Doctrine\ORM\EntityManager $em + * @param \Doctrine\ORMEntityManagerInterface $em */ - public function __construct(EntityManager $em); + public function __construct(EntityManagerInterface $em); /** * @param string $className The entity class. @@ -137,11 +137,11 @@ public function evictCollectionRegions(); public function containsQuery($regionName); /** - * Evicts all cached query results under the given name. + * Evicts all cached query results under the given name, or default query cache if the region name is NULL. * * @param string $regionName The cache name associated to the queries being cached. */ - public function evictQueryRegion($regionName); + public function evictQueryRegion($regionName = null); /** * Evict data from all query regions. @@ -153,9 +153,9 @@ public function evictQueryRegions(); /** * Get query cache by region name or create a new one if none exist. * - * @param regionName Query cache region name. + * @param regionName Query cache region name, or default query cache if the region name is NULL. * * @return \Doctrine\ORM\Cache\QueryCache The Query Cache associated with the region name. */ - public function getQueryCache($regionName); + public function getQueryCache($regionName = null); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php index 5789288d275..7e2601dce03 100644 --- a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php +++ b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteRegionAccessStrategy.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM\Cache\Access; use Doctrine\ORM\Cache\RegionAccess; +use Doctrine\ORM\Cache\CacheEntry; use Doctrine\ORM\Cache\CacheKey; use Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\Lock; @@ -57,17 +58,17 @@ public function getRegion() /** * {@inheritdoc} */ - public function afterInsert(CacheKey $key, array $value) + public function afterInsert(CacheKey $key, CacheEntry $entry) { - return $this->region->put($key, $value); + return $this->region->put($key, $entry); } /** * {@inheritdoc} */ - public function afterUpdate(CacheKey $key, array $value, Lock $lock = null) + public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null) { - return $this->region->put($key, $value); + return $this->region->put($key, $entry); } /** @@ -81,9 +82,9 @@ public function get(CacheKey $key) /** * {@inheritdoc} */ - public function put(CacheKey $key, array $value) + public function put(CacheKey $key, CacheEntry $entry) { - return $this->region->put($key, $value); + return $this->region->put($key, $entry); } /** diff --git a/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php b/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php index a3884f282f9..14698a96a67 100644 --- a/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php +++ b/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM\Cache\Access; use Doctrine\ORM\Cache\CacheException; +use Doctrine\ORM\Cache\CacheEntry; use Doctrine\ORM\Cache\CacheKey; use Doctrine\ORM\Cache\Lock; @@ -35,7 +36,7 @@ class ReadOnlyRegionAccess extends NonStrictReadWriteRegionAccessStrategy /** * {@inheritdoc} */ - public function afterUpdate(CacheKey $key, array $value, Lock $lock = null) + public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null) { throw CacheException::updateReadOnlyobject(); } diff --git a/lib/Doctrine/ORM/Cache/CacheEntry.php b/lib/Doctrine/ORM/Cache/CacheEntry.php new file mode 100644 index 00000000000..5b775615708 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CacheEntry.php @@ -0,0 +1,32 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Cache entry interface + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface CacheEntry +{ + +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/AccessProvider.php b/lib/Doctrine/ORM/Cache/CacheFactory.php similarity index 68% rename from lib/Doctrine/ORM/Cache/AccessProvider.php rename to lib/Doctrine/ORM/Cache/CacheFactory.php index 73af5f4781b..6ea5ba25a37 100644 --- a/lib/Doctrine/ORM/Cache/AccessProvider.php +++ b/lib/Doctrine/ORM/Cache/CacheFactory.php @@ -21,13 +21,13 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\Mapping\ClassMetadata; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; /** * @since 2.5 * @author Fabio B. Silva */ -interface AccessProvider +interface CacheFactory { /** * Build an entity RegionAccess for the input entity. @@ -53,12 +53,29 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata); public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName); /** - * @param \Doctrine\ORM\EntityManager $em The Entity manager. - * @param string $regionName The region name. + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param string $regionName The region name. * - * @return \Doctrine\ORM\Cache\QueryCache The built query cache. + * @return \Doctrine\ORM\Cache\QueryCache The built query cache, or default query cache if the region name is NULL. * * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. */ - public function buildQueryCache(EntityManager $em, $regionName); + public function buildQueryCache(EntityManagerInterface $em, $regionName = null); + + /** + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @return \Doctrine\ORM\Cache\EntityEntryStructure The built entity entry structure. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + */ + public function buildEntityEntryStructure(EntityManagerInterface $em); + + /** + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @return \Doctrine\ORM\Cache\CollectionEntryStructure The built collection entry structure. + * + * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + */ + public function buildCollectionEntryStructure(EntityManagerInterface $em); + } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php new file mode 100644 index 00000000000..a6dad8da1ef --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Collection cache entry + * + * @since 2.5 + * @author Fabio B. Silva + */ +class CollectionCacheEntry implements CacheEntry +{ + /** + * @var array + */ + public $dataList; + + /** + * @param array $dataList + */ + public function __construct(array $dataList) + { + $this->dataList = $dataList; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php index 6ac5d61fd89..9809cd3b3fb 100644 --- a/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php @@ -20,75 +20,35 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\EntityManager; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\CollectionCacheEntry; /** - * Structured cache entry for collection + * Structure cache entry for collections * * @since 2.5 * @author Fabio B. Silva */ -class CollectionEntryStructure +interface CollectionEntryStructure { - /** - * @var \Doctrine\ORM\EntityManager - */ - private $em; - - /** - * @var \Doctrine\ORM\UnitOfWork - */ - private $uow; - - /** - * @param \Doctrine\ORM\EntityManager $em The entity manager. - */ - public function __construct(EntityManager $em) - { - $this->em = $em; - $this->uow = $em->getUnitOfWork(); - } - /** * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key. * @param array|\Doctrine\Common\Collections\Collection $collection The collection. * - * @return array + * @return \Doctrine\ORM\Cache\CollectionCacheEntry */ - public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection) - { - $data = array(); - - foreach ($collection as $key => $entity) { - $data[$key] = $this->uow->getEntityIdentifier($entity); - } - - return $data; - } + public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection); /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata. - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key. - * @param array $cache Cached collection data. - * @param Doctrine\ORM\PersistentCollection $collection The collection to load the cache into. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key. + * @param \Doctrine\ORM\Cache\CollectionCacheEntry $entry The cached collection entry. + * @param Doctrine\ORM\PersistentCollection $collection The collection to load the cache into. * * @return array */ - public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array $cache, PersistentCollection $collection) - { - $list = array(); - - foreach ($cache as $key => $entry) { - $entity = $this->em->getReference($metadata->rootEntityName, $entry); - $list[$key] = $entity; - - $collection->hydrateSet($key, $entity); - } - - return $list; - } + public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection); } diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 59b9deade5d..ace7df97a52 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -21,10 +21,10 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache; -use Doctrine\ORM\EntityManager; use Doctrine\Common\Util\ClassUtils; -use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\ORMInvalidArgumentException; @@ -37,7 +37,7 @@ class DefaultCache implements Cache { /** - * @var \Doctrine\ORM\EntityManager + * @var \Doctrine\ORM\EntityManagerInterface */ private $em; @@ -46,13 +46,29 @@ class DefaultCache implements Cache */ private $uow; + /** + * @var \Doctrine\ORM\Cache\CacheFactory + */ + private $cacheFactory; + + /** + * @var array<\Doctrine\ORM\Cache\QueryCache> + */ + private $queryCaches; + + /** + * @var \Doctrine\ORM\Cache\QueryCache + */ + private $defaultQueryCache; + /** * {@inheritdoc} */ - public function __construct(EntityManager $em) + public function __construct(EntityManagerInterface $em) { - $this->em = $em; - $this->uow = $this->em->getUnitOfWork(); + $this->em = $em; + $this->uow = $em->getUnitOfWork(); + $this->cacheFactory = $em->getConfiguration()->getSecondLevelCacheFactory(); } /** @@ -227,15 +243,23 @@ public function evictCollectionRegions() */ public function containsQuery($regionName) { - throw new \BadMethodCallException("Not implemented."); + return isset($this->queryCaches[$regionName]); } /** * {@inheritdoc} */ - public function evictQueryRegion($regionName) + public function evictQueryRegion($regionName = null) { - throw new \BadMethodCallException("Not implemented."); + if ($regionName === null && $this->defaultQueryCache !== null) { + $this->defaultQueryCache->clear(); + + return; + } + + if (isset($this->queryCaches[$regionName])) { + $this->queryCaches[$regionName]->clear(); + } } /** @@ -243,15 +267,30 @@ public function evictQueryRegion($regionName) */ public function evictQueryRegions() { - throw new \BadMethodCallException("Not implemented."); + if ($this->defaultQueryCache !== null) { + $this->defaultQueryCache->clear(); + } + + foreach ($this->queryCaches as $queryCache) { + $queryCache->clear(); + } } /** * {@inheritdoc} */ - public function getQueryCache($regionName) + public function getQueryCache($regionName = null) { - throw new \BadMethodCallException("Not implemented."); + if ($regionName === null) { + return $this->defaultQueryCache ?: + $this->defaultQueryCache = $this->cacheFactory->buildQueryCache($this->em); + } + + if (isset($this->queryCaches[$regionName])) { + $this->queryCaches[$regionName] = $this->cacheFactory->buildQueryCache($this->em, $regionName); + } + + return $this->queryCaches[$regionName]; } /** diff --git a/lib/Doctrine/ORM/Cache/CacheAccessProvider.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php similarity index 61% rename from lib/Doctrine/ORM/Cache/CacheAccessProvider.php rename to lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index 49e47574877..425681b3589 100644 --- a/lib/Doctrine/ORM/Cache/CacheAccessProvider.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -20,9 +20,10 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Configuration; use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess; use Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy; @@ -31,16 +32,22 @@ * @since 2.5 * @author Fabio B. Silva */ -class CacheAccessProvider implements AccessProvider +class DefaultCacheFactory implements CacheFactory { /** * @var \Doctrine\Common\Cache\Cache */ private $cache; - public function __construct(Cache $cache) + /** + * @var \Doctrine\ORM\Configuration + */ + private $configuration; + + public function __construct(Configuration $configuration, Cache $cache) { - $this->cache = $cache; + $this->cache = $cache; + $this->configuration = $configuration; } /** @@ -48,16 +55,15 @@ public function __construct(Cache $cache) */ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) { - $properties = $metadata->cache['properties']; $regionName = $metadata->cache['region']; $usage = $metadata->cache['usage']; if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { - return new ReadOnlyRegionAccess(new DefaultRegion($regionName, $this->cache, $properties)); + return new ReadOnlyRegionAccess($this->createRegion($regionName)); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { - return new NonStrictReadWriteRegionAccessStrategy(new DefaultRegion($regionName, $this->cache, $properties)); + return new NonStrictReadWriteRegionAccessStrategy($this->createRegion($regionName)); } throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); @@ -69,16 +75,15 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) { $mapping = $metadata->getAssociationMapping($fieldName); - $properties = $mapping['cache']['properties']; $regionName = $mapping['cache']['region']; $usage = $mapping['cache']['usage']; if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { - return new ReadOnlyRegionAccess(new DefaultRegion($regionName, $this->cache, $properties)); + return new ReadOnlyRegionAccess($this->createRegion($regionName)); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { - return new NonStrictReadWriteRegionAccessStrategy(new DefaultRegion($regionName, $this->cache, $properties)); + return new NonStrictReadWriteRegionAccessStrategy($this->createRegion($regionName)); } throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); @@ -87,8 +92,36 @@ public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fi /** * {@inheritdoc} */ - public function buildQueryCache(EntityManager $em, $regionName) + public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { - return new DefaultQueryCache($em, new DefaultRegion($regionName ?: 'query.cache.region', $this->cache)); + return new DefaultQueryCache($em, $this->createRegion($regionName ?: 'query.cache.region')); } + + /** + * {@inheritdoc} + */ + public function buildCollectionEntryStructure(EntityManagerInterface $em) + { + return new DefaultCollectionEntryStructure($em); + } + + /** + * {@inheritdoc} + */ + public function buildEntityEntryStructure(EntityManagerInterface $em) + { + return new DefaultEntityEntryStructure($em); + } + + /** + * @param string $regionName + * @return \Doctrine\ORM\Cache\Region\DefaultRegion + */ + public function createRegion($regionName) + { + return new DefaultRegion($regionName, $this->cache, array( + 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($regionName) + )); + } + } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php new file mode 100644 index 00000000000..13fe52804fd --- /dev/null +++ b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php @@ -0,0 +1,86 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\CollectionCacheEntry; + +/** + * Default structure cache entry for collections + * + * @since 2.5 + * @author Fabio B. Silva + */ +class DefaultCollectionEntryStructure implements CollectionEntryStructure +{ + /** + * @var \Doctrine\ORM\EntityManagerInterface + */ + private $em; + + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + */ + public function __construct(EntityManagerInterface $em) + { + $this->em = $em; + $this->uow = $em->getUnitOfWork(); + } + + /** + * {@inheritdoc} + */ + public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection) + { + $data = array(); + + foreach ($collection as $key => $entity) { + $data[$key] = $this->uow->getEntityIdentifier($entity); + } + + return new CollectionCacheEntry($data); + } + + /** + * {@inheritdoc} + */ + public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection) + { + $list = array(); + + foreach ($entry->dataList as $key => $entry) { + $entity = $this->em->getReference($metadata->rootEntityName, $entry); + $list[$key] = $entity; + + $collection->hydrateSet($key, $entity); + } + + return $list; + } +} diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php new file mode 100644 index 00000000000..f3278a724dc --- /dev/null +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -0,0 +1,94 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Query; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Cache\EntityCacheEntry; + +/** + * Default cache entry structure for entities + * + * @since 2.5 + * @author Fabio B. Silva + */ +class DefaultEntityEntryStructure implements EntityEntryStructure +{ + /** + * @var \Doctrine\ORM\EntityManager + */ + private $em; + + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + */ + public function __construct(EntityManagerInterface $em) + { + $this->em = $em; + $this->uow = $em->getUnitOfWork(); + } + + /** + * {@inheritdoc} + */ + public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity) + { + $data = $this->uow->getOriginalEntityData($entity); + $data = array_merge($data, $key->identifier); // why update has no identifier values ? + + foreach ($metadata->associationMappings as $name => $assoc) { + + if ( ! isset($data[$name]) || $data[$name] === null) { + continue; + } + + if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { + $data[$name] = $this->uow->getEntityIdentifier($data[$name]); + + continue; + } + + unset($data[$name]); + } + + return new EntityCacheEntry($data); + } + + /** + * {@inheritdoc} + */ + public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null) + { + if ($entity !== null) { + $hints[Query::HINT_REFRESH] = true; + $hints[Query::HINT_REFRESH_ENTITY] = $entity; + } + + return $this->uow->createEntity($metadata->name, $entry->data, $hints); + } +} diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index fb1970a25f8..3d28419d0f4 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\EntityManager; /** @@ -62,16 +63,16 @@ public function __construct(EntityManager $em, Region $region) */ public function get(QueryCacheKey $key, ResultSetMapping $rsm) { - $data = $this->region->get($key); + $entry = $this->region->get($key); - if (empty($data)) { + if ( ! $entry instanceof QueryCacheEntry) { return null; } $entityName = reset($rsm->aliasMap); //@TODO find root entity $result = array(); - foreach ($data as $index => $value) { + foreach ($entry->result as $index => $value) { $result[$index] = $this->em->getReference($entityName, $value); //@TODO - handle associations ? @@ -93,7 +94,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) //@TODO - handle associations ? } - return $this->region->put($key, $data);; + return $this->region->put($key, new QueryCacheEntry($data)); } /** diff --git a/lib/Doctrine/ORM/Cache/EntityCacheEntry.php b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php new file mode 100644 index 00000000000..10cadb49c17 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Entity cache entry + * + * @since 2.5 + * @author Fabio B. Silva + */ +class EntityCacheEntry implements CacheEntry +{ + /** + * @var array + */ + public $data; + + /** + * @param array $data + */ + public function __construct(array $data) + { + $this->data = $data; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php index f855ac4d6cd..a0025edb3b5 100644 --- a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/EntityEntryStructure.php @@ -20,81 +20,32 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\Query; -use Doctrine\ORM\EntityManager; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Cache\EntityCacheEntry; /** - * Structured cache entry for entities + * Structure cache entry for entities * * @since 2.5 * @author Fabio B. Silva */ -class EntityEntryStructure +interface EntityEntryStructure { - /** - * @var \Doctrine\ORM\EntityManager - */ - private $em; - - /** - * @var \Doctrine\ORM\UnitOfWork - */ - private $uow; - - /** - * @param \Doctrine\ORM\EntityManager $em The entity manager. - */ - public function __construct(EntityManager $em) - { - $this->em = $em; - $this->uow = $em->getUnitOfWork(); - } - /** * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. * @param object $entity The entity. * - * @return array + * @return \Doctrine\ORM\Cache\EntityCacheEntry */ - public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity) - { - $data = $this->uow->getOriginalEntityData($entity); - $data = array_merge($data, $key->identifier); // why update has no identifier values ? - - foreach ($metadata->associationMappings as $name => $assoc) { - - if ( ! isset($data[$name]) || $data[$name] === null) { - continue; - } - - if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { - $data[$name] = $this->uow->getEntityIdentifier($data[$name]); - - continue; - } - - unset($data[$name]); - } - - return $data; - } + public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity); /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. - * @param array $cache The entity data. - * @param object $entity The entity to load the cache into. If not specified, a new entity is created. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. + * @param \Doctrine\ORM\Cache\EntityCacheEntry $entry The entity cache entry. + * @param object $entity The entity to load the cache into. If not specified, a new entity is created. */ - public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, array $cache, $entity = null) - { - if ($entity !== null) { - $hints[Query::HINT_REFRESH] = true; - $hints[Query::HINT_REFRESH_ENTITY] = $entity; - } - - return $this->uow->createEntity($metadata->name, $cache, $hints); - } + public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null); } diff --git a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php new file mode 100644 index 00000000000..3cb39667f66 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php @@ -0,0 +1,46 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\EntityManagerInterface; + +/** + * Query cache entry + * + * @since 2.5 + * @author Fabio B. Silva + */ +class QueryCacheEntry implements CacheEntry +{ + /** + * @var array + */ + public $result; + + /** + * @param array $result + */ + public function __construct($result) + { + $this->result = $result; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/Region.php b/lib/Doctrine/ORM/Cache/Region.php index 05a7ee4ebe8..324912d6066 100644 --- a/lib/Doctrine/ORM/Cache/Region.php +++ b/lib/Doctrine/ORM/Cache/Region.php @@ -49,7 +49,7 @@ public function contains(CacheKey $key); * * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to be retrieved. * - * @return array The cached data or NULL + * @return \Doctrine\ORM\Cache\CacheEntry The cached entry or NULL * * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the item or region. */ @@ -58,12 +58,12 @@ public function get(CacheKey $key); /** * Put an item into the cache. * - * @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item. - * @param array $value The item to cache. + * @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The entry to cache. * * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region. */ - public function put(CacheKey $key, array $value); + public function put(CacheKey $key, CacheEntry $entry); /** * Remove an item from the cache. diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index c57c24f7e1c..eb75129b40d 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -21,8 +21,9 @@ namespace Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\Region; -use Doctrine\ORM\Cache\CacheKey; use Doctrine\Common\Cache\Cache; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\ORM\Cache\CacheEntry; /** * The simplest cache region compatible with all doctrine-cache drivers. @@ -50,15 +51,15 @@ class DefaultRegion implements Region /** * @param strgin $name * @param \Doctrine\Common\Cache\Cache $cache - * @param array $properties + * @param array $configuration */ - public function __construct($name, Cache $cache, array $properties = array()) + public function __construct($name, Cache $cache, array $configuration = array()) { $this->name = $name; $this->cache = $cache; - if (isset($properties['lifetime']) && $properties['lifetime'] > 0) { - $this->lifetime = (integer) $properties['lifetime']; + if (isset($configuration['lifetime']) && $configuration['lifetime'] > 0) { + $this->lifetime = (integer) $configuration['lifetime']; } } @@ -113,7 +114,7 @@ public function get(CacheKey $key) /** * {@inheritdoc} */ - public function put(CacheKey $key, array $value) + public function put(CacheKey $key, CacheEntry $entry) { $entriesKey = $this->entriesMapKey(); $entryKey = $this->entryKey($key); @@ -121,7 +122,7 @@ public function put(CacheKey $key, array $value) $entries[$entryKey] = true; - if ($this->cache->save($entryKey, $value, $this->lifetime)) { + if ($this->cache->save($entryKey, $entry, $this->lifetime)) { $this->cache->save($entriesKey, $entries); return true; diff --git a/lib/Doctrine/ORM/Cache/RegionAccess.php b/lib/Doctrine/ORM/Cache/RegionAccess.php index 3c33618c77c..6f96f3a9dab 100644 --- a/lib/Doctrine/ORM/Cache/RegionAccess.php +++ b/lib/Doctrine/ORM/Cache/RegionAccess.php @@ -38,9 +38,9 @@ public function getRegion(); /** * Attempt to retrieve an object from the cache. * - * @param \Doctrine\ORM\Cache\CacheKey $identifier The identifier of the item to be retrieved. + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to be retrieved. * - * @return the cached object or null + * @return \Doctrine\ORM\Cache\CacheEntry The cached entry or null * * @throws \Doctrine\ORM\Cache\CacheException */ @@ -49,44 +49,44 @@ public function get(CacheKey $key); /** * Attempt to cache an object, after loading from the database. * - * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. - * @param array $value The item. + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. * * @return true if the object was successfully cached. * * @throws \Doctrine\ORM\Cache\CacheException */ - public function put(CacheKey $key, array $value); + public function put(CacheKey $key, CacheEntry $entry); /** * Called after an item has been inserted (after the transaction completes). * - * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. - * @param array $value The item. + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. * * @return boolean true If the contents of the cache actual were changed. * * @throws \Doctrine\ORM\Cache\CacheException */ - public function afterInsert(CacheKey $key, array $value); + public function afterInsert(CacheKey $key, CacheEntry $entry); /** * Called after an item has been updated (after the transaction completes). * - * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. - * @param array $value The item. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} * * @return boolean true If the contents of the cache actual were changed. * * @throws \Doctrine\ORM\Cache\CacheException */ - public function afterUpdate(CacheKey $key, array $value, Lock $lock = null); + public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null); /** * Forcibly evict an item from the cache immediately without regard for locks. * - * @param \Doctrine\ORM\Cache\CacheKey $identifier The identifier of the item to remove. + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to remove. * * @throws \Doctrine\ORM\Cache\CacheException */ diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index dc8598e647a..b2786cc7a05 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -35,7 +35,7 @@ use Doctrine\ORM\Mapping\QuoteStrategy; use Doctrine\ORM\Repository\DefaultRepositoryFactory; use Doctrine\ORM\Repository\RepositoryFactory; -use Doctrine\ORM\Cache\AccessProvider; +use Doctrine\ORM\Cache\CacheFactory; /** * Configuration container for all configuration options of Doctrine. @@ -255,23 +255,64 @@ public function setSecondLevelCacheEnabled($flag = true) } /** - * @return \Doctrine\ORM\Cache\AccessProvider|null + * @return \Doctrine\ORM\Cache\CacheFactory|null */ - public function getSecondLevelCacheAccessProvider() + public function getSecondLevelCacheFactory() { - return isset($this->_attributes['secondLevelCacheAccessProvider']) - ? $this->_attributes['secondLevelCacheAccessProvider'] + return isset($this->_attributes['secondLevelCacheFactory']) + ? $this->_attributes['secondLevelCacheFactory'] : null; } /** - * @param \Doctrine\ORM\Cache\AccessProvider $provider + * @param \Doctrine\ORM\Cache\CacheFactory $factory * * @return void */ - public function setSecondLevelCacheAccessProvider(AccessProvider $provider) + public function setSecondLevelCacheFactory(CacheFactory $factory) { - $this->_attributes['secondLevelCacheAccessProvider'] = $provider; + $this->_attributes['secondLevelCacheFactory'] = $factory; + } + + /** + * @param string $name + * + * @return integer + */ + public function getSecondLevelCacheRegionLifetime($name) + { + if (isset($this->_attributes['secondLevelCacheRegionLifetime'][$name])) { + return $this->_attributes['secondLevelCacheRegionLifetime'][$name]; + } + + return $this->getSecondLevelCacheDefaultRegionLifetime(); + } + + /** + * @param string $name + * @param integer $lifetime + */ + public function setSecondLevelCacheRegionLifetime($name, $lifetime) + { + $this->_attributes['secondLevelCacheRegionLifetime'][$name] = (integer) $lifetime; + } + + /** + * @return integer + */ + public function getSecondLevelCacheDefaultRegionLifetime() + { + return isset($this->_attributes['secondLevelCacheDefaultRegionLifetime']) + ? $this->_attributes['secondLevelCacheDefaultRegionLifetime'] + : 0; + } + + /** + * @param integer $lifetime + */ + public function setSecondLevelCacheDefaultRegionLifetime($lifetime) + { + $this->_attributes['secondLevelCacheDefaultRegionLifetime'] = (integer) $lifetime; } /** diff --git a/lib/Doctrine/ORM/Mapping/Cache.php b/lib/Doctrine/ORM/Mapping/Cache.php index 116d6b0a193..560a9c4eb34 100644 --- a/lib/Doctrine/ORM/Mapping/Cache.php +++ b/lib/Doctrine/ORM/Mapping/Cache.php @@ -41,9 +41,4 @@ final class Cache implements Annotation * @var string Cache region name. */ public $region; - - /** - * @var array Cache options. - */ - public $properties; } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php index 3c029334f0a..359f2632272 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php @@ -134,7 +134,6 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $metadata->enableCache(array( 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage), - 'properties' => $cacheAnnot->properties, 'region' => $cacheAnnot->region, )); } @@ -381,7 +380,6 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) if (($cacheAnnot = $this->reader->getPropertyAnnotation($property, 'Doctrine\ORM\Mapping\Cache')) !== null) { $metadata->enableAssociationCache($mapping['fieldName'], array( 'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage), - 'properties' => $cacheAnnot->properties, 'region' => $cacheAnnot->region, )); } diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index e3d7dad4d3b..84203d3f543 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -26,7 +26,6 @@ use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Cache\ConcurrentRegionAccess; -use Doctrine\ORM\Cache\CollectionEntryStructure; /** * Base class for all collection persisters. @@ -112,12 +111,10 @@ public function __construct(EntityManager $em, array $association) $this->hasCache = isset($association['cache']) && $em->getConfiguration()->isSecondLevelCacheEnabled(); if ($this->hasCache) { - $sourceClass = $em->getClassMetadata($association['sourceEntity']); - $this->cacheRegionAccess = $em->getConfiguration() - ->getSecondLevelCacheAccessProvider() - ->buildCollectionRegionAccessStrategy($sourceClass, $association['fieldName']); - - $this->cacheEntryStructure = new CollectionEntryStructure($em); + $sourceClass = $em->getClassMetadata($association['sourceEntity']); + $cacheFactory = $em->getConfiguration()->getSecondLevelCacheFactory(); + $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($sourceClass, $association['fieldName']); + $this->cacheEntryStructure = $cacheFactory->buildCollectionEntryStructure($em); $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); } } @@ -281,9 +278,9 @@ public function saveLoadedCollection(ClassMetadata $targetMetadata, CollectionCa $targetClass = $this->association['targetEntity']; $targetPersister = $this->uow->getEntityPersister($targetClass); $targetRegionAcess = $targetPersister->getCacheRegionAcess(); - $listData = $this->cacheEntryStructure->buildCacheEntry($targetMetadata, $key, $elements); + $entry = $this->cacheEntryStructure->buildCacheEntry($targetMetadata, $key, $elements); - foreach ($listData as $index => $identifier) { + foreach ($entry->dataList as $index => $identifier) { $entityKey = new EntityCacheKey($targetClass, $identifier); if ($targetRegionAcess->getRegion()->contains($entityKey)) { @@ -296,7 +293,7 @@ public function saveLoadedCollection(ClassMetadata $targetMetadata, CollectionCa $targetRegionAcess->put($entityKey, $entityEntry); } - $this->cacheRegionAccess->put($key, $listData); + $this->cacheRegionAccess->put($key, $entry); } /** diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index fc096ae80ab..6939a82f2ff 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -253,12 +253,10 @@ public function __construct(EntityManager $em, ClassMetadata $class) $this->hasCache = ($class->cache !== null) && $em->getConfiguration()->isSecondLevelCacheEnabled(); if ($this->hasCache) { - $this->cacheRegionAccess = $em->getConfiguration() - ->getSecondLevelCacheAccessProvider() - ->buildEntityRegionAccessStrategy($this->class); - - $this->cacheEntryStructure = new EntityEntryStructure($em); - $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); + $cacheFactory = $em->getConfiguration()->getSecondLevelCacheFactory(); + $this->cacheRegionAccess = $cacheFactory->buildEntityRegionAccessStrategy($this->class); + $this->cacheEntryStructure = $cacheFactory->buildEntityEntryStructure($em); + $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); } } diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 8b30ce86533..6fe6a500615 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -354,8 +354,8 @@ private function executeUsingQueryCache($parameters = null, $hydrationMode = nul throw new ORMException("Second level cache does not suport mixed results"); } - $queryCache = $this->_em->getConfiguration()->getSecondLevelCacheAccessProvider()->buildQueryCache($this->_em, $this->cacheRegion); $querykey = new QueryCacheKey($this->_getQueryCacheId()); + $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); $result = $queryCache->get($querykey, $this->_resultSetMapping); if ($result === null) { diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php index 6fd30284fe1..ab7b9c3d73d 100644 --- a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php @@ -6,6 +6,7 @@ use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\ArrayCache; use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\EntityCacheEntry; use Doctrine\ORM\Cache\Region\DefaultRegion; require_once __DIR__ . '/../../TestInit.php'; @@ -74,21 +75,21 @@ static public function dataProviderCacheValues() $entityName = '\Doctrine\Tests\Models\Cache\Country'; return array( - array(new EntityCacheKey($entityName, array('id'=>1)), array('id'=>1, 'name' => 'bar')), - array(new EntityCacheKey($entityName, array('id'=>2)), array('id'=>2, 'name' => 'foo')), + array(new EntityCacheKey($entityName, array('id'=>1)), new EntityCacheEntry(array('id'=>1, 'name' => 'bar'))), + array(new EntityCacheKey($entityName, array('id'=>2)), new EntityCacheEntry(array('id'=>2, 'name' => 'foo'))), ); } /** * @dataProvider dataProviderCacheValues */ - public function testPutGetAndEvict($key, $value) + public function testPutGetAndEvict(EntityCacheKey $key, EntityCacheEntry $entry) { $this->assertNull($this->regionAccess->get($key)); - $this->regionAccess->put($key, $value); + $this->regionAccess->put($key, $entry); - $this->assertEquals($value, $this->regionAccess->get($key)); + $this->assertEquals($entry, $this->regionAccess->get($key)); $this->regionAccess->evict($key); @@ -103,11 +104,11 @@ public function testEvictAll() $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->put($key1, array('value' => 'foo')); - $this->regionAccess->put($key2, array('value' => 'bar')); + $this->regionAccess->put($key1, new EntityCacheEntry(array('value' => 'foo'))); + $this->regionAccess->put($key2, new EntityCacheEntry(array('value' => 'bar'))); - $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); - $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + $this->assertEquals(new EntityCacheEntry(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new EntityCacheEntry(array('value' => 'bar')), $this->regionAccess->get($key2)); $this->regionAccess->evictAll(); @@ -123,11 +124,11 @@ public function testAfterInsert() $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->afterInsert($key1, array('value' => 'foo')); - $this->regionAccess->afterInsert($key2, array('value' => 'bar')); + $this->regionAccess->afterInsert($key1, new EntityCacheEntry(array('value' => 'foo'))); + $this->regionAccess->afterInsert($key2, new EntityCacheEntry(array('value' => 'bar'))); - $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); - $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + $this->assertEquals(new EntityCacheEntry(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new EntityCacheEntry(array('value' => 'bar')), $this->regionAccess->get($key2)); } public function testAfterUpdate() @@ -138,10 +139,10 @@ public function testAfterUpdate() $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->afterUpdate($key1, array('value' => 'foo')); - $this->regionAccess->afterUpdate($key2, array('value' => 'bar')); + $this->regionAccess->afterUpdate($key1, new EntityCacheEntry(array('value' => 'foo'))); + $this->regionAccess->afterUpdate($key2, new EntityCacheEntry(array('value' => 'bar'))); - $this->assertEquals(array('value' => 'foo'), $this->regionAccess->get($key1)); - $this->assertEquals(array('value' => 'bar'), $this->regionAccess->get($key2)); + $this->assertEquals(new EntityCacheEntry(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new EntityCacheEntry(array('value' => 'bar')), $this->regionAccess->get($key2)); } } diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index 2cef7b0d88b..368b30679c7 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -7,7 +7,9 @@ use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Country; use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\EntityCacheEntry; use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\CollectionCacheEntry; /** * @group DDC-2183 @@ -30,12 +32,13 @@ protected function setUp() /** * @param string $className * @param array $identifier - * @param array $cacheEntry + * @param array $data */ - private function putEntityCacheEntry($className, array $identifier, array $cacheEntry) + private function putEntityCacheEntry($className, array $identifier, array $data) { $metadata = $this->_em->getClassMetadata($className); $cacheKey = new EntityCacheKey($metadata->rootEntityName, $identifier); + $cacheEntry = new EntityCacheEntry($data); $persister = $this->_em->getUnitOfWork()->getEntityPersister($metadata->rootEntityName); $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); @@ -45,12 +48,13 @@ private function putEntityCacheEntry($className, array $identifier, array $cache * @param string $className * @param string $association * @param array $ownerIdentifier - * @param array $cacheEntry + * @param array $data */ - private function putCollectionCacheEntry($className, $association, array $ownerIdentifier, array $cacheEntry) + private function putCollectionCacheEntry($className, $association, array $ownerIdentifier, array $data) { $metadata = $this->_em->getClassMetadata($className); $cacheKey = new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); + $cacheEntry = new CollectionCacheEntry($data); $persister = $this->_em->getUnitOfWork()->getCollectionPersister($metadata->getAssociationMapping($association)); $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); diff --git a/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php similarity index 82% rename from tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php rename to tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php index a98fd4e91fe..89a26cac340 100644 --- a/tests/Doctrine/Tests/ORM/Cache/CollectionEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php @@ -4,12 +4,13 @@ use Doctrine\ORM\UnitOfWork; use Doctrine\Tests\OrmTestCase; -use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\Tests\Models\Cache\State; use Doctrine\ORM\PersistentCollection; +use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\City; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\CollectionCacheEntry; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\Cache\CollectionEntryStructure; +use Doctrine\ORM\Cache\DefaultCollectionEntryStructure; /** * @group DDC-2183 @@ -31,21 +32,26 @@ protected function setUp() parent::setUp(); $this->em = $this->_getTestEntityManager(); - $this->structure = new CollectionEntryStructure($this->em); + $this->structure = new DefaultCollectionEntryStructure($this->em); + } + + public function testImplementsCollectionEntryStructure() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\CollectionEntryStructure', $this->structure); } public function testLoadCacheCollection() { - $cache = array( + $entry = new CollectionCacheEntry(array( array('id'=>31), array('id'=>32), - ); + )); $sourceClass = $this->em->getClassMetadata(State::CLASSNAME); $targetClass = $this->em->getClassMetadata(City::CLASSNAME); $key = new CollectionCacheKey($sourceClass->name, 'cities', array('id'=>21)); $collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); - $list = $this->structure->loadCacheEntry($sourceClass, $key, $cache, $collection); + $list = $this->structure->loadCacheEntry($sourceClass, $key, $entry, $collection); $this->assertCount(2, $list); $this->assertCount(2, $collection); diff --git a/tests/Doctrine/Tests/ORM/Cache/EntityEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php similarity index 74% rename from tests/Doctrine/Tests/ORM/Cache/EntityEntryStructureTest.php rename to tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php index a8c1e7b27be..b9898a7a0f5 100644 --- a/tests/Doctrine/Tests/ORM/Cache/EntityEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php @@ -5,9 +5,10 @@ use Doctrine\ORM\UnitOfWork; use Doctrine\Tests\OrmTestCase; use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\EntityCacheEntry; use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Country; -use Doctrine\ORM\Cache\EntityEntryStructure; +use Doctrine\ORM\Cache\DefaultEntityEntryStructure; /** * @group DDC-2183 @@ -29,15 +30,20 @@ protected function setUp() parent::setUp(); $this->em = $this->_getTestEntityManager(); - $this->structure = new EntityEntryStructure($this->em); + $this->structure = new DefaultEntityEntryStructure($this->em); + } + + public function testImplementsEntityEntryStructure() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityEntryStructure', $this->structure); } public function testCreateEntity() { $metadata = $this->em->getClassMetadata(Country::CLASSNAME); $key = new EntityCacheKey($metadata->name, array('id'=>1)); - $cache = array('id'=>1, 'name'=>'Foo'); - $entity = $this->structure->loadCacheEntry($metadata, $key, $cache); + $entry = new EntityCacheEntry(array('id'=>1, 'name'=>'Foo')); + $entity = $this->structure->loadCacheEntry($metadata, $key, $entry); $this->assertInstanceOf($metadata->name, $entity); @@ -50,9 +56,9 @@ public function testLoadProxy() { $metadata = $this->em->getClassMetadata(Country::CLASSNAME); $key = new EntityCacheKey($metadata->name, array('id'=>1)); + $entry = new EntityCacheEntry(array('id'=>1, 'name'=>'Foo')); $proxy = $this->em->getReference($metadata->name, $key->identifier); - $cache = array('id'=>1, 'name'=>'Foo'); - $entity = $this->structure->loadCacheEntry($metadata, $key, $cache, $proxy); + $entity = $this->structure->loadCacheEntry($metadata, $key, $entry, $proxy); $this->assertInstanceOf($metadata->name, $entity); $this->assertSame($proxy, $entity); @@ -75,12 +81,15 @@ public function testBuildCacheEntry() $cache = $this->structure->buildCacheEntry($metadata, $key, $entity); - $this->assertArrayHasKey('id', $cache); - $this->assertArrayHasKey('name', $cache); + $this->assertInstanceOf('Doctrine\ORM\Cache\CacheEntry', $cache); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $cache); + + $this->assertArrayHasKey('id', $cache->data); + $this->assertArrayHasKey('name', $cache->data); $this->assertEquals(array( 'id' => 1, 'name' => 'Foo', - ), $cache); + ), $cache->data); } public function testBuildCacheEntryOwningSide() @@ -101,13 +110,16 @@ public function testBuildCacheEntryOwningSide() $cache = $this->structure->buildCacheEntry($metadata, $key, $state); - $this->assertArrayHasKey('id', $cache); - $this->assertArrayHasKey('name', $cache); - $this->assertArrayHasKey('country', $cache); + $this->assertInstanceOf('Doctrine\ORM\Cache\CacheEntry', $cache); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $cache); + + $this->assertArrayHasKey('id', $cache->data); + $this->assertArrayHasKey('name', $cache->data); + $this->assertArrayHasKey('country', $cache->data); $this->assertEquals(array( 'id' => 11, 'name' => 'Bar', 'country' => array ('id' => 11), - ), $cache); + ), $cache->data); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php index b9bf347141c..27fe31a813a 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -5,6 +5,8 @@ use Doctrine\Tests\OrmFunctionalTestCase; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\Common\Cache\ArrayCache; +use Doctrine\ORM\Cache\CacheEntry; +use Doctrine\ORM\Cache\CacheKey; /** * @group DDC-2183 @@ -38,8 +40,8 @@ public function testGetters() static public function dataProviderCacheValues() { return array( - array(new DefaultRegionTestKey('key.1'), array('id'=>1, 'name' => 'bar')), - array(new DefaultRegionTestKey('key.2'), array('id'=>2, 'name' => 'foo')), + array(new DefaultRegionTestKey('key.1'), new DefaultRegionTestEntry(array('id'=>1, 'name' => 'bar'))), + array(new DefaultRegionTestKey('key.2'), new DefaultRegionTestEntry(array('id'=>2, 'name' => 'foo'))), ); } @@ -71,8 +73,8 @@ public function testEvictAll() $this->assertFalse($this->region->contains($key1)); $this->assertFalse($this->region->contains($key2)); - $this->region->put($key1, array('value' => 'foo')); - $this->region->put($key2, array('value' => 'bar')); + $this->region->put($key1, new DefaultRegionTestEntry(array('value' => 'foo'))); + $this->region->put($key2, new DefaultRegionTestEntry(array('value' => 'bar'))); $this->assertTrue($this->region->contains($key1)); $this->assertTrue($this->region->contains($key2)); @@ -84,7 +86,7 @@ public function testEvictAll() } } -class DefaultRegionTestKey implements \Doctrine\ORM\Cache\CacheKey +class DefaultRegionTestKey implements CacheKey { function __construct($hash) @@ -98,3 +100,7 @@ public function hash() } } +class DefaultRegionTestEntry extends \ArrayObject implements CacheEntry +{ + +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php index 3a20653fc32..d9556557257 100644 --- a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php @@ -21,6 +21,6 @@ protected function createRegionAccess(Region $region) */ public function testAfterUpdate() { - $this->regionAccess->afterUpdate(new DefaultRegionTestKey('key'), array('value' => 'foo')); + $this->regionAccess->afterUpdate(new DefaultRegionTestKey('key'), new DefaultRegionTestEntry(array('value' => 'foo'))); } } diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 5f0d4f9ca90..3edab4bdf82 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -3,7 +3,7 @@ namespace Doctrine\Tests; use Doctrine\Common\Cache\Cache; -use Doctrine\ORM\Cache\CacheAccessProvider; +use Doctrine\ORM\Cache\DefaultCacheFactory; /** * Base testcase class for all functional ORM testcases. @@ -18,9 +18,9 @@ abstract class OrmFunctionalTestCase extends OrmTestCase private $isSecondLevelCacheEnabled = false; /** - * @var \Doctrine\Common\Cache\CacheProvider + * @var \Doctrine\ORM\Cache\CacheFactory */ - private $secondLevelCacheAccessProvider; + private $secondLevelCacheFactory; /** * The metadata cache shared between all functional tests. @@ -441,8 +441,14 @@ protected function _getEntityManager($config = null, $eventManager = null) { $config->setProxyNamespace('Doctrine\Tests\Proxies'); if ($this->isSecondLevelCacheEnabled) { + + $cache = self::getSharedSecondLevelCacheDriverImpl(); + $factory = new DefaultCacheFactory($config, $cache); + + $this->secondLevelCacheFactory = $factory; + $config->setSecondLevelCacheEnabled(); - $config->setSecondLevelCacheAccessProvider($this->secondLevelCacheAccessProvider); + $config->setSecondLevelCacheFactory($factory); } $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array( @@ -474,17 +480,9 @@ protected function _getEntityManager($config = null, $eventManager = null) { return \Doctrine\ORM\EntityManager::create($conn, $config); } - - /** - * @param \Doctrine\Common\Cache\Cache $cache - */ - protected function enableSecondLevelCache(Cache $cache = null) + protected function enableSecondLevelCache() { - $cache = $cache ?: self::getSharedSecondLevelCacheDriverImpl(); - $provider = new CacheAccessProvider($cache); - - $this->secondLevelCacheAccessProvider = $provider; - $this->isSecondLevelCacheEnabled = true; + $this->isSecondLevelCacheEnabled = true; } /** From 784fe633b46473698f13bdfb0b9acacba2700fc1 Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 17 Jun 2013 13:13:39 -0400 Subject: [PATCH 19/71] test flush fail --- .../ORM/Functional/SecondLevelCacheTest.php | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index e4aebae8dd4..10907ad99d4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -4,6 +4,7 @@ use Doctrine\Tests\Models\Cache\Country; use Doctrine\Tests\Models\Cache\State; +use Doctrine\ORM\Events; /** * @group DDC-2183 @@ -156,4 +157,146 @@ public function testUpdateEntities() $this->assertEquals($s2->getId(), $c4->getId()); $this->assertEquals("NEW NAME 2", $c4->getName()); } + + public function testPostFlushFailure() + { + $listener = new ListenerSecondLevelCacheTest(array(Events::postFlush => function(){ + throw new \RuntimeException('pre insert failure'); + })); + + $this->_em->getEventManager() + ->addEventListener(Events::postFlush, $listener); + + $country = new Country("Brazil"); + + $this->cache->evictEntityRegion(Country::CLASSNAME); + + try { + + $this->_em->persist($country); + $this->_em->flush(); + $this->fail('Should throw exception'); + + } catch (\RuntimeException $exc) { + $this->assertNotNull($country->getId()); + $this->assertEquals('pre insert failure', $exc->getMessage()); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $country->getId())); + } + } + + public function testPostUpdateFailure() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->_em->clear(); + + $listener = new ListenerSecondLevelCacheTest(array(Events::postUpdate => function(){ + throw new \RuntimeException('post update failure'); + })); + + $this->_em->getEventManager() + ->addEventListener(Events::postUpdate, $listener); + + $this->cache->evictEntityRegion(State::CLASSNAME); + + $stateId = $this->states[0]->getId(); + $stateName = $this->states[0]->getName(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $stateId)); + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertEquals($stateName, $state->getName()); + + $state->setName($stateName . uniqid()); + + $this->_em->persist($state); + + try { + $this->_em->flush(); + $this->fail('Should throw exception'); + + } catch (\Exception $exc) { + $this->assertEquals('post update failure', $exc->getMessage()); + } + + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $stateId)); + + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertEquals($stateName, $state->getName()); + } + + public function testPostRemoveFailure() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $listener = new ListenerSecondLevelCacheTest(array(Events::postRemove => function(){ + throw new \RuntimeException('post remove failure'); + })); + + $this->_em->getEventManager() + ->addEventListener(Events::postRemove, $listener); + + $this->cache->evictEntityRegion(Country::CLASSNAME); + + $countryId = $this->countries[0]->getId(); + $country = $this->_em->find(Country::CLASSNAME, $countryId); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + $this->assertInstanceOf(Country::CLASSNAME, $country); + + $this->_em->remove($country); + + try { + $this->_em->flush(); + $this->fail('Should throw exception'); + + } catch (\Exception $exc) { + $this->assertEquals('post remove failure', $exc->getMessage()); + } + + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + + $country = $this->_em->find(Country::CLASSNAME, $countryId); + $this->assertInstanceOf(Country::CLASSNAME, $country); + } +} + + +class ListenerSecondLevelCacheTest +{ + public $callbacks; + + public function __construct(array $callbacks = array()) + { + $this->callbacks = $callbacks; + } + + private function dispatch($eventName, $args) + { + if (isset($this->callbacks[$eventName])) { + call_user_func($this->callbacks[$eventName], $args); + } + } + + public function postFlush($args) + { + $this->dispatch(__FUNCTION__, $args); + } + + public function postUpdate($args) + { + $this->dispatch(__FUNCTION__, $args); + } + + public function postRemove($args) + { + $this->dispatch(__FUNCTION__, $args); + } } \ No newline at end of file From cb3aa8dd92ea965cb04a01d780e8bf55833b20f2 Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 17 Jun 2013 13:55:47 -0400 Subject: [PATCH 20/71] test select using parameters and missing entity cache --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 12 ++- .../SecondLevelCacheQueryCacheTest.php | 75 ++++++++++++++++++- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 3d28419d0f4..6b76d9f242b 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -22,6 +22,7 @@ use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Cache\QueryCacheEntry; +use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\EntityManager; /** @@ -69,10 +70,17 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) return null; } - $entityName = reset($rsm->aliasMap); //@TODO find root entity $result = array(); - + $entityName = reset($rsm->aliasMap); //@TODO find root entity + $persister = $this->uow->getEntityPersister($entityName); + $region = $persister->getCacheRegionAcess()->getRegion(); + foreach ($entry->result as $index => $value) { + + if ( ! $region->contains(new EntityCacheKey($entityName, $value))) { + return null; + } + $result[$index] = $this->em->getReference($entityName, $value); //@TODO - handle associations ? diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 0d986b23a26..e42436ad4d5 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -14,8 +14,8 @@ public function testSelectAll() $this->loadFixturesCountries(); $this->_em->clear(); - $this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId()); - $this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId()); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; $query1 = $this->_em->createQuery($dql)->setCacheable(true); @@ -42,4 +42,75 @@ public function testSelectAll() $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); } + + public function testSelectParams() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $name = $this->countries[0]->getName(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c WHERE c.name = :name'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->setParameter('name', $name) + ->getResult(); + + $queryCount = $this->getCurrentQueryCount(); + + $this->_em->clear(); + + $result2 = $this->_em->createQuery($dql)->setCacheable(true) + ->setParameter('name', $name) + ->getResult(); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + $this->assertCount(1, $result2); + + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); + + $this->assertEquals($result1[0]->getId(), $result2[0]->getId()); + $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); + } + + public function testLoadFromDatabaseWhenEntityMissing() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $result1 = $this->_em->createQuery($dql)->setCacheable(true)->getResult(); + $queryCount = $this->getCurrentQueryCount(); + + $this->cache->evictEntity(Country::CLASSNAME, $result1[1]->getId()); + + $this->assertCount(2, $result1); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $result1[1]->getId())); + + $this->_em->clear(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertEquals($queryCount + 1 , $this->getCurrentQueryCount()); + $this->assertCount(2, $result2); + + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); + + $this->assertEquals($result1[0]->getId(), $result2[0]->getId()); + $this->assertEquals($result1[1]->getId(), $result2[1]->getId()); + + $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); + $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); + + $this->assertEquals($queryCount + 1 , $this->getCurrentQueryCount()); + } } \ No newline at end of file From 06fc8d3cdf65e8975ed3ed96ec58b0081e2cf863 Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 17 Jun 2013 14:33:09 -0400 Subject: [PATCH 21/71] improve query test coverage --- lib/Doctrine/ORM/Cache/DefaultCache.php | 6 +- tests/Doctrine/Tests/Models/Cache/City.php | 2 +- tests/Doctrine/Tests/Models/Cache/Country.php | 2 +- tests/Doctrine/Tests/Models/Cache/State.php | 2 +- .../SecondLevelCacheAbstractTest.php | 7 +++ .../SecondLevelCacheQueryCacheTest.php | 62 +++++++++++++------ 6 files changed, 54 insertions(+), 27 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index ace7df97a52..2dd94c81ed5 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -54,7 +54,7 @@ class DefaultCache implements Cache /** * @var array<\Doctrine\ORM\Cache\QueryCache> */ - private $queryCaches; + private $queryCaches = array(); /** * @var \Doctrine\ORM\Cache\QueryCache @@ -267,9 +267,7 @@ public function evictQueryRegion($regionName = null) */ public function evictQueryRegions() { - if ($this->defaultQueryCache !== null) { - $this->defaultQueryCache->clear(); - } + $this->getQueryCache()->clear(); foreach ($this->queryCaches as $queryCache) { $queryCache->clear(); diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php index 87b721bbf53..a2c0feb7f7d 100644 --- a/tests/Doctrine/Tests/Models/Cache/City.php +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -21,7 +21,7 @@ class City protected $id; /** - * @Column + * @Column(unique=true) */ protected $name; diff --git a/tests/Doctrine/Tests/Models/Cache/Country.php b/tests/Doctrine/Tests/Models/Cache/Country.php index c147f26cbf2..33a9cf40cfd 100644 --- a/tests/Doctrine/Tests/Models/Cache/Country.php +++ b/tests/Doctrine/Tests/Models/Cache/Country.php @@ -19,7 +19,7 @@ class Country protected $id; /** - * @Column + * @Column(unique=true) */ protected $name; diff --git a/tests/Doctrine/Tests/Models/Cache/State.php b/tests/Doctrine/Tests/Models/Cache/State.php index eaab569e9a7..9891da93bcb 100644 --- a/tests/Doctrine/Tests/Models/Cache/State.php +++ b/tests/Doctrine/Tests/Models/Cache/State.php @@ -21,7 +21,7 @@ class State protected $id; /** - * @Column + * @Column(unique=true) */ protected $name; diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php index 70339053b34..f029fdf23a4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -132,4 +132,11 @@ protected function loadFixturesTravels() $this->_em->flush(); } + + protected function evictRegions() + { + $this->cache->evictQueryRegions(); + $this->cache->evictEntityRegions(); + $this->cache->evictCollectionRegions(); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index e42436ad4d5..c6af1248524 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -11,25 +11,33 @@ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest { public function testSelectAll() { + $this->evictRegions(); $this->loadFixturesCountries(); $this->_em->clear(); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; - $query1 = $this->_em->createQuery($dql)->setCacheable(true); - - $result1 = $query1->getResult(); $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $result1 = $this->_em->createQuery($dql)->setCacheable(true)->getResult(); + + $this->assertCount(2, $result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals($this->countries[0]->getId(), $result1[0]->getId()); + $this->assertEquals($this->countries[1]->getId(), $result1[1]->getId()); + $this->assertEquals($this->countries[0]->getName(), $result1[0]->getName()); + $this->assertEquals($this->countries[1]->getName(), $result1[1]->getName()); + $this->_em->clear(); - $query2 = $this->_em->createQuery($dql)->setCacheable(true); - $result2 = $query2->getResult(); + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); - $this->assertEquals($queryCount, $this->getCurrentQueryCount()); - $this->assertCount(count($this->countries), $result2); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertCount(2, $result2); $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); @@ -45,20 +53,25 @@ public function testSelectAll() public function testSelectParams() { + $this->evictRegions(); + $this->loadFixturesCountries(); $this->_em->clear(); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $name = $this->countries[0]->getName(); - $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c WHERE c.name = :name'; - $result1 = $this->_em->createQuery($dql) + $queryCount = $this->getCurrentQueryCount(); + $name = $this->countries[0]->getName(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c WHERE c.name = :name'; + $result1 = $this->_em->createQuery($dql) ->setCacheable(true) ->setParameter('name', $name) ->getResult(); - $queryCount = $this->getCurrentQueryCount(); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals($this->countries[0]->getId(), $result1[0]->getId()); + $this->assertEquals($this->countries[0]->getName(), $result1[0]->getName()); $this->_em->clear(); @@ -66,7 +79,7 @@ public function testSelectParams() ->setParameter('name', $name) ->getResult(); - $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); $this->assertCount(1, $result2); $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); @@ -78,20 +91,27 @@ public function testSelectParams() public function testLoadFromDatabaseWhenEntityMissing() { + $this->evictRegions(); + $this->loadFixturesCountries(); $this->_em->clear(); - + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + $queryCount = $this->getCurrentQueryCount(); $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; $result1 = $this->_em->createQuery($dql)->setCacheable(true)->getResult(); - $queryCount = $this->getCurrentQueryCount(); - - $this->cache->evictEntity(Country::CLASSNAME, $result1[1]->getId()); $this->assertCount(2, $result1); - $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $result1[1]->getId())); + $this->assertEquals($queryCount + 1 , $this->getCurrentQueryCount()); + $this->assertEquals($this->countries[0]->getId(), $result1[0]->getId()); + $this->assertEquals($this->countries[1]->getId(), $result1[1]->getId()); + $this->assertEquals($this->countries[0]->getName(), $result1[0]->getName()); + $this->assertEquals($this->countries[1]->getName(), $result1[1]->getName()); + + $this->cache->evictEntity(Country::CLASSNAME, $result1[0]->getId()); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $result1[0]->getId())); $this->_em->clear(); @@ -99,9 +119,11 @@ public function testLoadFromDatabaseWhenEntityMissing() ->setCacheable(true) ->getResult(); - $this->assertEquals($queryCount + 1 , $this->getCurrentQueryCount()); + $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); $this->assertCount(2, $result2); + $this->assertNotInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); + $this->assertNotInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); @@ -111,6 +133,6 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); - $this->assertEquals($queryCount + 1 , $this->getCurrentQueryCount()); + $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); } } \ No newline at end of file From 014c2b7b91cd0e3f11817a4e363012414608d7fd Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 17 Jun 2013 16:54:33 -0400 Subject: [PATCH 22/71] reload collection when entity cache is missing --- lib/Doctrine/ORM/Cache/DefaultCache.php | 2 +- .../Cache/DefaultCollectionEntryStructure.php | 24 +++++--- .../AbstractCollectionPersister.php | 59 +++++++++++-------- .../ORM/Persisters/BasicEntityPersister.php | 8 +-- .../ORM/Persisters/ManyToManyPersister.php | 39 ++++++------ .../ORM/Persisters/OneToManyPersister.php | 17 +++--- .../DefaultCollectionEntryStructureTest.php | 40 +++++++------ .../SecondLevelCacheOneToManyTest.php | 37 ++++++++++++ 8 files changed, 137 insertions(+), 89 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 2dd94c81ed5..7400c239ef8 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -230,7 +230,7 @@ public function evictCollectionRegions() $persister = $this->uow->getCollectionPersister($association); if ( ! $persister->hasCache()) { - return; + continue; } $persister->getCacheRegionAcess()->evictAll(); diff --git a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php index 13fe52804fd..88f96373736 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php @@ -60,8 +60,8 @@ public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key { $data = array(); - foreach ($collection as $key => $entity) { - $data[$key] = $this->uow->getEntityIdentifier($entity); + foreach ($collection as $index => $entity) { + $data[$index] = $this->uow->getEntityIdentifier($entity); } return new CollectionCacheEntry($data); @@ -72,15 +72,25 @@ public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key */ public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection) { - $list = array(); + $targetEntity = $metadata->associationMappings[$key->association]['targetEntity']; + $targetPersister = $this->uow->getEntityPersister($targetEntity); + $targetRegion = $targetPersister->getCacheRegionAcess()->getRegion(); + $list = array(); - foreach ($entry->dataList as $key => $entry) { - $entity = $this->em->getReference($metadata->rootEntityName, $entry); - $list[$key] = $entity; + foreach ($entry->dataList as $index => $entry) { - $collection->hydrateSet($key, $entity); + if ( ! $targetRegion->contains(new EntityCacheKey($targetEntity, $entry))) { + return null; + } + + $entity = $this->em->getReference($targetEntity, $entry); + $list[$index] = $entity; } + array_walk($list, function($entity, $index) use ($collection){ + $collection->hydrateSet($index, $entity); + }); + return $list; } } diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 84203d3f543..073ea0c1661 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -64,6 +64,16 @@ abstract class AbstractCollectionPersister */ protected $quoteStrategy; + /** + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + protected $sourceEntity; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + protected $targetEntity; + /** * @var array */ @@ -108,12 +118,13 @@ public function __construct(EntityManager $em, array $association) $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); + $this->targetEntity = $em->getClassMetadata($association['targetEntity']); $this->hasCache = isset($association['cache']) && $em->getConfiguration()->isSecondLevelCacheEnabled(); if ($this->hasCache) { - $sourceClass = $em->getClassMetadata($association['sourceEntity']); $cacheFactory = $em->getConfiguration()->getSecondLevelCacheFactory(); - $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($sourceClass, $association['fieldName']); + $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($this->sourceEntity, $association['fieldName']); $this->cacheEntryStructure = $cacheFactory->buildCollectionEntryStructure($em); $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); } @@ -254,41 +265,43 @@ public function insertRows(PersistentCollection $coll) * * @return \Doctrine\ORM\PersistentCollection|null */ - public function loadCachedCollection(PersistentCollection $collection, CollectionCacheKey $key) + public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key) { - $metadata = $collection->getTypeClass(); - $cache = $this->cacheRegionAccess->get($key); - if ($cache === null) { + if (($cache = $this->cacheRegionAccess->get($key)) === null) { return null; } - return $this->cacheEntryStructure->loadCacheEntry($metadata, $key, $cache, $collection); + if (($cache = $this->cacheEntryStructure->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) { + return null; + } + + return $cache; } /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $targetMetadata * @param \Doctrine\ORM\Cache\CollectionCacheKey $key * @param array|\Doctrine\Common\Collections\Collection $elements * * @return void */ - public function saveLoadedCollection(ClassMetadata $targetMetadata, CollectionCacheKey $key, $elements) + public function saveCollectionCache(CollectionCacheKey $key, $elements) { - $targetClass = $this->association['targetEntity']; - $targetPersister = $this->uow->getEntityPersister($targetClass); + $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); $targetRegionAcess = $targetPersister->getCacheRegionAcess(); - $entry = $this->cacheEntryStructure->buildCacheEntry($targetMetadata, $key, $elements); + $targetStructure = $targetPersister->getCacheEntryStructure(); + $targetRegion = $targetRegionAcess->getRegion(); + $entry = $this->cacheEntryStructure->buildCacheEntry($this->targetEntity, $key, $elements); foreach ($entry->dataList as $index => $identifier) { - $entityKey = new EntityCacheKey($targetClass, $identifier); + $entityKey = new EntityCacheKey($this->targetEntity->rootEntityName, $identifier); - if ($targetRegionAcess->getRegion()->contains($entityKey)) { + if ($targetRegion->contains($entityKey)) { continue; } $entity = $elements[$index]; - $entityEntry = $targetPersister->getCacheEntryStructure()->buildCacheEntry($targetMetadata, $entityKey, $entity); + $entityEntry = $targetStructure->buildCacheEntry($this->targetEntity, $entityKey, $entity); $targetRegionAcess->put($entityKey, $entityEntry); } @@ -307,18 +320,12 @@ public function afterTransactionComplete() $uow = $this->em->getUnitOfWork(); - if ( ! empty($this->queuedCache)) { + foreach ($this->queuedCache as $item) { + $elements = $item['elements']; + $ownerId = $uow->getEntityIdentifier($item['owner']); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - $association = $this->association['fieldName']; - $mapping = $this->em->getClassMetadata($this->association['sourceEntity']); - - foreach ($this->queuedCache as $item) { - $elements = $item['elements']; - $ownerId = $uow->getEntityIdentifier($item['owner']); - $key = new CollectionCacheKey($mapping->rootEntityName, $association, $ownerId); - - $this->saveLoadedCollection($mapping, $key, $elements); - } + $this->saveCollectionCache($key, $elements); } $this->queuedCache = array(); diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 6939a82f2ff..dc9695bf974 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1154,7 +1154,7 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent if ($hasCache) { $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $collPersister->loadCachedCollection($coll, $key); + $list = $collPersister->loadCollectionCache($coll, $key); if ($list !== null) { return $list; @@ -1165,7 +1165,7 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); if ($hasCache && ! empty($list)) { - $collPersister->saveLoadedCollection($coll->getTypeClass(), $key, $list); + $collPersister->saveCollectionCache($key, $list); } return $list; @@ -1897,7 +1897,7 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC if ($hasCache) { $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $collPersister->loadCachedCollection($coll, $key); + $list = $collPersister->loadCollectionCache($coll, $key); if ($list !== null) { return $list; @@ -1908,7 +1908,7 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); if ($hasCache && ! empty($list)) { - $collPersister->saveLoadedCollection($coll->getTypeClass(), $key, $list); + $collPersister->saveCollectionCache($key, $list); } return $list; diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 1ec6e5e7c1f..363f1de46ca 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -42,7 +42,7 @@ protected function getDeleteRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); - $class = $this->em->getClassMetadata(get_class($coll->getOwner())); + $class = $this->sourceEntity; $tableName = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { @@ -90,7 +90,7 @@ protected function getInsertRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); - $class = $this->em->getClassMetadata(get_class($coll->getOwner())); + $class = $this->sourceEntity; $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { @@ -132,30 +132,25 @@ private function collectJoinTableColumnParameters(PersistentCollection $coll, $e $mapping = $coll->getMapping(); $isComposite = count($mapping['joinTableColumns']) > 2; - $identifier1 = $this->uow->getEntityIdentifier($coll->getOwner()); - $identifier2 = $this->uow->getEntityIdentifier($element); - - if ($isComposite) { - $class1 = $this->em->getClassMetadata(get_class($coll->getOwner())); - $class2 = $coll->getTypeClass(); - } + $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); + $targetId = $this->uow->getEntityIdentifier($element); foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); if ( ! $isComposite) { - $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); + $params[] = $isRelationToSource ? array_pop($ownerId) : array_pop($targetId); continue; } if ($isRelationToSource) { - $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; + $params[] = $ownerId[$this->sourceEntity->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; continue; } - $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; + $params[] = $targetId[$this->targetEntity->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; } return $params; @@ -170,7 +165,7 @@ protected function getDeleteSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); - $class = $this->em->getClassMetadata(get_class($coll->getOwner())); + $class = $this->sourceEntity; $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { @@ -199,7 +194,7 @@ protected function getDeleteSQLParameters(PersistentCollection $coll) } // Composite identifier - $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); + $sourceClass = $this->sourceEntity; $params = array(); foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { @@ -220,11 +215,11 @@ public function count(PersistentCollection $coll) $params = array(); $mapping = $coll->getMapping(); $association = $mapping; - $class = $this->em->getClassMetadata($mapping['sourceEntity']); + $class = $this->sourceEntity; $id = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); if ( ! $mapping['isOwningSide']) { - $targetEntity = $this->em->getClassMetadata($mapping['targetEntity']); + $targetEntity = $this->targetEntity; $association = $targetEntity->associationMappings[$mapping['mappedBy']]; } @@ -343,15 +338,15 @@ private function getJoinTableRestrictions(PersistentCollection $coll, $element, $mapping = $filterMapping; if ( ! $mapping['isOwningSide']) { - $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']); - $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']); + $sourceClass = $this->targetEntity; + $targetClass = $this->sourceEntity; $sourceId = $uow->getEntityIdentifier($element); $targetId = $uow->getEntityIdentifier($coll->getOwner()); $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; } else { - $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); - $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); + $sourceClass = $this->sourceEntity; + $targetClass = $this->targetEntity; $sourceId = $uow->getEntityIdentifier($coll->getOwner()); $targetId = $uow->getEntityIdentifier($element); } @@ -406,7 +401,7 @@ private function getJoinTableRestrictions(PersistentCollection $coll, $element, */ public function getFilterSql($mapping) { - $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); + $targetClass = $this->targetEntity; $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); $filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); @@ -418,7 +413,7 @@ public function getFilterSql($mapping) $association = $mapping; if ( ! $mapping['isOwningSide']) { - $class = $this->em->getClassMetadata($mapping['targetEntity']); + $class = $this->targetEntity; $association = $class->associationMappings[$mapping['mappedBy']]; } diff --git a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php index 2915fd2ed89..db86f582b9f 100644 --- a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php @@ -62,10 +62,8 @@ public function get(PersistentCollection $coll, $index) */ protected function getDeleteRowSQL(PersistentCollection $coll) { - $mapping = $coll->getMapping(); - $class = $this->em->getClassMetadata($mapping['targetEntity']); - $tableName = $this->quoteStrategy->getTableName($class, $this->platform); - $idColumns = $class->getIdentifierColumnNames(); + $tableName = $this->quoteStrategy->getTableName($this->targetEntity, $this->platform); + $idColumns = $this->quoteStrategy->getIdentifierColumnNames($this->targetEntity, $this->platform); return 'DELETE FROM ' . $tableName . ' WHERE ' . implode('= ? AND ', $idColumns) . ' = ?'; @@ -135,8 +133,8 @@ protected function getDeleteSQLParameters(PersistentCollection $coll) public function count(PersistentCollection $coll) { $mapping = $coll->getMapping(); - $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); - $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); + $targetClass = $this->targetEntity; + $sourceClass = $this->sourceEntity; $id = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $whereClauses = array(); @@ -237,10 +235,9 @@ public function removeElement(PersistentCollection $coll, $element) return false; } - $mapping = $coll->getMapping(); - $class = $this->em->getClassMetadata($mapping['targetEntity']); - $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform) - . ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?'; + $tableName = $this->quoteStrategy->getTableName($this->targetEntity, $this->platform); + $idColumns = $this->quoteStrategy->getIdentifierColumnNames($this->targetEntity, $this->platform); + $sql = 'DELETE FROM ' . $tableName . ' WHERE ' . implode('= ? AND ', $idColumns) . ' = ?'; return (bool) $this->conn->executeUpdate($sql, $this->getDeleteRowSQLParameters($coll, $element)); } diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php index 89a26cac340..95996d31cd1 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php @@ -3,10 +3,12 @@ namespace Doctrine\Tests\ORM\Cache; use Doctrine\ORM\UnitOfWork; -use Doctrine\Tests\OrmTestCase; use Doctrine\ORM\PersistentCollection; use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\City; +use Doctrine\Tests\OrmFunctionalTestCase; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\EntityCacheEntry; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\CollectionCacheEntry; use Doctrine\Common\Collections\ArrayCollection; @@ -15,24 +17,19 @@ /** * @group DDC-2183 */ -class CollectionEntryStructureTest extends OrmTestCase +class CollectionEntryStructureTest extends OrmFunctionalTestCase { /** * @var \Doctrine\ORM\Cache\CollectionEntryStructure */ private $structure; - /** - * @var \Doctrine\ORM\EntityManager - */ - private $em; - protected function setUp() { + $this->enableSecondLevelCache(); parent::setUp(); - $this->em = $this->_getTestEntityManager(); - $this->structure = new DefaultCollectionEntryStructure($this->em); + $this->structure = new DefaultCollectionEntryStructure($this->_em); } public function testImplementsCollectionEntryStructure() @@ -42,24 +39,29 @@ public function testImplementsCollectionEntryStructure() public function testLoadCacheCollection() { - $entry = new CollectionCacheEntry(array( + $targetRegion = $this->_em->getCache()->getEntityCacheRegionAccess(City::CLASSNAME); + $entry = new CollectionCacheEntry(array( array('id'=>31), array('id'=>32), )); - $sourceClass = $this->em->getClassMetadata(State::CLASSNAME); - $targetClass = $this->em->getClassMetadata(City::CLASSNAME); + $targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>31)), new EntityCacheEntry(array('id'=>31, 'name'=>'Foo'))); + $targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>32)), new EntityCacheEntry(array('id'=>32, 'name'=>'Bar'))); + + $sourceClass = $this->_em->getClassMetadata(State::CLASSNAME); + $targetClass = $this->_em->getClassMetadata(City::CLASSNAME); $key = new CollectionCacheKey($sourceClass->name, 'cities', array('id'=>21)); - $collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); + $collection = new PersistentCollection($this->_em, $targetClass, new ArrayCollection()); $list = $this->structure->loadCacheEntry($sourceClass, $key, $entry, $collection); + $this->assertNotNull($list); $this->assertCount(2, $list); $this->assertCount(2, $collection); - $this->assertInstanceOf($sourceClass->name, $list[0]); - $this->assertInstanceOf($sourceClass->name, $list[1]); - $this->assertInstanceOf($sourceClass->name, $collection[0]); - $this->assertInstanceOf($sourceClass->name, $collection[1]); + $this->assertInstanceOf($targetClass->name, $list[0]); + $this->assertInstanceOf($targetClass->name, $list[1]); + $this->assertInstanceOf($targetClass->name, $collection[0]); + $this->assertInstanceOf($targetClass->name, $collection[1]); $this->assertSame($list[0], $collection[0]); $this->assertSame($list[1], $collection[1]); @@ -68,8 +70,8 @@ public function testLoadCacheCollection() $this->assertEquals(32, $list[1]->getId()); $this->assertEquals($list[0]->getId(), $collection[0]->getId()); $this->assertEquals($list[1]->getId(), $collection[1]->getId()); - $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($collection[0])); - $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->em->getUnitOfWork()->getEntityState($collection[1])); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($collection[0])); + $this->assertEquals(UnitOfWork::STATE_MANAGED, $this->_em->getUnitOfWork()->getEntityState($collection[1])); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 48746562e2b..a1b45c7b448 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -174,4 +174,41 @@ public function testStoreOneToManyAssociationWhitCascade() $this->assertEquals($queryCount2 + 1, $this->getCurrentQueryCount()); } + + public function testLoadOnoToManyCollectionFromDatabaseWhenEntityMissing() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(0)->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(1)->getId())); + + $queryCount = $this->getCurrentQueryCount(); + $stateId = $this->states[0]->getId(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + $cityId = $this->states[0]->getCities()->get(1)->getId(); + + //trigger lazy load from cache + $this->assertCount(2, $state->getCities()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $cityId)); + + $this->cache->evictEntity(City::CLASSNAME, $cityId); + + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $cityId)); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $stateId)); + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + + $this->_em->clear(); + + $state = $this->_em->find(State::CLASSNAME, $stateId); + + //trigger lazy load from database + $this->assertCount(2, $state->getCities()); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + } } \ No newline at end of file From 894e3c8318106ae1ca791b6a6d24134f93112ece Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 17 Jun 2013 18:14:42 -0400 Subject: [PATCH 23/71] single table inheritance mapping --- .../ORM/Mapping/ClassMetadataFactory.php | 4 + .../Tests/Models/Cache/Attraction.php | 74 +++++++++++++++++++ tests/Doctrine/Tests/Models/Cache/Bar.php | 11 +++ tests/Doctrine/Tests/Models/Cache/Beach.php | 11 +++ tests/Doctrine/Tests/Models/Cache/City.php | 22 +++++- .../Tests/Models/Cache/Restaurant.php | 11 +++ .../SecondLevelCacheAbstractTest.php | 32 ++++++-- ...ndLevelCacheSingleTableInheritanceTest.php | 41 ++++++++++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 5 ++ 9 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/Cache/Attraction.php create mode 100644 tests/Doctrine/Tests/Models/Cache/Bar.php create mode 100644 tests/Doctrine/Tests/Models/Cache/Beach.php create mode 100644 tests/Doctrine/Tests/Models/Cache/Restaurant.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php index 9320d1b6d50..62634a1fd59 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php @@ -144,6 +144,10 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS $class->setPrimaryTable($parent->table); } + if ($parent && $parent->cache) { + $class->cache = $parent->cache; + } + if ($parent && $parent->containsForeignIdentifier) { $class->containsForeignIdentifier = true; } diff --git a/tests/Doctrine/Tests/Models/Cache/Attraction.php b/tests/Doctrine/Tests/Models/Cache/Attraction.php new file mode 100644 index 00000000000..e6e74cf31fc --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/Attraction.php @@ -0,0 +1,74 @@ +name = $name; + $this->city = $city; + } + + public function getId() + { + return $this->id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getCity() + { + return $this->city; + } + + public function setCity(City $city) + { + $this->city = $city; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/Bar.php b/tests/Doctrine/Tests/Models/Cache/Bar.php new file mode 100644 index 00000000000..f0d0931270d --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/Bar.php @@ -0,0 +1,11 @@ +name = $name; - $this->state = $state; - $this->travels = new ArrayCollection(); + $this->name = $name; + $this->state = $state; + $this->travels = new ArrayCollection(); + $this->attractions = new ArrayCollection(); } public function getId() @@ -83,4 +89,14 @@ public function getTravels() { return $this->travels; } + + public function addAttraction(Attraction $attraction) + { + $this->attractions[] = $attraction; + } + + public function getAttractions() + { + return $this->attractions; + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/Restaurant.php b/tests/Doctrine/Tests/Models/Cache/Restaurant.php new file mode 100644 index 00000000000..00d3ac06183 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/Restaurant.php @@ -0,0 +1,11 @@ +_em->flush(); } + protected function loadFixturesAttractions() + { + $this->attractions[] = new Bar('Boteco São Bento', $this->cities[0]); + $this->attractions[] = new Bar('Prainha Paulista', $this->cities[0]); + $this->attractions[] = new Beach('Copacabana', $this->cities[1]); + $this->attractions[] = new Beach('Ipanema', $this->cities[1]); + $this->attractions[] = new Restaurant('Reinstoff', $this->cities[3]); + $this->attractions[] = new Restaurant('Fischers Fritz', $this->cities[2]); + + foreach ($this->attractions as $attraction) { + $this->_em->persist($attraction); + } + + $this->_em->flush(); + } + + protected function evictRegions() { $this->cache->evictQueryRegions(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php new file mode 100644 index 00000000000..4df66ff74bc --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -0,0 +1,41 @@ +cache->getEntityCacheRegionAccess(Attraction::CLASSNAME)->getRegion(); + $restaurantRegion = $this->cache->getEntityCacheRegionAccess(Restaurant::CLASSNAME)->getRegion(); + $beachRegion = $this->cache->getEntityCacheRegionAccess(Beach::CLASSNAME)->getRegion(); + $barRegion = $this->cache->getEntityCacheRegionAccess(Bar::CLASSNAME)->getRegion(); + + $this->assertEquals($attractionRegion->getName(), $restaurantRegion->getName()); + $this->assertEquals($attractionRegion->getName(), $beachRegion->getName()); + $this->assertEquals($attractionRegion->getName(), $barRegion->getName()); + } + + public function testCountaisRootClass() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + + $this->_em->clear(); + + foreach ($this->attractions as $attraction) { + $this->assertTrue($this->cache->containsEntity(Attraction::CLASSNAME, $attraction->getId())); + $this->assertTrue($this->cache->containsEntity(get_class($attraction), $attraction->getId())); + } + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 3edab4bdf82..2947c78e184 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -181,6 +181,10 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\Cache\City', 'Doctrine\Tests\Models\Cache\Traveler', 'Doctrine\Tests\Models\Cache\Travel', + 'Doctrine\Tests\Models\Cache\Attraction', + 'Doctrine\Tests\Models\Cache\Restaurant', + 'Doctrine\Tests\Models\Cache\Beach', + 'Doctrine\Tests\Models\Cache\Bar', ), ); @@ -324,6 +328,7 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM cache_city'); $conn->executeUpdate('DELETE FROM cache_state'); $conn->executeUpdate('DELETE FROM cache_country'); + $conn->executeUpdate('DELETE FROM cache_attraction'); } $this->_em->clear(); From 961c7738207d31ed0525a99189cbab330efd17e1 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 18 Jun 2013 12:40:26 -0400 Subject: [PATCH 24/71] Basic inheritance support --- .../ORM/Cache/DefaultEntityEntryStructure.php | 4 +- lib/Doctrine/ORM/Cache/EntityCacheEntry.php | 13 ++- lib/Doctrine/ORM/Cache/EntityCacheKey.php | 4 +- .../ORM/Persisters/BasicEntityPersister.php | 14 ++- .../ORM/Cache/AbstractRegionAccessTest.php | 43 +++---- .../Tests/ORM/Cache/DefaultCacheTest.php | 6 +- .../DefaultCollectionEntryStructureTest.php | 4 +- .../Cache/DefaultEntityEntryStructureTest.php | 4 +- .../SecondLevelCacheAbstractTest.php | 2 +- ...ndLevelCacheSingleTableInheritanceTest.php | 109 ++++++++++++++++++ 10 files changed, 162 insertions(+), 41 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php index f3278a724dc..28ce62e2387 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -76,7 +76,7 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e unset($data[$name]); } - return new EntityCacheEntry($data); + return new EntityCacheEntry($metadata->name, $data); } /** @@ -89,6 +89,6 @@ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, Ent $hints[Query::HINT_REFRESH_ENTITY] = $entity; } - return $this->uow->createEntity($metadata->name, $entry->data, $hints); + return $this->uow->createEntity($entry->class, $entry->data, $hints); } } diff --git a/lib/Doctrine/ORM/Cache/EntityCacheEntry.php b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php index 10cadb49c17..d71aca65b5c 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php @@ -34,10 +34,17 @@ class EntityCacheEntry implements CacheEntry public $data; /** - * @param array $data + * @var string */ - public function __construct(array $data) + public $class; + + /** + * @param string $class The entity class. + * @param array $data The entity data. + */ + public function __construct($class, array $data) { - $this->data = $data; + $this->class = $class; + $this->data = $data; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/EntityCacheKey.php b/lib/Doctrine/ORM/Cache/EntityCacheKey.php index 25bc0dcd90b..a44fe4e6a4d 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheKey.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheKey.php @@ -44,8 +44,8 @@ class EntityCacheKey implements CacheKey private $hash; /** - * @param string $entityClass - * @param array $identifier + * @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class. + * @param array $identifier The entity identifier */ public function __construct($entityClass, array $identifier) { diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index dc9695bf974..620109ab9a0 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -38,7 +38,6 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\Cache\EntityEntryStructure; use Doctrine\ORM\Cache\ConcurrentRegionAccess; /** @@ -889,7 +888,8 @@ public function loadById(array $identifier, $entity = null, $lockMode = 0) $entity = $this->load($identifier, $entity, null, array(), $lockMode); if ($this->hasCache && $entity !== null) { - $cacheEntry = $this->cacheEntryStructure->buildCacheEntry($this->class, $cacheKey, $entity); + $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); + $cacheEntry = $this->cacheEntryStructure->buildCacheEntry($class, $cacheKey, $entity); $this->cacheRegionAccess->put($cacheKey, $cacheEntry); } @@ -2164,8 +2164,9 @@ public function afterTransactionComplete() if (isset($this->queuedCache['insert'])) { foreach ($this->queuedCache['insert'] as $item) { - $key = new EntityCacheKey($this->class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); - $entry = $this->cacheEntryStructure->buildCacheEntry($this->class, $key, $item['entity']); + $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); + $key = new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); + $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); $this->cacheRegionAccess->afterInsert($key, $entry); } @@ -2174,8 +2175,9 @@ public function afterTransactionComplete() if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { - $key = $item['key'] ?: new EntityCacheKey($this->class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); - $entry = $this->cacheEntryStructure->buildCacheEntry($this->class, $key, $item['entity']); + $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); + $key = new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); + $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); $this->cacheRegionAccess->afterUpdate($key, $entry); diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php index ab7b9c3d73d..1f0f7e1ce64 100644 --- a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php @@ -75,8 +75,8 @@ static public function dataProviderCacheValues() $entityName = '\Doctrine\Tests\Models\Cache\Country'; return array( - array(new EntityCacheKey($entityName, array('id'=>1)), new EntityCacheEntry(array('id'=>1, 'name' => 'bar'))), - array(new EntityCacheKey($entityName, array('id'=>2)), new EntityCacheEntry(array('id'=>2, 'name' => 'foo'))), + array(new EntityCacheKey($entityName, array('id'=>1)), new EntityCacheEntry($entityName, array('id'=>1, 'name' => 'bar'))), + array(new EntityCacheKey($entityName, array('id'=>2)), new EntityCacheEntry($entityName, array('id'=>2, 'name' => 'foo'))), ); } @@ -98,17 +98,18 @@ public function testPutGetAndEvict(EntityCacheKey $key, EntityCacheEntry $entry) public function testEvictAll() { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + $class = '\Doctrine\Tests\Models\Cache\Country'; $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->put($key1, new EntityCacheEntry(array('value' => 'foo'))); - $this->regionAccess->put($key2, new EntityCacheEntry(array('value' => 'bar'))); + $this->regionAccess->put($key1, new EntityCacheEntry($class, array('value' => 'foo'))); + $this->regionAccess->put($key2, new EntityCacheEntry($class, array('value' => 'bar'))); - $this->assertEquals(new EntityCacheEntry(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new EntityCacheEntry(array('value' => 'bar')), $this->regionAccess->get($key2)); + $this->assertEquals(new EntityCacheEntry($class, array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new EntityCacheEntry($class, array('value' => 'bar')), $this->regionAccess->get($key2)); $this->regionAccess->evictAll(); @@ -118,31 +119,33 @@ public function testEvictAll() public function testAfterInsert() { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + $class = '\Doctrine\Tests\Models\Cache\Country'; $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->afterInsert($key1, new EntityCacheEntry(array('value' => 'foo'))); - $this->regionAccess->afterInsert($key2, new EntityCacheEntry(array('value' => 'bar'))); + $this->regionAccess->afterInsert($key1, new EntityCacheEntry($class, array('value' => 'foo'))); + $this->regionAccess->afterInsert($key2, new EntityCacheEntry($class, array('value' => 'bar'))); - $this->assertEquals(new EntityCacheEntry(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new EntityCacheEntry(array('value' => 'bar')), $this->regionAccess->get($key2)); + $this->assertEquals(new EntityCacheEntry($class, array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new EntityCacheEntry($class, array('value' => 'bar')), $this->regionAccess->get($key2)); } public function testAfterUpdate() { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); + $key1 = new DefaultRegionTestKey('key.1'); + $key2 = new DefaultRegionTestKey('key.2'); + $class = '\Doctrine\Tests\Models\Cache\Country'; $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->afterUpdate($key1, new EntityCacheEntry(array('value' => 'foo'))); - $this->regionAccess->afterUpdate($key2, new EntityCacheEntry(array('value' => 'bar'))); + $this->regionAccess->afterUpdate($key1, new EntityCacheEntry($class, array('value' => 'foo'))); + $this->regionAccess->afterUpdate($key2, new EntityCacheEntry($class, array('value' => 'bar'))); - $this->assertEquals(new EntityCacheEntry(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new EntityCacheEntry(array('value' => 'bar')), $this->regionAccess->get($key2)); + $this->assertEquals(new EntityCacheEntry($class, array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new EntityCacheEntry($class, array('value' => 'bar')), $this->regionAccess->get($key2)); } } diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index 368b30679c7..7b220f89122 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -37,8 +37,8 @@ protected function setUp() private function putEntityCacheEntry($className, array $identifier, array $data) { $metadata = $this->_em->getClassMetadata($className); - $cacheKey = new EntityCacheKey($metadata->rootEntityName, $identifier); - $cacheEntry = new EntityCacheEntry($data); + $cacheKey = new EntityCacheKey($metadata->name, $identifier); + $cacheEntry = new EntityCacheEntry($metadata->name, $data); $persister = $this->_em->getUnitOfWork()->getEntityPersister($metadata->rootEntityName); $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); @@ -53,7 +53,7 @@ private function putEntityCacheEntry($className, array $identifier, array $data) private function putCollectionCacheEntry($className, $association, array $ownerIdentifier, array $data) { $metadata = $this->_em->getClassMetadata($className); - $cacheKey = new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier); + $cacheKey = new CollectionCacheKey($metadata->name, $association, $ownerIdentifier); $cacheEntry = new CollectionCacheEntry($data); $persister = $this->_em->getUnitOfWork()->getCollectionPersister($metadata->getAssociationMapping($association)); diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php index 95996d31cd1..6419bc8e49e 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php @@ -45,8 +45,8 @@ public function testLoadCacheCollection() array('id'=>32), )); - $targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>31)), new EntityCacheEntry(array('id'=>31, 'name'=>'Foo'))); - $targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>32)), new EntityCacheEntry(array('id'=>32, 'name'=>'Bar'))); + $targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>31)), new EntityCacheEntry(City::CLASSNAME, array('id'=>31, 'name'=>'Foo'))); + $targetRegion->put(new EntityCacheKey(City::CLASSNAME, array('id'=>32)), new EntityCacheEntry(City::CLASSNAME, array('id'=>32, 'name'=>'Bar'))); $sourceClass = $this->_em->getClassMetadata(State::CLASSNAME); $targetClass = $this->_em->getClassMetadata(City::CLASSNAME); diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php index b9898a7a0f5..9480e86b35b 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php @@ -42,7 +42,7 @@ public function testCreateEntity() { $metadata = $this->em->getClassMetadata(Country::CLASSNAME); $key = new EntityCacheKey($metadata->name, array('id'=>1)); - $entry = new EntityCacheEntry(array('id'=>1, 'name'=>'Foo')); + $entry = new EntityCacheEntry($metadata->name, array('id'=>1, 'name'=>'Foo')); $entity = $this->structure->loadCacheEntry($metadata, $key, $entry); $this->assertInstanceOf($metadata->name, $entity); @@ -56,7 +56,7 @@ public function testLoadProxy() { $metadata = $this->em->getClassMetadata(Country::CLASSNAME); $key = new EntityCacheKey($metadata->name, array('id'=>1)); - $entry = new EntityCacheEntry(array('id'=>1, 'name'=>'Foo')); + $entry = new EntityCacheEntry($metadata->name, array('id'=>1, 'name'=>'Foo')); $proxy = $this->em->getReference($metadata->name, $key->identifier); $entity = $this->structure->loadCacheEntry($metadata, $key, $entry, $proxy); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php index ec4a62eba7d..c7643352c86 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -145,7 +145,7 @@ protected function loadFixturesAttractions() $this->attractions[] = new Beach('Copacabana', $this->cities[1]); $this->attractions[] = new Beach('Ipanema', $this->cities[1]); $this->attractions[] = new Restaurant('Reinstoff', $this->cities[3]); - $this->attractions[] = new Restaurant('Fischers Fritz', $this->cities[2]); + $this->attractions[] = new Restaurant('Fischers Fritz', $this->cities[3]); foreach ($this->attractions as $attraction) { $this->_em->persist($attraction); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index 4df66ff74bc..d8ae32aaeb8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -24,6 +24,19 @@ public function testUseSameRegion() $this->assertEquals($attractionRegion->getName(), $barRegion->getName()); } + public function testPutOnPersistXXX() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Bar::CLASSNAME, $this->attractions[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Bar::CLASSNAME, $this->attractions[1]->getId())); + } + public function testCountaisRootClass() { $this->loadFixturesCountries(); @@ -38,4 +51,100 @@ public function testCountaisRootClass() $this->assertTrue($this->cache->containsEntity(get_class($attraction), $attraction->getId())); } } + + /** + * @group menina + */ + public function testPutAndLoadEntities() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + + $this->_em->clear(); + + $this->cache->evictEntityRegion(Attraction::CLASSNAME); + + $entityId1 = $this->attractions[0]->getId(); + $entityId2 = $this->attractions[1]->getId(); + + $this->assertFalse($this->cache->containsEntity(Attraction::CLASSNAME, $entityId1)); + $this->assertFalse($this->cache->containsEntity(Attraction::CLASSNAME, $entityId2)); + $this->assertFalse($this->cache->containsEntity(Bar::CLASSNAME, $entityId1)); + $this->assertFalse($this->cache->containsEntity(Bar::CLASSNAME, $entityId2)); + + $entity1 = $this->_em->find(Attraction::CLASSNAME, $entityId1); + $entity2 = $this->_em->find(Attraction::CLASSNAME, $entityId2); + + $this->assertTrue($this->cache->containsEntity(Attraction::CLASSNAME, $entityId1)); + $this->assertTrue($this->cache->containsEntity(Attraction::CLASSNAME, $entityId2)); + $this->assertTrue($this->cache->containsEntity(Bar::CLASSNAME, $entityId1)); + $this->assertTrue($this->cache->containsEntity(Bar::CLASSNAME, $entityId2)); + + $this->assertInstanceOf(Attraction::CLASSNAME, $entity1); + $this->assertInstanceOf(Attraction::CLASSNAME, $entity2); + $this->assertInstanceOf(Bar::CLASSNAME, $entity1); + $this->assertInstanceOf(Bar::CLASSNAME, $entity2); + + $this->assertEquals($this->attractions[0]->getId(), $entity1->getId()); + $this->assertEquals($this->attractions[0]->getName(), $entity1->getName()); + + $this->assertEquals($this->attractions[1]->getId(), $entity2->getId()); + $this->assertEquals($this->attractions[1]->getName(), $entity2->getName()); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + + $entity3 = $this->_em->find(Attraction::CLASSNAME, $entityId1); + $entity4 = $this->_em->find(Attraction::CLASSNAME, $entityId2); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->assertInstanceOf(Attraction::CLASSNAME, $entity3); + $this->assertInstanceOf(Attraction::CLASSNAME, $entity4); + $this->assertInstanceOf(Bar::CLASSNAME, $entity3); + $this->assertInstanceOf(Bar::CLASSNAME, $entity4); + + $this->assertNotSame($entity1, $entity3); + $this->assertEquals($entity1->getId(), $entity3->getId()); + $this->assertEquals($entity1->getName(), $entity3->getName()); + + $this->assertNotSame($entity2, $entity4); + $this->assertEquals($entity2->getId(), $entity4->getId()); + $this->assertEquals($entity2->getName(), $entity4->getName()); + } + + public function testQueryCacheFindAll() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT a FROM Doctrine\Tests\Models\Cache\Attraction a'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(count($this->attractions), $result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->_em->clear(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(count($this->attractions), $result2); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + foreach ($result2 as $entity) { + $this->assertInstanceOf(Attraction::CLASSNAME, $entity); + } + } } \ No newline at end of file From 4d058a455d6d36186b28c6a58e756567bb402cc4 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 18 Jun 2013 12:47:46 -0400 Subject: [PATCH 25/71] fix test tear down --- tests/Doctrine/Tests/OrmFunctionalTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 2947c78e184..d1818f4d98d 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -323,12 +323,12 @@ protected function tearDown() if (isset($this->_usedModelSets['cache'])) { $conn->executeUpdate('DELETE FROM cache_visited_cities'); + $conn->executeUpdate('DELETE FROM cache_attraction'); $conn->executeUpdate('DELETE FROM cache_travel'); $conn->executeUpdate('DELETE FROM cache_traveler'); $conn->executeUpdate('DELETE FROM cache_city'); $conn->executeUpdate('DELETE FROM cache_state'); $conn->executeUpdate('DELETE FROM cache_country'); - $conn->executeUpdate('DELETE FROM cache_attraction'); } $this->_em->clear(); From 0ed68451ab467b31385e329bb86c6e006d2e4a51 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 18 Jun 2013 16:44:57 -0400 Subject: [PATCH 26/71] test collection using inheritance --- lib/Doctrine/ORM/Cache/DefaultCache.php | 2 +- .../AbstractCollectionPersister.php | 15 +++-- .../ORM/Persisters/BasicEntityPersister.php | 22 +++---- lib/Doctrine/ORM/UnitOfWork.php | 16 +++--- tests/Doctrine/Tests/Models/Cache/City.php | 2 + .../Tests/ORM/Cache/DefaultCacheTest.php | 15 +++++ .../SecondLevelCacheAbstractTest.php | 9 +++ .../SecondLevelCacheOneToManyTest.php | 42 ++++++++++++++ ...ndLevelCacheSingleTableInheritanceTest.php | 57 +++++++++++++++++++ 9 files changed, 155 insertions(+), 25 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 7400c239ef8..170268472a4 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -331,7 +331,7 @@ private function buildCollectionCacheKey(ClassMetadata $metadata, $association, private function toIdentifierArray(ClassMetadata $metadata, $identifier) { if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) { - $identifier = $this->unitOfWork->getSingleIdentifierValue($identifier); + $identifier = $this->uow->getSingleIdentifierValue($identifier); if ($identifier === null) { throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 073ea0c1661..d75a608cc2a 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -21,7 +21,6 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\PersistentCollection; -use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\Common\Collections\Collection; @@ -166,7 +165,7 @@ public function delete(PersistentCollection $coll) $this->conn->executeUpdate($sql, $this->getDeleteSQLParameters($coll)); if ($this->hasCache) { - $this->queuedCachedCollectionChange($coll->getOwner(), $coll->toArray()); + $this->queuedCachedCollectionChange($coll->getOwner(), null); } } @@ -208,8 +207,8 @@ public function update(PersistentCollection $coll) $this->deleteRows($coll); $this->insertRows($coll); - if ($this->hasCache) { - $this->queuedCachedCollectionChange($coll->getOwner(), $coll->toArray()); + if ($this->hasCache && $coll->isDirty()) { + $this->queuedCachedCollectionChange($coll->getOwner(), $coll); } } @@ -305,7 +304,7 @@ public function saveCollectionCache(CollectionCacheKey $key, $elements) $targetRegionAcess->put($entityKey, $entityEntry); } - + $this->cacheRegionAccess->put($key, $entry); } @@ -325,6 +324,12 @@ public function afterTransactionComplete() $ownerId = $uow->getEntityIdentifier($item['owner']); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + if ($elements === null) { + $this->cacheRegionAccess->evict($key); + + continue; + } + $this->saveCollectionCache($key, $elements); } diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 620109ab9a0..d8d47477cfc 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1147,14 +1147,14 @@ private function loadCollectionFromStatement($assoc, $stmt, $coll) */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { - $collPersister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); - $hasCache = $collPersister->hasCache(); - $key = null; + $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); + $hasCache = $persister->hasCache(); + $key = null; if ($hasCache) { $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $collPersister->loadCollectionCache($coll, $key); + $list = $persister->loadCollectionCache($coll, $key); if ($list !== null) { return $list; @@ -1165,7 +1165,7 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); if ($hasCache && ! empty($list)) { - $collPersister->saveCollectionCache($key, $list); + $persister->saveCollectionCache($key, $list); } return $list; @@ -1890,14 +1890,14 @@ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = nu */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { - $collPersister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); - $hasCache = $collPersister->hasCache(); - $key = null; + $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); + $hasCache = $persister->hasCache(); + $key = null; if ($hasCache) { $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $collPersister->loadCollectionCache($coll, $key); + $list = $persister->loadCollectionCache($coll, $key); if ($list !== null) { return $list; @@ -1906,9 +1906,9 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); - + if ($hasCache && ! empty($list)) { - $collPersister->saveCollectionCache($key, $list); + $persister->saveCollectionCache($key, $list); } return $list; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index a6407754413..efeb13fc5f0 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -366,7 +366,7 @@ public function commit($entity = null) $collectionPersister->delete($collectionToDelete); if ($collectionPersister->hasCache()) { - $this->cachedPersisters[] = $collectionPersister; + $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; } } @@ -377,7 +377,7 @@ public function commit($entity = null) $collectionPersister->update($collectionToUpdate); if ($collectionPersister->hasCache()) { - $this->cachedPersisters[] = $collectionPersister; + $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; } } @@ -817,13 +817,13 @@ private function computeAssociationChanges($entity, $assoc, $value) $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); // Handle colection cache - if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && isset($assoc['cache'])) { - + if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && isset($assoc['cache']) && count($value) > 0) { + $collectionPersister = $this->getCollectionPersister($assoc); $collectionPersister->queuedCachedCollectionChange($entity, $value); - $this->cachedPersisters[] = $collectionPersister; + $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; } foreach ($unwrappedValue as $key => $entry) { @@ -1022,7 +1022,7 @@ private function executeInserts($class) } if ($persister->hasCache()) { - $this->cachedPersisters[$className] = $persister; + $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -1061,7 +1061,7 @@ private function executeUpdates($class) } if ($persister->hasCache()) { - $this->cachedPersisters[$className] = $persister; + $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } } @@ -1105,7 +1105,7 @@ private function executeDeletions($class) } if ($persister->hasCache()) { - $this->cachedPersisters[$className] = $persister; + $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } } diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php index e14b8a593b8..7bf5513daa3 100644 --- a/tests/Doctrine/Tests/Models/Cache/City.php +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -38,6 +38,8 @@ class City public $travels; /** + * @Cache + * @OrderBy({"name" = "ASC"}) * @OneToMany(targetEntity="Attraction", mappedBy="city") */ public $attractions; diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index 7b220f89122..5b9742d1594 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -209,4 +209,19 @@ public function testEvictCollectionRegions() $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); } + public function testToIdentifierArrayShoudLookupForEntityIdentifier() + { + $identifier = 123; + $entity = new Country('Foo'); + $metadata = $this->_em->getClassMetadata(Country::CLASSNAME); + $method = new \ReflectionMethod($this->cache, 'toIdentifierArray'); + $property = new \ReflectionProperty($entity, 'id'); + + $property->setAccessible(true); + $method->setAccessible(true); + $property->setValue($entity, $identifier); + + $this->assertEquals(array('id'=>$identifier), $method->invoke($this->cache, $metadata, $identifier)); + } + } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php index c7643352c86..722d6ae7dd7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -144,9 +144,18 @@ protected function loadFixturesAttractions() $this->attractions[] = new Bar('Prainha Paulista', $this->cities[0]); $this->attractions[] = new Beach('Copacabana', $this->cities[1]); $this->attractions[] = new Beach('Ipanema', $this->cities[1]); + $this->attractions[] = new Bar('Schneider Weisse', $this->cities[2]); $this->attractions[] = new Restaurant('Reinstoff', $this->cities[3]); $this->attractions[] = new Restaurant('Fischers Fritz', $this->cities[3]); + $this->cities[0]->addAttraction($this->attractions[0]); + $this->cities[0]->addAttraction($this->attractions[1]); + $this->cities[1]->addAttraction($this->attractions[2]); + $this->cities[1]->addAttraction($this->attractions[3]); + $this->cities[2]->addAttraction($this->attractions[4]); + $this->cities[3]->addAttraction($this->attractions[5]); + $this->cities[3]->addAttraction($this->attractions[6]); + foreach ($this->attractions as $attraction) { $this->_em->persist($attraction); } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index a1b45c7b448..66402198b5c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -211,4 +211,46 @@ public function testLoadOnoToManyCollectionFromDatabaseWhenEntityMissing() $this->assertCount(2, $state->getCities()); $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); } + + + public function testShoudNotPutOneToManyRelationOnPersistIfTheCollectionIsEmpty() + { + $this->loadFixturesCountries(); + $this->evictRegions(); + + $state = new State("State Foo", $this->countries[0]); + + $this->_em->persist($state); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $state->getId())); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $state->getId())); + + $state = $this->_em->find(State::CLASSNAME, $state); + $this->assertInstanceOf(State::CLASSNAME, $state); + + $city = new City("City Bar", $state); + + $state->addCity($city); + + $this->_em->persist($city); + $this->_em->persist($state); + $this->_em->flush(); + + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $state->getId())); + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $state->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $city->getId())); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $state); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertCount(1, $state->getCities()); + $this->assertInstanceOf(City::CLASSNAME, $state->getCities()->get(0)); + $this->assertEquals($city->getName(), $state->getCities()->get(0)->getName()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index d8ae32aaeb8..c227f535b7f 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -5,6 +5,7 @@ use Doctrine\Tests\Models\Cache\Attraction; use Doctrine\Tests\Models\Cache\Restaurant; use Doctrine\Tests\Models\Cache\Beach; +use Doctrine\Tests\Models\Cache\City; use Doctrine\Tests\Models\Cache\Bar; /** @@ -147,4 +148,60 @@ public function testQueryCacheFindAll() $this->assertInstanceOf(Attraction::CLASSNAME, $entity); } } + + public function testPutOneToManyRelationOnPersist() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + + $this->_em->clear(); + + foreach ($this->cities as $city) { + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $city->getId())); + $this->assertTrue($this->cache->containsCollection(City::CLASSNAME, 'attractions', $city->getId())); + } + + foreach ($this->attractions as $attraction) { + $this->assertTrue($this->cache->containsEntity(Attraction::CLASSNAME, $attraction->getId())); + } + } + + public function testOneToManyRelation() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + $this->evictRegions(); + $this->_em->clear(); + + $entity = $this->_em->find(City::CLASSNAME, $this->cities[0]->getId()); + $this->assertCount(2, $entity->getAttractions()); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $entity->getAttractions()); + + $ownerId = $this->cities[0]->getId(); + $queryCount = $this->getCurrentQueryCount(); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $ownerId)); + $this->assertTrue($this->cache->containsCollection(City::CLASSNAME, 'attractions', $ownerId)); + + $this->assertInstanceOf(Bar::CLASSNAME, $entity->getAttractions()->get(0)); + $this->assertInstanceOf(Bar::CLASSNAME, $entity->getAttractions()->get(1)); + $this->assertEquals($this->attractions[0]->getName(), $entity->getAttractions()->get(0)->getName()); + $this->assertEquals($this->attractions[1]->getName(), $entity->getAttractions()->get(1)->getName()); + + $city = $this->_em->find(City::CLASSNAME, $ownerId); + + $this->assertInstanceOf(City::CLASSNAME, $city); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $city->getAttractions()); + $this->assertCount(2, $city->getAttractions()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->assertInstanceOf(Bar::CLASSNAME, $city->getAttractions()->get(0)); + $this->assertInstanceOf(Bar::CLASSNAME, $city->getAttractions()->get(1)); + $this->assertEquals($this->attractions[0]->getName(), $city->getAttractions()->get(0)->getName()); + $this->assertEquals($this->attractions[1]->getName(), $city->getAttractions()->get(1)->getName()); + } } \ No newline at end of file From f97eca0e9c158445200caaf3084347665df55072 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 18 Jun 2013 17:16:19 -0400 Subject: [PATCH 27/71] check collection cache only if the owner entity cache its enabled --- .../ORM/Persisters/BasicEntityPersister.php | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index d8d47477cfc..2cb8ab001b3 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -1147,20 +1147,25 @@ private function loadCollectionFromStatement($assoc, $stmt, $coll) */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { - $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); - $hasCache = $persister->hasCache(); + $hasCache = false; $key = null; + $persister = null; - if ($hasCache) { - $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); - $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $persister->loadCollectionCache($coll, $key); + if ($this->hasCache) { + $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); + $hasCache = $persister->hasCache(); + + if ($hasCache) { + $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); + $list = $persister->loadCollectionCache($coll, $key); - if ($list !== null) { - return $list; + if ($list !== null) { + return $list; + } } } - + $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); @@ -1890,17 +1895,22 @@ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = nu */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { - $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); - $hasCache = $persister->hasCache(); + $hasCache = false; $key = null; + $persister = null; + + if ($this->hasCache) { + $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); + $hasCache = $persister->hasCache(); - if ($hasCache) { - $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); - $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $persister->loadCollectionCache($coll, $key); + if ($hasCache) { + $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); + $list = $persister->loadCollectionCache($coll, $key); - if ($list !== null) { - return $list; + if ($list !== null) { + return $list; + } } } From db4efdf62bb91a9133f04f4e5ed7f1652dd41a72 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 18 Jun 2013 17:46:40 -0400 Subject: [PATCH 28/71] remove lock mode parameter --- lib/Doctrine/ORM/EntityManager.php | 4 ++-- lib/Doctrine/ORM/Persisters/BasicEntityPersister.php | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/EntityManager.php b/lib/Doctrine/ORM/EntityManager.php index 675e168ad9d..d759f70b2f5 100644 --- a/lib/Doctrine/ORM/EntityManager.php +++ b/lib/Doctrine/ORM/EntityManager.php @@ -482,7 +482,7 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion throw OptimisticLockException::notVersioned($class->name); } - $entity = $persister->loadById($sortedId); + $entity = $persister->load($sortedId); $unitOfWork->lock($entity, $lockMode, $lockVersion); @@ -493,7 +493,7 @@ public function find($entityName, $id, $lockMode = LockMode::NONE, $lockVersion throw TransactionRequiredException::transactionRequired(); } - return $persister->loadById($sortedId, $lockMode); + return $persister->load($sortedId, null, null, array(), $lockMode); } } diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 2cb8ab001b3..f28613908d2 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -866,13 +866,12 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint * * @param array $identifier The entity identifier. * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. - * @param int $lockMode * * @return object The loaded and managed entity instance or NULL if the entity can not be found. * * @todo Check parameters */ - public function loadById(array $identifier, $entity = null, $lockMode = 0) + public function loadById(array $identifier, $entity = null) { $cacheKey = null; @@ -885,7 +884,7 @@ public function loadById(array $identifier, $entity = null, $lockMode = 0) } } - $entity = $this->load($identifier, $entity, null, array(), $lockMode); + $entity = $this->load($identifier, $entity); if ($this->hasCache && $entity !== null) { $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); From 4f7d4ff7d451fcad012f5bbb882ccb4254fee3f9 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 18 Jun 2013 17:56:25 -0400 Subject: [PATCH 29/71] fix cache configuration exception message --- lib/Doctrine/ORM/ORMException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index 731c5baeb0c..bc4bd15c1ad 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -255,7 +255,7 @@ public static function invalidEntityRepository($className) */ public static function invalidSecondLevelCache($className) { - return new self(sprintf('Invalid repository class "%s". It must be a Doctrine\ORM\Cache.', $className)); + return new self(sprintf('Invalid cache class "%s". It must be a Doctrine\ORM\Cache.', $className)); } /** From ffc624d5a7cad51088e1773ef59ee62a869d31b6 Mon Sep 17 00:00:00 2001 From: fabios Date: Wed, 19 Jun 2013 18:43:50 -0400 Subject: [PATCH 30/71] basic cache loggin/stats --- .../ORM/Cache/ConcurrentRegionAccess.php | 6 +- .../ORM/Cache/Logging/CacheLogger.php | 106 +++++++++ .../Cache/Logging/StatisticsCacheLogger.php | 223 ++++++++++++++++++ lib/Doctrine/ORM/Configuration.php | 19 ++ .../AbstractCollectionPersister.php | 19 +- .../ORM/Persisters/BasicEntityPersister.php | 46 +++- .../SecondLevelCacheAbstractTest.php | 9 + ...ndLevelCacheSingleTableInheritanceTest.php | 2 +- .../ORM/Functional/SecondLevelCacheTest.php | 15 ++ .../Doctrine/Tests/OrmFunctionalTestCase.php | 9 +- 10 files changed, 439 insertions(+), 15 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache/Logging/CacheLogger.php create mode 100644 lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php diff --git a/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php b/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php index debd1808df4..d87ab62da0b 100644 --- a/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php +++ b/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php @@ -31,7 +31,7 @@ interface ConcurrentRegionAccess extends RegionAccess /** * We are going to attempt to update/delete the keyed object. * - * @param \Doctrine\ORM\Cache\CacheKey $identifier The key of the item to lock. + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. * * @return \Doctrine\ORM\Cache\Lock A representation of our lock on the item * @@ -42,8 +42,8 @@ public function lockItem(CacheKey $key); /** * Called when we have finished the attempted update/delete (which may or may not have been successful), after transaction completion. * - * @param \Doctrine\ORM\Cache\CacheKey $identifier The item identifier. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to unlock. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} * * @throws \Doctrine\ORM\Cache\CacheException */ diff --git a/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php b/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php new file mode 100644 index 00000000000..df7e052ea8b --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php @@ -0,0 +1,106 @@ +. + */ + +namespace Doctrine\ORM\Cache\Logging; + +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\QueryCacheKey; + +/** + * Interface for logging. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface CacheLogger +{ + /** + * Log an entity put into second level cache. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. + */ + public function entityCachePut($regionName, EntityCacheKey $key); + + /** + * Log an entity get from second level cache resulted in a hit. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. + */ + public function entityCacheHit($regionName, EntityCacheKey $key); + + /** + * Log an entity get from second level cache resulted in a miss. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. + */ + public function entityCacheMiss($regionName, EntityCacheKey $key); + + /** + * Log an entity put into second level cache. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. + */ + public function collectionCachePut($regionName, CollectionCacheKey $key); + + /** + * Log an entity get from second level cache resulted in a hit. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. + */ + public function collectionCacheHit($regionName, CollectionCacheKey $key); + + /** + * Log an entity get from second level cache resulted in a miss. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. + */ + public function collectionCacheMiss($regionName, CollectionCacheKey $key); + + /** + * Log a query put into the query cache. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query. + */ + public function queryCachePut($regionName, QueryCacheKey $key); + + /** + * Log a query get from the query cache resulted in a hit. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query. + */ + public function queryCacheHit($regionName, QueryCacheKey $key); + + /** + * Log a query get from the query cache resulted in a miss. + * + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query. + */ + public function queryCacheMiss($regionName, QueryCacheKey $key); +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php new file mode 100644 index 00000000000..46344378e39 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php @@ -0,0 +1,223 @@ +. + */ + +namespace Doctrine\ORM\Cache\Logging; + +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\QueryCacheKey; + +/** + * Provide basic second level cache statistics. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class StatisticsCacheLogger implements CacheLogger +{ + /** + * @var array + */ + private $cacheMissCountMap = array(); + + /** + * @var array + */ + private $cacheHitCountMap = array(); + + /** + * @var array + */ + private $cachePutCountMap = array(); + + /** + * {@inheritdoc} + */ + public function collectionCacheMiss($regionName, CollectionCacheKey $key) + { + $this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName]) + ? $this->cacheMissCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function collectionCacheHit($regionName, CollectionCacheKey $key) + { + $this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName]) + ? $this->cacheHitCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function collectionCachePut($regionName, CollectionCacheKey $key) + { + $this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName]) + ? $this->cachePutCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function entityCacheMiss($regionName, EntityCacheKey $key) + { + $this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName]) + ? $this->cacheMissCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function entityCacheHit($regionName, EntityCacheKey $key) + { + $this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName]) + ? $this->cacheHitCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function entityCachePut($regionName, EntityCacheKey $key) + { + $this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName]) + ? $this->cachePutCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function queryCacheHit($regionName, QueryCacheKey $key) + { + $this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName]) + ? $this->cacheHitCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function queryCacheMiss($regionName, QueryCacheKey $key) + { + $this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName]) + ? $this->cacheMissCountMap[$regionName] + 1 + : 1; + } + + /** + * {@inheritdoc} + */ + public function queryCachePut($regionName, QueryCacheKey $key) + { + $this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName]) + ? $this->cachePutCountMap[$regionName] + 1 + : 1; + } + + /** + * Get the number of entries successfully retrieved from cache. + * + * @param string $regionName The name of the cache region. + * + * @return integer + */ + public function getRegionHitCount($regionName) + { + return isset($this->cacheHitCountMap[$regionName]) ? $this->cacheHitCountMap[$regionName] : 0; + } + + /** + * Get the number of cached entries *not* found in cache. + * + * @param string $regionName The name of the cache region. + * + * @return integer + */ + public function getRegionMissCount($regionName) + { + return isset($this->cacheMissCountMap[$regionName]) ? $this->cacheMissCountMap[$regionName] : 0; + } + + /** + * Get the number of cacheable entries put in cache. + * + * @param string $regionName The name of the cache region. + * + * @return integer + */ + public function getRegionPutCount($regionName) + { + return isset($this->cachePutCountMap[$regionName]) ? $this->cachePutCountMap[$regionName] : 0; + } + + /** + * Get the total number of put in cache. + * + * @return integer + */ + public function getPutCount() + { + $total = 0; + + foreach ($this->cachePutCountMap as $count) { + $total = $total + $count; + } + + return $total; + } + + /** + * Get the total number of entries successfully retrieved from cache. + * + * @return integer + */ + public function getHitCount() + { + $total = 0; + + foreach ($this->cacheHitCountMap as $count) { + $total = $total + $count; + } + + return $total; + } + + /** + * Get the total number of cached entries *not* found in cache. + * + * @return integer + */ + public function getMissCount() + { + $total = 0; + + foreach ($this->cacheMissCountMap as $count) { + $total = $total + $count; + } + + return $total; + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index b2786cc7a05..821d91d56f7 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -36,6 +36,7 @@ use Doctrine\ORM\Repository\DefaultRepositoryFactory; use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\ORM\Cache\CacheFactory; +use Doctrine\ORM\Cache\Logging\CacheLogger; /** * Configuration container for all configuration options of Doctrine. @@ -315,6 +316,24 @@ public function setSecondLevelCacheDefaultRegionLifetime($lifetime) $this->_attributes['secondLevelCacheDefaultRegionLifetime'] = (integer) $lifetime; } + /** + * @return \Doctrine\ORM\Cache\Logging\CacheLogger|null + */ + public function getSecondLevelCacheLogger() + { + return isset($this->_attributes['secondLevelCacheLogger']) + ? $this->_attributes['secondLevelCacheLogger'] + : null; + } + + /** + * @param \Doctrine\ORM\Cache\Logging\CacheLogger $logger + */ + public function setSecondLevelCacheLogger(CacheLogger $logger) + { + $this->_attributes['secondLevelCacheLogger'] = $logger; + } + /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index d75a608cc2a..b750eea2c8e 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -103,6 +103,11 @@ abstract class AbstractCollectionPersister */ protected $cacheEntryStructure; + /** + * @var \Doctrine\ORM\Cache\Logging\CacheLogger + */ + protected $cacheLogger; + /** * Initializes a new instance of a class derived from AbstractCollectionPersister. * @@ -115,14 +120,16 @@ public function __construct(EntityManager $em, array $association) $this->association = $association; $this->uow = $em->getUnitOfWork(); $this->conn = $em->getConnection(); + $configuration = $em->getConfiguration(); + $this->quoteStrategy = $configuration->getQuoteStrategy(); $this->platform = $this->conn->getDatabasePlatform(); - $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); $this->targetEntity = $em->getClassMetadata($association['targetEntity']); $this->hasCache = isset($association['cache']) && $em->getConfiguration()->isSecondLevelCacheEnabled(); if ($this->hasCache) { - $cacheFactory = $em->getConfiguration()->getSecondLevelCacheFactory(); + $cacheFactory = $configuration->getSecondLevelCacheFactory(); + $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($this->sourceEntity, $association['fieldName']); $this->cacheEntryStructure = $cacheFactory->buildCollectionEntryStructure($em); $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); @@ -304,8 +311,12 @@ public function saveCollectionCache(CollectionCacheKey $key, $elements) $targetRegionAcess->put($entityKey, $entityEntry); } - - $this->cacheRegionAccess->put($key, $entry); + + $cached = $this->cacheRegionAccess->put($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->collectionCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } } /** diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index f28613908d2..036df5d7031 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -235,6 +235,11 @@ class BasicEntityPersister */ protected $cacheEntryStructure; + /** + * @var \Doctrine\ORM\Cache\Logging\CacheLogger + */ + protected $cacheLogger; + /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. @@ -247,14 +252,16 @@ public function __construct(EntityManager $em, ClassMetadata $class) $this->em = $em; $this->class = $class; $this->conn = $em->getConnection(); + $configuration = $em->getConfiguration(); $this->platform = $this->conn->getDatabasePlatform(); - $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); + $this->quoteStrategy = $configuration->getQuoteStrategy(); $this->hasCache = ($class->cache !== null) && $em->getConfiguration()->isSecondLevelCacheEnabled(); if ($this->hasCache) { - $cacheFactory = $em->getConfiguration()->getSecondLevelCacheFactory(); - $this->cacheRegionAccess = $cacheFactory->buildEntityRegionAccessStrategy($this->class); + $cacheFactory = $configuration->getSecondLevelCacheFactory(); + $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); $this->cacheEntryStructure = $cacheFactory->buildEntityEntryStructure($em); + $this->cacheRegionAccess = $cacheFactory->buildEntityRegionAccessStrategy($this->class); $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); } } @@ -880,8 +887,17 @@ public function loadById(array $identifier, $entity = null) $cacheEntry = $this->cacheRegionAccess->get($cacheKey); if ($cacheEntry !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->entityCacheHit($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } + return $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity); } + + if ($this->cacheLogger) { + $this->cacheLogger->entityCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } } $entity = $this->load($identifier, $entity); @@ -889,8 +905,11 @@ public function loadById(array $identifier, $entity = null) if ($this->hasCache && $entity !== null) { $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); $cacheEntry = $this->cacheEntryStructure->buildCacheEntry($class, $cacheKey, $entity); + $cached = $this->cacheRegionAccess->put($cacheKey, $cacheEntry); - $this->cacheRegionAccess->put($cacheKey, $cacheEntry); + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } } return $entity; @@ -1160,8 +1179,17 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent $list = $persister->loadCollectionCache($coll, $key); if ($list !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheHit($this->cacheRegionAccess->getRegion()->getName(), $key); + } + return $list; } + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $key); + } } } @@ -2176,8 +2204,11 @@ public function afterTransactionComplete() $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); $key = new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); + $cached = $this->cacheRegionAccess->afterInsert($key, $entry); - $this->cacheRegionAccess->afterInsert($key, $entry); + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } } } @@ -2187,8 +2218,11 @@ public function afterTransactionComplete() $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); $key = new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); + $cached = $this->cacheRegionAccess->afterUpdate($key, $entry); - $this->cacheRegionAccess->afterUpdate($key, $entry); + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } if ($this->isConcurrentRegion && $item['lock'] !== null) { $this->cacheRegionAccess->unlockItem($key, $item['lock']); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php index 722d6ae7dd7..6a97ae5f376 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -163,6 +163,15 @@ protected function loadFixturesAttractions() $this->_em->flush(); } + protected function getEntityRegion($className) + { + return $this->cache->getEntityCacheRegionAccess($className)->getRegion()->getName(); + } + + protected function getCollectionRegion($className, $association) + { + return $this->cache->getCollectionCacheRegionAccess($className, $association)->getRegion()->getName(); + } protected function evictRegions() { diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index c227f535b7f..fda55f65846 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -25,7 +25,7 @@ public function testUseSameRegion() $this->assertEquals($attractionRegion->getName(), $barRegion->getName()); } - public function testPutOnPersistXXX() + public function testPutOnPersistSingleTableInheritance() { $this->loadFixturesCountries(); $this->loadFixturesStates(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index 10907ad99d4..7713f4009fe 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -18,6 +18,8 @@ public function testPutOnPersist() $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); } public function testPutAndLoadEntities() @@ -25,6 +27,9 @@ public function testPutAndLoadEntities() $this->loadFixturesCountries(); $this->_em->clear(); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); + $this->cache->evictEntityRegion(Country::CLASSNAME); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); @@ -53,6 +58,8 @@ public function testPutAndLoadEntities() $c4 = $this->_em->find(Country::CLASSNAME, $this->countries[1]->getId()); $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); $this->assertInstanceOf(Country::CLASSNAME, $c3); $this->assertInstanceOf(Country::CLASSNAME, $c4); @@ -107,6 +114,10 @@ public function testUpdateEntities() $this->loadFixturesStates(); $this->_em->clear(); + $this->assertEquals(6, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); + $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $this->cache->evictEntityRegion(State::CLASSNAME); $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); @@ -138,6 +149,10 @@ public function testUpdateEntities() $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); + //$this->assertEquals(8, $this->secondLevelCacheLogger->getPutCount()); + //$this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); + //$this->assertEquals(6, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $queryCount = $this->getCurrentQueryCount(); $c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index d1818f4d98d..e049431b25a 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests; -use Doctrine\Common\Cache\Cache; +use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; use Doctrine\ORM\Cache\DefaultCacheFactory; /** @@ -79,6 +79,11 @@ abstract class OrmFunctionalTestCase extends OrmTestCase */ protected static $_entityTablesCreated = array(); + /** + * @var \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger + */ + protected $secondLevelCacheLogger; + /** * List of model sets and their classes. * @@ -451,9 +456,11 @@ protected function _getEntityManager($config = null, $eventManager = null) { $factory = new DefaultCacheFactory($config, $cache); $this->secondLevelCacheFactory = $factory; + $this->secondLevelCacheLogger = new StatisticsCacheLogger(); $config->setSecondLevelCacheEnabled(); $config->setSecondLevelCacheFactory($factory); + $config->setSecondLevelCacheLogger($this->secondLevelCacheLogger); } $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array( From c8ed9be45b16788773ef2f37c4b2489e21ddb222 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 20 Jun 2013 11:31:45 -0400 Subject: [PATCH 31/71] fix cache log and improve hit/miss/put coverage --- .../Cache/Logging/StatisticsCacheLogger.php | 22 +++++ .../AbstractCollectionPersister.php | 3 +- .../ORM/Persisters/BasicEntityPersister.php | 29 ++++--- .../ORM/Persisters/CachedPersister.php | 52 +++++++++++ lib/Doctrine/ORM/UnitOfWork.php | 2 +- .../SecondLevelCacheManyToManyTest.php | 18 ++++ .../SecondLevelCacheOneToManyTest.php | 86 ++++++++++++------- .../ORM/Functional/SecondLevelCacheTest.php | 26 +++++- 8 files changed, 192 insertions(+), 46 deletions(-) create mode 100644 lib/Doctrine/ORM/Persisters/CachedPersister.php diff --git a/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php index 46344378e39..ebbff91ded9 100644 --- a/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php +++ b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php @@ -173,6 +173,28 @@ public function getRegionPutCount($regionName) return isset($this->cachePutCountMap[$regionName]) ? $this->cachePutCountMap[$regionName] : 0; } + /** + * Clear region statistics + * + * @param string $regionName The name of the cache region. + */ + public function clearRegionStats($regionName) + { + $this->cachePutCountMap[$regionName] = 0; + $this->cacheHitCountMap[$regionName] = 0; + $this->cacheMissCountMap[$regionName] = 0; + } + + /** + * Clear all statistics + */ + public function clearStats() + { + $this->cachePutCountMap = array(); + $this->cacheHitCountMap = array(); + $this->cacheMissCountMap = array(); + } + /** * Get the total number of put in cache. * diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index b750eea2c8e..b90b2a33c75 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -23,7 +23,6 @@ use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Cache\ConcurrentRegionAccess; /** @@ -32,7 +31,7 @@ * @since 2.0 * @author Roman Borschel */ -abstract class AbstractCollectionPersister +abstract class AbstractCollectionPersister implements CachedPersister { /** * @var EntityManager diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 036df5d7031..f5427b41c7b 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -82,7 +82,7 @@ * @author Fabio B. Silva * @since 2.0 */ -class BasicEntityPersister +class BasicEntityPersister implements CachedPersister { /** * @var array @@ -894,10 +894,6 @@ public function loadById(array $identifier, $entity = null) return $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity); } - - if ($this->cacheLogger) { - $this->cacheLogger->entityCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); - } } $entity = $this->load($identifier, $entity); @@ -910,6 +906,10 @@ public function loadById(array $identifier, $entity = null) if ($this->cacheLogger && $cached) { $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); } + + if ($this->cacheLogger) { + $this->cacheLogger->entityCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } } return $entity; @@ -1181,15 +1181,11 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent if ($list !== null) { if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheHit($this->cacheRegionAccess->getRegion()->getName(), $key); + $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); } return $list; } - - if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $key); - } } } @@ -1198,6 +1194,10 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent if ($hasCache && ! empty($list)) { $persister->saveCollectionCache($key, $list); + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + } } return $list; @@ -1936,6 +1936,11 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC $list = $persister->loadCollectionCache($coll, $key); if ($list !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + } + return $list; } } @@ -1946,6 +1951,10 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC if ($hasCache && ! empty($list)) { $persister->saveCollectionCache($key, $list); + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + } } return $list; diff --git a/lib/Doctrine/ORM/Persisters/CachedPersister.php b/lib/Doctrine/ORM/Persisters/CachedPersister.php new file mode 100644 index 00000000000..f3742c8a06b --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/CachedPersister.php @@ -0,0 +1,52 @@ +. + */ +namespace Doctrine\ORM\Persisters; + +/** + * Interface for persister that support second level cache. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface CachedPersister +{ + /** + * Perform whatever processing is encapsulated here after completion of the transaction. + */ + public function afterTransactionComplete(); + + /** + * Perform whatever processing is encapsulated here after completion of the rolled-back. + */ + public function afterTransactionRolledBack(); + + /** + * Gets the The region access. + * + *@return \Doctrine\ORM\Cache\RegionAccess + */ + public function getCacheRegionAcess(); + + /** + * Returns TRUE if the persists is cacheable, FALSE otherwise + * + * @return boolean + */ + public function hasCache(); +} diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index efeb13fc5f0..4a7bccf6524 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -262,7 +262,7 @@ class UnitOfWork implements PropertyChangedListener private $eagerLoadingEntities = array(); /** - * @var array + * @var array<\Doctrine\ORM\Persisters\CachedPersister> */ private $cachedPersisters = array(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php index d4daf303a17..c0bf98d07da 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -42,6 +42,7 @@ public function testPutAndLoadManyToManyRelation() $this->loadFixturesTravels(); $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); $this->cache->evictEntityRegion(Travel::CLASSNAME); $this->cache->evictEntityRegion(City::CLASSNAME); @@ -61,10 +62,20 @@ public function testPutAndLoadManyToManyRelation() $t1 = $this->_em->find(Travel::CLASSNAME, $this->travels[0]->getId()); $t2 = $this->_em->find(Travel::CLASSNAME, $this->travels[1]->getId()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Travel::CLASSNAME))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getEntityRegion(Travel::CLASSNAME))); + //trigger lazy load $this->assertCount(3, $t1->getVisitedCities()); $this->assertCount(2, $t2->getVisitedCities()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getCollectionRegion(Travel::CLASSNAME, 'visitedCities'))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getCollectionRegion(Travel::CLASSNAME, 'visitedCities'))); + $this->assertInstanceOf(City::CLASSNAME, $t1->getVisitedCities()->get(0)); $this->assertInstanceOf(City::CLASSNAME, $t1->getVisitedCities()->get(1)); $this->assertInstanceOf(City::CLASSNAME, $t1->getVisitedCities()->get(2)); @@ -84,6 +95,7 @@ public function testPutAndLoadManyToManyRelation() $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[3]->getId())); $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); $queryCount = $this->getCurrentQueryCount(); @@ -101,6 +113,10 @@ public function testPutAndLoadManyToManyRelation() $this->assertInstanceOf(City::CLASSNAME, $t4->getVisitedCities()->get(0)); $this->assertInstanceOf(City::CLASSNAME, $t4->getVisitedCities()->get(1)); + $this->assertEquals(4, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Travel::CLASSNAME))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(Travel::CLASSNAME, 'visitedCities'))); + $this->assertNotSame($t1->getVisitedCities()->get(0), $t3->getVisitedCities()->get(0)); $this->assertEquals($t1->getVisitedCities()->get(0)->getId(), $t3->getVisitedCities()->get(0)->getId()); $this->assertEquals($t1->getVisitedCities()->get(0)->getName(), $t3->getVisitedCities()->get(0)->getName()); @@ -121,6 +137,8 @@ public function testPutAndLoadManyToManyRelation() $this->assertEquals($t2->getVisitedCities()->get(1)->getId(), $t4->getVisitedCities()->get(1)->getId()); $this->assertEquals($t2->getVisitedCities()->get(1)->getName(), $t4->getVisitedCities()->get(1)->getName()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(City::CLASSNAME))); + $this->assertEquals(8, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 66402198b5c..f39493df876 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -2,9 +2,11 @@ namespace Doctrine\Tests\ORM\Functional; -use Doctrine\Tests\Models\Cache\State; + use Doctrine\Tests\Models\Cache\City; +use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Travel; +use Doctrine\Tests\Models\Cache\Country; use Doctrine\Tests\Models\Cache\Traveler; /** @@ -29,6 +31,12 @@ public function testPutOnPersist() $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(1)->getId())); $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(0)->getId())); $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(1)->getId())); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); + $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(City::CLASSNAME))); + $this->assertEquals(12, $this->secondLevelCacheLogger->getPutCount()); } public function testPutAndLoadOneToManyRelation() @@ -37,6 +45,7 @@ public function testPutAndLoadOneToManyRelation() $this->loadFixturesStates(); $this->loadFixturesCities(); $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); $this->cache->evictEntityRegion(State::CLASSNAME); $this->cache->evictEntityRegion(City::CLASSNAME); @@ -53,18 +62,28 @@ public function testPutAndLoadOneToManyRelation() $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(0)->getId())); $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(1)->getId())); - $c1 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); - $c2 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + $s1 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $s2 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getEntityRegion(State::CLASSNAME))); //trigger lazy load - $this->assertCount(2, $c1->getCities()); - $this->assertCount(2, $c2->getCities()); + $this->assertCount(2, $s1->getCities()); + $this->assertCount(2, $s2->getCities()); - $this->assertInstanceOf(City::CLASSNAME, $c1->getCities()->get(0)); - $this->assertInstanceOf(City::CLASSNAME, $c1->getCities()->get(1)); + $this->assertEquals(4, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); - $this->assertInstanceOf(City::CLASSNAME, $c2->getCities()->get(0)); - $this->assertInstanceOf(City::CLASSNAME, $c2->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $s1->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $s1->getCities()->get(1)); + + $this->assertInstanceOf(City::CLASSNAME, $s2->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $s2->getCities()->get(1)); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); @@ -78,37 +97,44 @@ public function testPutAndLoadOneToManyRelation() $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(1)->getId())); $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); $queryCount = $this->getCurrentQueryCount(); - $c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); - $c4 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); - + $s3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $s4 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + //trigger lazy load from cache - $this->assertCount(2, $c3->getCities()); - $this->assertCount(2, $c4->getCities()); + $this->assertCount(2, $s3->getCities()); + $this->assertCount(2, $s4->getCities()); + + $this->assertEquals(4, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); - $this->assertInstanceOf(City::CLASSNAME, $c3->getCities()->get(0)); - $this->assertInstanceOf(City::CLASSNAME, $c3->getCities()->get(1)); - $this->assertInstanceOf(City::CLASSNAME, $c4->getCities()->get(0)); - $this->assertInstanceOf(City::CLASSNAME, $c4->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $s3->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $s3->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $s4->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $s4->getCities()->get(1)); - $this->assertNotSame($c1->getCities()->get(0), $c3->getCities()->get(0)); - $this->assertEquals($c1->getCities()->get(0)->getId(), $c3->getCities()->get(0)->getId()); - $this->assertEquals($c1->getCities()->get(0)->getName(), $c3->getCities()->get(0)->getName()); + $this->assertNotSame($s1->getCities()->get(0), $s3->getCities()->get(0)); + $this->assertEquals($s1->getCities()->get(0)->getId(), $s3->getCities()->get(0)->getId()); + $this->assertEquals($s1->getCities()->get(0)->getName(), $s3->getCities()->get(0)->getName()); - $this->assertNotSame($c1->getCities()->get(1), $c3->getCities()->get(1)); - $this->assertEquals($c1->getCities()->get(1)->getId(), $c3->getCities()->get(1)->getId()); - $this->assertEquals($c1->getCities()->get(1)->getName(), $c3->getCities()->get(1)->getName()); + $this->assertNotSame($s1->getCities()->get(1), $s3->getCities()->get(1)); + $this->assertEquals($s1->getCities()->get(1)->getId(), $s3->getCities()->get(1)->getId()); + $this->assertEquals($s1->getCities()->get(1)->getName(), $s3->getCities()->get(1)->getName()); - $this->assertNotSame($c2->getCities()->get(0), $c4->getCities()->get(0)); - $this->assertEquals($c2->getCities()->get(0)->getId(), $c4->getCities()->get(0)->getId()); - $this->assertEquals($c2->getCities()->get(0)->getName(), $c4->getCities()->get(0)->getName()); + $this->assertNotSame($s2->getCities()->get(0), $s4->getCities()->get(0)); + $this->assertEquals($s2->getCities()->get(0)->getId(), $s4->getCities()->get(0)->getId()); + $this->assertEquals($s2->getCities()->get(0)->getName(), $s4->getCities()->get(0)->getName()); - $this->assertNotSame($c2->getCities()->get(1), $c4->getCities()->get(1)); - $this->assertEquals($c2->getCities()->get(1)->getId(), $c4->getCities()->get(1)->getId()); - $this->assertEquals($c2->getCities()->get(1)->getName(), $c4->getCities()->get(1)->getName()); + $this->assertNotSame($s2->getCities()->get(1), $s4->getCities()->get(1)); + $this->assertEquals($s2->getCities()->get(1)->getId(), $s4->getCities()->get(1)->getId()); + $this->assertEquals($s2->getCities()->get(1)->getName(), $s4->getCities()->get(1)->getName()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(City::CLASSNAME))); + $this->assertEquals(8, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index 7713f4009fe..3d4729abab1 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -76,7 +76,10 @@ public function testRemoveEntities() $this->loadFixturesCountries(); $this->_em->clear(); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->cache->evictEntityRegion(Country::CLASSNAME); + $this->secondLevelCacheLogger->clearRegionStats($this->getEntityRegion(Country::CLASSNAME)); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); @@ -84,6 +87,9 @@ public function testRemoveEntities() $c1 = $this->_em->find(Country::CLASSNAME, $this->countries[0]->getId()); $c2 = $this->_em->find(Country::CLASSNAME, $this->countries[1]->getId()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $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())); @@ -106,6 +112,9 @@ public function testRemoveEntities() $this->assertNull($this->_em->find(Country::CLASSNAME, $this->countries[0]->getId())); $this->assertNull($this->_em->find(Country::CLASSNAME, $this->countries[1]->getId())); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); } public function testUpdateEntities() @@ -119,6 +128,7 @@ public function testUpdateEntities() $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); $this->cache->evictEntityRegion(State::CLASSNAME); + $this->secondLevelCacheLogger->clearRegionStats($this->getEntityRegion(State::CLASSNAME)); $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); @@ -126,6 +136,10 @@ public function testUpdateEntities() $s1 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); $s2 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); @@ -149,15 +163,18 @@ public function testUpdateEntities() $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); - //$this->assertEquals(8, $this->secondLevelCacheLogger->getPutCount()); - //$this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); - //$this->assertEquals(6, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(6, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); + $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); $queryCount = $this->getCurrentQueryCount(); $c3 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); $c4 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); @@ -171,6 +188,9 @@ public function testUpdateEntities() $this->assertEquals($s2->getId(), $c4->getId()); $this->assertEquals("NEW NAME 2", $c4->getName()); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); } public function testPostFlushFailure() From b8b76d78cc833b6d570017cf4f7cf3ba8b9b6859 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 20 Jun 2013 15:32:10 -0400 Subject: [PATCH 32/71] query cache log --- lib/Doctrine/ORM/AbstractQuery.php | 20 ++++-- lib/Doctrine/ORM/Cache.php | 2 + .../ORM/Cache/DefaultCacheFactory.php | 7 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 23 ++++--- lib/Doctrine/ORM/Cache/QueryCache.php | 18 ++--- lib/Doctrine/ORM/Cache/QueryCacheEntry.php | 9 ++- lib/Doctrine/ORM/Query.php | 66 +++++++++++++------ .../SecondLevelCacheAbstractTest.php | 5 ++ .../SecondLevelCacheQueryCacheTest.php | 33 ++++++++++ 9 files changed, 136 insertions(+), 47 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 07b6b9ea95f..34c0f2d2740 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -306,6 +306,16 @@ public function setResultSetMapping(Query\ResultSetMapping $rsm) return $this; } + /** + * Gets the ResultSetMapping used for hydration. + * + * @return \Doctrine\ORM\Query\ResultSetMapping + */ + public function getResultSetMapping() + { + return $this->_resultSetMapping; + } + /** * Allows to translate entity namespaces to full qualified names. * @@ -742,11 +752,10 @@ public function iterate($parameters = null, $hydrationMode = null) $this->setParameters($parameters); } + $rsm = $this->_resultSetMapping ?: $this->getResultSetMapping(); $stmt = $this->_doExecute(); - return $this->_em->newHydrator($this->_hydrationMode)->iterate( - $stmt, $this->_resultSetMapping, $this->_hints - ); + return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints); } /** @@ -799,9 +808,8 @@ public function execute($parameters = null, $hydrationMode = null) return $stmt; } - $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll( - $stmt, $this->_resultSetMapping, $this->_hints - ); + $rsm = $this->_resultSetMapping ?: $this->getResultSetMapping(); + $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints); $setCacheEntry($data); diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index f1c50189f79..b88989c1d93 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -30,6 +30,8 @@ */ interface Cache { + const DEFAULT_QUERY_REGION_NAME = 'query.cache.region'; + /** * Construct * diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index 425681b3589..a7eda60b54d 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -20,11 +20,12 @@ namespace Doctrine\ORM\Cache; +use Doctrine\ORM\Cache; use Doctrine\ORM\Configuration; -use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\Region\DefaultRegion; +use Doctrine\Common\Cache\Cache as CacheDriver; use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess; use Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy; @@ -44,7 +45,7 @@ class DefaultCacheFactory implements CacheFactory */ private $configuration; - public function __construct(Configuration $configuration, Cache $cache) + public function __construct(Configuration $configuration, CacheDriver $cache) { $this->cache = $cache; $this->configuration = $configuration; @@ -94,7 +95,7 @@ public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fi */ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { - return new DefaultQueryCache($em, $this->createRegion($regionName ?: 'query.cache.region')); + return new DefaultQueryCache($em, $this->createRegion($regionName ?: Cache::DEFAULT_QUERY_REGION_NAME)); } /** diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 6b76d9f242b..acdf57f517b 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -20,10 +20,10 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Query; /** * Default query cache implementation. @@ -34,7 +34,7 @@ class DefaultQueryCache implements QueryCache { /** - * @var \Doctrine\ORM\EntityManager + * @var \Doctrine\ORM\EntityManagerInterface */ private $em; @@ -49,20 +49,26 @@ class DefaultQueryCache implements QueryCache private $region; /** - * @param \Doctrine\ORM\EntityManager $em The entity manager. - * @param \Doctrine\ORM\Cache\Region $region + * @var \Doctrine\ORM\Cache\Logging\CacheLogger */ - public function __construct(EntityManager $em, Region $region) + private $logger; + + /** + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param \Doctrine\ORM\Cache\Region $region The query region. + */ + public function __construct(EntityManagerInterface $em, Region $region) { $this->em = $em; $this->region = $region; $this->uow = $em->getUnitOfWork(); + $this->logger = $em->getConfiguration()->getSecondLevelCacheLogger(); } /** * {@inheritdoc} */ - public function get(QueryCacheKey $key, ResultSetMapping $rsm) + public function get(QueryCacheKey $key, Query $query) { $entry = $this->region->get($key); @@ -71,6 +77,7 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) } $result = array(); + $rsm = $query->getResultSetMapping(); $entityName = reset($rsm->aliasMap); //@TODO find root entity $persister = $this->uow->getEntityPersister($entityName); $region = $persister->getCacheRegionAcess()->getRegion(); @@ -92,7 +99,7 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) /** * {@inheritdoc} */ - public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) + public function put(QueryCacheKey $key, Query $query, array $result) { $data = array(); diff --git a/lib/Doctrine/ORM/Cache/QueryCache.php b/lib/Doctrine/ORM/Cache/QueryCache.php index f90d8dfa607..4f4ed9beeee 100644 --- a/lib/Doctrine/ORM/Cache/QueryCache.php +++ b/lib/Doctrine/ORM/Cache/QueryCache.php @@ -20,7 +20,7 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\ORM\Query; /** * Defines the contract for caches capable of storing query results. @@ -37,19 +37,21 @@ interface QueryCache public function clear(); /** - * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\ResultSetMapping $rsm - * @param array $result + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\Query $query + * @param array $result * * @return boolean */ - public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result); + public function put(QueryCacheKey $key, Query $query, array $result); /** - * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\ResultSetMapping $rsm + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\Query $query + * + * @return void */ - public function get(QueryCacheKey $key, ResultSetMapping $rsm); + public function get(QueryCacheKey $key, Query $rsm); /** * @return \Doctrine\ORM\Cache\Region diff --git a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php index 3cb39667f66..e638eb0e6bb 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php @@ -20,9 +20,6 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\Query\ResultSetMapping; -use Doctrine\ORM\EntityManagerInterface; - /** * Query cache entry * @@ -36,11 +33,17 @@ class QueryCacheEntry implements CacheEntry */ public $result; + /** + * @var integer + */ + public $time; + /** * @param array $result */ public function __construct($result) { $this->result = $result; + $this->time = time(); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 6fe6a500615..66ea1fb933b 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -191,6 +191,11 @@ final class Query extends AbstractQuery */ protected $cacheRegion; + /** + * @var \Doctrine\ORM\Cache\Logging\CacheLogger + */ + protected $cacheLogger; + /** * * Enable/disable second level query (result) caching for this query. @@ -243,14 +248,14 @@ protected function isCacheEnabled() } /** - * Initializes a new Query instance. - * - * @param \Doctrine\ORM\EntityManager $entityManager + * {@inheritdoc} */ - /*public function __construct(EntityManager $entityManager) + public function __construct(EntityManager $em) { - parent::__construct($entityManager); - }*/ + parent::__construct($em); + + $this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheLogger(); + } /** * Gets the SQL query/queries that correspond to this DQL query. @@ -278,6 +283,19 @@ public function getAST() return $parser->getAST(); } + /** + * {@inheritdoc} + */ + public function getResultSetMapping() + { + // parse query or load from cache + if ($this->_resultSetMapping === null) { + $this->_resultSetMapping = $this->_parse()->getResultSetMapping(); + } + + return $this->_resultSetMapping; + } + /** * Parses the DQL query, if necessary, and stores the parser result. * @@ -345,23 +363,32 @@ public function execute($parameters = null, $hydrationMode = null) */ private function executeUsingQueryCache($parameters = null, $hydrationMode = null) { - // parse query or load from cache - if ($this->_resultSetMapping === null) { - $this->_resultSetMapping = $this->_parse()->getResultSetMapping(); - } - - if ($this->_resultSetMapping->isMixed) { - throw new ORMException("Second level cache does not suport mixed results"); + if ($this->getResultSetMapping()->isMixed) { + throw new ORMException("Second level cache does not suport mixed results yet."); } $querykey = new QueryCacheKey($this->_getQueryCacheId()); $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); - $result = $queryCache->get($querykey, $this->_resultSetMapping); + $result = $queryCache->get($querykey, $this); - if ($result === null) { - $result = parent::execute($parameters, $hydrationMode); + if ($result !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey); + } + + return $result; + } + + $result = parent::execute($parameters, $hydrationMode); + $cached = $queryCache->put($querykey, $this, $result); + + if ($this->cacheLogger && $result) { + $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey); + } - $queryCache->put($querykey, $this->_resultSetMapping, $result); + if ($this->cacheLogger && $cached) { + $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey); } return $result; @@ -411,13 +438,14 @@ private function processParameterMappings($paramMappings) foreach ($this->parameters as $parameter) { $key = $parameter->getName(); $value = $parameter->getValue(); + $rsm = $this->_resultSetMapping ?: $this->getResultSetMapping(); if ( ! isset($paramMappings[$key])) { throw QueryException::unknownParameter($key); } - if (isset($this->_resultSetMapping->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) { - $value = $value->getMetadataValue($this->_resultSetMapping->metadataParameterMapping[$key]); + if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) { + $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); } $value = $this->processParameterValue($value); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php index 6a97ae5f376..bd099e98dc3 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -173,6 +173,11 @@ protected function getCollectionRegion($className, $association) return $this->cache->getCollectionCacheRegionAccess($className, $association)->getRegion()->getName(); } + protected function getDefaultQueryRegionName() + { + return $this->cache->getQueryCache()->getRegion()->getName(); + } + protected function evictRegions() { $this->cache->evictQueryRegions(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index c6af1248524..62230144ffe 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -13,6 +13,8 @@ public function testSelectAll() { $this->evictRegions(); $this->loadFixturesCountries(); + + $this->secondLevelCacheLogger->clearStats(); $this->_em->clear(); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); @@ -29,6 +31,10 @@ public function testSelectAll() $this->assertEquals($this->countries[0]->getName(), $result1[0]->getName()); $this->assertEquals($this->countries[1]->getName(), $result1[1]->getName()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); $this->_em->clear(); @@ -39,6 +45,14 @@ public function testSelectAll() $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); $this->assertCount(2, $result2); + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); @@ -49,6 +63,15 @@ public function testSelectAll() $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); } public function testSelectParams() @@ -110,6 +133,11 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($this->countries[0]->getName(), $result1[0]->getName()); $this->assertEquals($this->countries[1]->getName(), $result1[1]->getName()); + $this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->cache->evictEntity(Country::CLASSNAME, $result1[0]->getId()); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $result1[0]->getId())); @@ -122,6 +150,11 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); $this->assertCount(2, $result2); + $this->assertEquals(4, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->assertNotInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); $this->assertNotInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); From b71623ff81b249b16cece54a598abd4221c8f102 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 20 Jun 2013 16:51:50 -0400 Subject: [PATCH 33/71] move query cache API to AbstractQuery --- lib/Doctrine/ORM/AbstractQuery.php | 154 +++++++++++++++++- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 15 +- lib/Doctrine/ORM/Cache/QueryCache.php | 10 +- lib/Doctrine/ORM/Query.php | 136 +--------------- .../SecondLevelCacheQueryCacheTest.php | 75 ++++++++- 5 files changed, 242 insertions(+), 148 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 34c0f2d2740..9ad3d94c3dc 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -23,6 +23,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Types\Type; +use Doctrine\ORM\Cache\QueryCacheKey; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\ORM\Query\QueryException; @@ -120,6 +121,76 @@ abstract class AbstractQuery */ protected $_hydrationCacheProfile; + /** + * Whether to use second level cache, if available. Defaults to TRUE. + * + * @var boolean + */ + protected $cacheable; + + /** + * Second level cache region name. + * + * @var string + */ + protected $cacheRegion; + + /** + * @var \Doctrine\ORM\Cache\Logging\CacheLogger + */ + protected $cacheLogger; + + /** + * + * Enable/disable second level query (result) caching for this query. + * + * @param boolean $cacheable + * @return \Doctrine\ORM\Query + */ + public function setCacheable($cacheable) + { + $this->cacheable = (boolean) $cacheable; + + return $this; + } + + /** + * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise. + */ + public function isCacheable() + { + return $this->cacheable; + } + + /** + * @param string $cacheRegion + * @return \Doctrine\ORM\Query + */ + public function setCacheRegion($cacheRegion) + { + $this->cacheRegion = $cacheRegion; + + return $this; + } + + /** + * Obtain the name of the second level query cache region in which query results will be stored + * + * @return The cache region name; NULL indicates the default region. + */ + public function getCacheRegion() + { + return $this->cacheRegion; + } + + /** + * @return boolean TRUE if the query cache and second level cache are anabled, FALSE otherwise. + */ + protected function isCacheEnabled() + { + return $this->cacheable && $this->_em->getConfiguration()->isSecondLevelCacheEnabled(); + } + /** * Initializes a new instance of a class derived from AbstractQuery. * @@ -127,8 +198,9 @@ abstract class AbstractQuery */ public function __construct(EntityManager $em) { - $this->_em = $em; - $this->parameters = new ArrayCollection(); + $this->_em = $em; + $this->parameters = new ArrayCollection(); + $this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheLogger(); } /** @@ -767,6 +839,23 @@ public function iterate($parameters = null, $hydrationMode = null) * @return mixed */ public function execute($parameters = null, $hydrationMode = null) + { + if ($this->cacheable && $this->isCacheEnabled()) { + return $this->executeUsingQueryCache($parameters, $hydrationMode); + } + + return $this->executeIgnoreQueryCache($parameters, $hydrationMode); + } + + /** + * Execute query ignoring second level cache. + * + * @param ArrayCollection|array|null $parameters + * @param integer|null $hydrationMode + * + * @return mixed + */ + private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null) { if ($hydrationMode !== null) { $this->setHydrationMode($hydrationMode); @@ -816,6 +905,47 @@ public function execute($parameters = null, $hydrationMode = null) return $data; } + /** + * Load from second level cache or executes the query and put into cache. + * + * @param ArrayCollection|array|null $parameters + * @param integer|null $hydrationMode + * + * @return mixed + */ + private function executeUsingQueryCache($parameters = null, $hydrationMode = null) + { + if ($this->getResultSetMapping()->isMixed) { + throw new ORMException("Second level cache does not suport mixed results yet."); + } + + $querykey = new QueryCacheKey($this->getHash()); + $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); + $result = $queryCache->get($querykey, $this); + + if ($result !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey); + } + + return $result; + } + + $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode); + $cached = $queryCache->put($querykey, $this, $result); + + if ($this->cacheLogger) { + $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey); + } + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey); + } + + return $result; + } + /** * 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 @@ -889,4 +1019,24 @@ public function __clone() $this->_hints = array(); } + + /** + * Generates a string of currently query to use for the cache second level cache. + * + * @return string + */ + protected function getHash() + { + $hints = $this->getHints(); + $query = $this->getSQL(); + $params = array(); + + foreach ($this->parameters as $parameter) { + $params[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); + } + + ksort($hints); + + return sha1($query . '-' . serialize($params) . '-' . serialize($hints)); + } } diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index acdf57f517b..d4c054f7778 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -23,7 +23,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Query; +use Doctrine\ORM\AbstractQuery; /** * Default query cache implementation. @@ -68,14 +68,21 @@ public function __construct(EntityManagerInterface $em, Region $region) /** * {@inheritdoc} */ - public function get(QueryCacheKey $key, Query $query) + public function get(QueryCacheKey $key, AbstractQuery $query) { - $entry = $this->region->get($key); + $entry = $this->region->get($key); + $lifetime = $query->getResultCacheLifetime(); if ( ! $entry instanceof QueryCacheEntry) { return null; } + if ($lifetime > 0 && ($entry->time + $lifetime) < time()) { + $this->region->evict($key); + + return null; + } + $result = array(); $rsm = $query->getResultSetMapping(); $entityName = reset($rsm->aliasMap); //@TODO find root entity @@ -99,7 +106,7 @@ public function get(QueryCacheKey $key, Query $query) /** * {@inheritdoc} */ - public function put(QueryCacheKey $key, Query $query, array $result) + public function put(QueryCacheKey $key, AbstractQuery $query, array $result) { $data = array(); diff --git a/lib/Doctrine/ORM/Cache/QueryCache.php b/lib/Doctrine/ORM/Cache/QueryCache.php index 4f4ed9beeee..668b386de55 100644 --- a/lib/Doctrine/ORM/Cache/QueryCache.php +++ b/lib/Doctrine/ORM/Cache/QueryCache.php @@ -20,7 +20,7 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\Query; +use Doctrine\ORM\AbstractQuery; /** * Defines the contract for caches capable of storing query results. @@ -38,20 +38,20 @@ public function clear(); /** * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\Query $query + * @param \Doctrine\ORM\Query\AbstractQuery $query * @param array $result * * @return boolean */ - public function put(QueryCacheKey $key, Query $query, array $result); + public function put(QueryCacheKey $key, AbstractQuery $query, array $result); /** * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\Query $query + * @param \Doctrine\ORM\Query\AbstractQuery $query * * @return void */ - public function get(QueryCacheKey $key, Query $rsm); + public function get(QueryCacheKey $key, AbstractQuery $query); /** * @return \Doctrine\ORM\Cache\Region diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 66ea1fb933b..63bbea89f5c 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -19,14 +19,13 @@ namespace Doctrine\ORM; -use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\LockMode; -use Doctrine\ORM\Cache\QueryCacheKey; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\ParserResult; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Query\ParameterTypeInferer; +use Doctrine\Common\Collections\ArrayCollection; /** * A Query object represents a DQL query. @@ -177,86 +176,6 @@ final class Query extends AbstractQuery */ private $_useQueryCache = true; - /** - * Whether to use second level cache, if available. Defaults to TRUE. - * - * @var boolean - */ - protected $cacheable; - - /** - * Second level cache region name. - * - * @var string - */ - protected $cacheRegion; - - /** - * @var \Doctrine\ORM\Cache\Logging\CacheLogger - */ - protected $cacheLogger; - - /** - * - * Enable/disable second level query (result) caching for this query. - * - * @param boolean $cacheable - * @return \Doctrine\ORM\Query - */ - public function setCacheable($cacheable) - { - $this->cacheable = (boolean) $cacheable; - - return $this; - } - - /** - * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise. - */ - public function isCacheable() - { - return $this->cacheable; - } - - /** - * @param string $cacheRegion - * @return \Doctrine\ORM\Query - */ - public function setCacheRegion($cacheRegion) - { - $this->cacheRegion = $cacheRegion; - - return $this; - } - - /** - * Obtain the name of the second level query cache region in which query results will be stored - * - * @return The cache region name; NULL indicates the default region. - */ - public function getCacheRegion() - { - return $this->cacheRegion; - } - - /** - * @return boolean TRUE if the query cache and second level cache are anabled, FALSE otherwise. - */ - protected function isCacheEnabled() - { - return $this->cacheable && $this->_em->getConfiguration()->isSecondLevelCacheEnabled(); - } - - /** - * {@inheritdoc} - */ - public function __construct(EntityManager $em) - { - parent::__construct($em); - - $this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheLogger(); - } - /** * Gets the SQL query/queries that correspond to this DQL query. * @@ -341,59 +260,6 @@ private function _parse() return $this->_parserResult; } - /** - * {@inheritdoc} - */ - public function execute($parameters = null, $hydrationMode = null) - { - if ($this->cacheable && $this->isCacheEnabled()) { - return $this->executeUsingQueryCache($parameters, $hydrationMode); - } - - return parent::execute($parameters, $hydrationMode); - } - - /** - * Load from second level cache or executes the query and put into cache. - * - * @param ArrayCollection|array|null $parameters - * @param integer|null $hydrationMode - * - * @return mixed - */ - private function executeUsingQueryCache($parameters = null, $hydrationMode = null) - { - if ($this->getResultSetMapping()->isMixed) { - throw new ORMException("Second level cache does not suport mixed results yet."); - } - - $querykey = new QueryCacheKey($this->_getQueryCacheId()); - $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); - $result = $queryCache->get($querykey, $this); - - if ($result !== null) { - - if ($this->cacheLogger) { - $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey); - } - - return $result; - } - - $result = parent::execute($parameters, $hydrationMode); - $cached = $queryCache->put($querykey, $this, $result); - - if ($this->cacheLogger && $result) { - $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey); - } - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey); - } - - return $result; - } - /** * {@inheritdoc} */ diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 62230144ffe..d345a6d3b7a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -3,13 +3,14 @@ namespace Doctrine\Tests\ORM\Functional; use Doctrine\Tests\Models\Cache\Country; +use Doctrine\ORM\Query\ResultSetMapping; /** * @group DDC-2183 */ class SecondLevelCacheQueryCacheTest extends SecondLevelCacheAbstractTest { - public function testSelectAll() + public function testBasicQueryCache() { $this->evictRegions(); $this->loadFixturesCountries(); @@ -74,7 +75,7 @@ public function testSelectAll() $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); } - public function testSelectParams() + public function testBasicQueryParamsParams() { $this->evictRegions(); @@ -168,4 +169,74 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); } + + public function testBasicNativeQueryCache() + { + $this->evictRegions(); + $this->loadFixturesCountries(); + + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $rsm = new ResultSetMapping; + $rsm->addEntityResult(Country::CLASSNAME, 'c'); + $rsm->addFieldResult('c', 'name', 'name'); + $rsm->addFieldResult('c', 'id', 'id'); + + $queryCount = $this->getCurrentQueryCount(); + $sql = 'SELECT id, name FROM cache_country'; + $result1 = $this->_em->createNativeQuery($sql, $rsm)->setCacheable(true)->getResult(); + + $this->assertCount(2, $result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals($this->countries[0]->getId(), $result1[0]->getId()); + $this->assertEquals($this->countries[1]->getId(), $result1[1]->getId()); + $this->assertEquals($this->countries[0]->getName(), $result1[0]->getName()); + $this->assertEquals($this->countries[1]->getName(), $result1[1]->getName()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + + $this->_em->clear(); + + $result2 = $this->_em->createNativeQuery($sql, $rsm) + ->setCacheable(true) + ->getResult(); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertCount(2, $result2); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); + + $this->assertEquals($result1[0]->getId(), $result2[0]->getId()); + $this->assertEquals($result1[1]->getId(), $result2[1]->getId()); + + $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); + $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); + } } \ No newline at end of file From 3b3414504cd1f3f5695ca7e9033f54206543f49e Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 20 Jun 2013 17:11:34 -0400 Subject: [PATCH 34/71] test fetch-join one-to-many --- .../SecondLevelCacheQueryCacheTest.php | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index d345a6d3b7a..b6ac42348b3 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -4,6 +4,8 @@ use Doctrine\Tests\Models\Cache\Country; use Doctrine\ORM\Query\ResultSetMapping; +use Doctrine\Tests\Models\Cache\State; +use Doctrine\Tests\Models\Cache\City; /** * @group DDC-2183 @@ -170,6 +172,78 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); } + public function testBasicQueryFetchJoinsOneToMany() + { + $this->evictRegions(); + + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT s, c FROM Doctrine\Tests\Models\Cache\State s JOIN s.cities c'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertInstanceOf(State::CLASSNAME, $result1[0]); + $this->assertInstanceOf(State::CLASSNAME, $result1[1]); + $this->assertCount(2, $result1[0]->getCities()); + $this->assertCount(2, $result1[1]->getCities()); + + $this->assertInstanceOf(City::CLASSNAME, $result1[0]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result1[0]->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $result1[1]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result1[1]->getCities()->get(1)); + + $this->assertNotNull($result1[0]->getCities()->get(0)->getId()); + $this->assertNotNull($result1[0]->getCities()->get(1)->getId()); + $this->assertNotNull($result1[1]->getCities()->get(0)->getId()); + $this->assertNotNull($result1[1]->getCities()->get(1)->getId()); + + $this->assertNotNull($result1[0]->getCities()->get(0)->getName()); + $this->assertNotNull($result1[0]->getCities()->get(1)->getName()); + $this->assertNotNull($result1[1]->getCities()->get(0)->getName()); + $this->assertNotNull($result1[1]->getCities()->get(1)->getName()); + + $this->_em->clear(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); + $this->assertInstanceOf(State::CLASSNAME, $result2[0]); + $this->assertInstanceOf(State::CLASSNAME, $result2[1]); + $this->assertCount(2, $result2[0]->getCities()); + $this->assertCount(2, $result2[1]->getCities()); + + $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(1)); + + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]->getCities()->get(0)); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]->getCities()->get(1)); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]->getCities()->get(0)); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]->getCities()->get(1)); + + $this->assertNotNull($result2[0]->getCities()->get(0)->getId()); + $this->assertNotNull($result2[0]->getCities()->get(1)->getId()); + $this->assertNotNull($result2[1]->getCities()->get(0)->getId()); + $this->assertNotNull($result2[1]->getCities()->get(1)->getId()); + + $this->assertNotNull($result2[0]->getCities()->get(0)->getName()); + $this->assertNotNull($result2[0]->getCities()->get(1)->getName()); + $this->assertNotNull($result2[1]->getCities()->get(0)->getName()); + $this->assertNotNull($result2[1]->getCities()->get(1)->getName()); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + } + public function testBasicNativeQueryCache() { $this->evictRegions(); From c1636019fc03e25facd3f9fc263f37068d3f03e4 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 20 Jun 2013 17:46:03 -0400 Subject: [PATCH 35/71] put entity loaded from query --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 20 +++++- .../ORM/Persisters/BasicEntityPersister.php | 24 ++++++- .../SecondLevelCacheQueryCacheTest.php | 69 ++++++++++++++++++- 3 files changed, 107 insertions(+), 6 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index d4c054f7778..1db90f42063 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -108,10 +108,24 @@ public function get(QueryCacheKey $key, AbstractQuery $query) */ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) { - $data = array(); + $data = array(); + $rsm = $query->getResultSetMapping(); + $entityName = reset($rsm->aliasMap); //@TODO find root entity + $persister = $this->uow->getEntityPersister($entityName); + $region = $persister->getCacheRegionAcess()->getRegion(); + + foreach ($result as $index => $entity) { + $identifier = $this->uow->getEntityIdentifier($entity); + $data[$index] = $identifier; + + if ($region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) { + continue; + } - foreach ($result as $index => $value) { - $data[$index] = $this->uow->getEntityIdentifier($value); + //cancel put result if entity is not cached + if ( ! $persister->putEntityCache($entity, $entityKey)) { + return; + } //@TODO - handle associations ? } diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index f5427b41c7b..0ec4259cf1e 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -2198,6 +2198,26 @@ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targ return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" } + /** + * @param object $entity + * @param \Doctrine\ORM\Cache\EntityCacheKey $key + * + * @return boolean + */ + public function putEntityCache($entity, EntityCacheKey $key) + { + $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); + $key = $key ?: new EntityCacheKey($class->rootEntityName, $this->em->getUnitOfWork()->getEntityIdentifier($item['entity'])); + $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $entity); + $cached = $this->cacheRegionAccess->put($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } + + return $cached; + } + /** * Execute operations after transaction complete * @@ -2211,7 +2231,7 @@ public function afterTransactionComplete() foreach ($this->queuedCache['insert'] as $item) { $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); - $key = new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); + $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); $cached = $this->cacheRegionAccess->afterInsert($key, $entry); @@ -2225,7 +2245,7 @@ public function afterTransactionComplete() foreach ($this->queuedCache['update'] as $item) { $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); - $key = new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); + $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); $cached = $this->cacheRegionAccess->afterUpdate($key, $entry); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index b6ac42348b3..6157d9ec476 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -77,6 +77,73 @@ public function testBasicQueryCache() $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); } + public function testBasicQueryCachePutEntityCache() + { + $this->evictRegions(); + $this->loadFixturesCountries(); + + $this->evictRegions(); + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $result1 = $this->_em->createQuery($dql)->setCacheable(true)->getResult(); + + $this->assertCount(2, $result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals($this->countries[0]->getId(), $result1[0]->getId()); + $this->assertEquals($this->countries[1]->getId(), $result1[1]->getId()); + $this->assertEquals($this->countries[0]->getName(), $result1[0]->getName()); + $this->assertEquals($this->countries[1]->getName(), $result1[1]->getName()); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); + + $this->_em->clear(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertCount(2, $result2); + + $this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); + $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); + + $this->assertEquals($result1[0]->getId(), $result2[0]->getId()); + $this->assertEquals($result1[1]->getId(), $result2[1]->getId()); + + $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); + $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); + + $this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); + } + public function testBasicQueryParamsParams() { $this->evictRegions(); @@ -153,7 +220,7 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); $this->assertCount(2, $result2); - $this->assertEquals(4, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(5, $this->secondLevelCacheLogger->getPutCount()); $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); From b61becc56974137e10dea8bcc9db4ccb3aeaa6b8 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 20 Jun 2013 18:43:27 -0400 Subject: [PATCH 36/71] handling fetch-joins --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 42 +++++++++- .../SecondLevelCacheQueryCacheTest.php | 78 ++++++++++++++++++- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 1db90f42063..7c9008c980b 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -21,8 +21,11 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\AbstractQuery; /** @@ -111,6 +114,7 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) $data = array(); $rsm = $query->getResultSetMapping(); $entityName = reset($rsm->aliasMap); //@TODO find root entity + $metadata = $this->em->getClassMetadata($entityName); $persister = $this->uow->getEntityPersister($entityName); $region = $persister->getCacheRegionAcess()->getRegion(); @@ -122,12 +126,46 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) continue; } - //cancel put result if entity is not cached + // Cancel put result if entity is not cached if ( ! $persister->putEntityCache($entity, $entityKey)) { return; } - //@TODO - handle associations ? + // @TODO - save relations into query cache + foreach ($rsm->relationMap as $name) { + $assoc = $metadata->associationMappings[$name]; + + if (($assocValue = $metadata->getFieldValue($entity, $name)) === null) { + continue; + } + + $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); + + if ($assoc['type'] & ClassMetadata::TO_ONE) { + + $assocPersister = $this->uow->getEntityPersister($assocMetadata->name); + $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); + + if ($assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { + continue; + } + + // Cancel put result if entity is not cached + if ( ! $assocPersister->putEntityCache($assocValue, $entityKey)) { + return; + } + + continue; + } + + if ($assocValue instanceof PersistentCollection && $assocValue->isInitialized()) { + $assocPersister = $this->uow->getCollectionPersister($assoc); + $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + + continue; + } + } } return $this->region->put($key, new QueryCacheEntry($data)); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 6157d9ec476..48df57118ec 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -241,11 +241,11 @@ public function testLoadFromDatabaseWhenEntityMissing() public function testBasicQueryFetchJoinsOneToMany() { - $this->evictRegions(); - $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); + + $this->evictRegions(); $this->_em->clear(); $queryCount = $this->getCurrentQueryCount(); @@ -288,6 +288,8 @@ public function testBasicQueryFetchJoinsOneToMany() $this->assertCount(2, $result2[0]->getCities()); $this->assertCount(2, $result2[1]->getCities()); + $this->markTestIncomplete(); + $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(0)); $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(1)); $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(0)); @@ -311,6 +313,78 @@ public function testBasicQueryFetchJoinsOneToMany() $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); } + public function testBasicQueryFetchJoinsManyToOne() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + + $this->evictRegions(); + $this->secondLevelCacheLogger->clearStats(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c, s FROM Doctrine\Tests\Models\Cache\City c JOIN c.state s'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(4, $result1); + $this->assertInstanceOf(City::CLASSNAME, $result1[0]); + $this->assertInstanceOf(City::CLASSNAME, $result1[1]); + $this->assertInstanceOf(State::CLASSNAME, $result1[0]->getState()); + $this->assertInstanceOf(State::CLASSNAME, $result1[1]->getState()); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $result1[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $result1[1]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $result1[0]->getState()->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $result1[1]->getState()->getId())); + + $this->assertEquals(7, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(City::CLASSNAME))); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(4, $result1); + $this->assertInstanceOf(City::CLASSNAME, $result2[0]); + $this->assertInstanceOf(City::CLASSNAME, $result2[1]); + $this->assertInstanceOf(State::CLASSNAME, $result2[0]->getState()); + $this->assertInstanceOf(State::CLASSNAME, $result2[1]->getState()); + + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]->getState()); + $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]->getState()); + + $this->assertNotNull($result2[0]->getId()); + $this->assertNotNull($result2[0]->getId()); + $this->assertNotNull($result2[1]->getState()->getId()); + $this->assertNotNull($result2[1]->getState()->getId()); + + $this->assertNotNull($result2[0]->getName()); + $this->assertNotNull($result2[0]->getName()); + $this->assertNotNull($result2[1]->getState()->getName()); + $this->assertNotNull($result2[1]->getState()->getName()); + + $this->assertEquals($result1[0]->getName(), $result2[0]->getName()); + $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); + $this->assertEquals($result1[0]->getState()->getName(), $result2[0]->getState()->getName()); + $this->assertEquals($result1[1]->getState()->getName(), $result2[1]->getState()->getName()); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + } + public function testBasicNativeQueryCache() { $this->evictRegions(); From f5713494efc5f09f0b17fe8b0c836fedced4bfa9 Mon Sep 17 00:00:00 2001 From: fabios Date: Fri, 21 Jun 2013 10:49:25 -0400 Subject: [PATCH 37/71] drop suport for query x-to-many --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 13 +++---------- .../Functional/SecondLevelCacheQueryCacheTest.php | 4 ++++ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 7c9008c980b..feb5c983fe9 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -24,8 +24,6 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\AbstractQuery; /** @@ -126,7 +124,7 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) continue; } - // Cancel put result if entity is not cached + // Cancel put result if entity put fail if ( ! $persister->putEntityCache($entity, $entityKey)) { return; } @@ -151,7 +149,7 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) continue; } - // Cancel put result if entity is not cached + // Cancel put result if entity put fail if ( ! $assocPersister->putEntityCache($assocValue, $entityKey)) { return; } @@ -159,12 +157,7 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) continue; } - if ($assocValue instanceof PersistentCollection && $assocValue->isInitialized()) { - $assocPersister = $this->uow->getCollectionPersister($assoc); - $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); - - continue; - } + throw new \RuntimeException('Second level cache query does not support collections yet.'); } } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 48df57118ec..3e307d45237 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -239,6 +239,10 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); } + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Second level cache query does not support collections yet. + */ public function testBasicQueryFetchJoinsOneToMany() { $this->loadFixturesCountries(); From 29caac4d966bad5131e6b141ddf500e117c00f8d Mon Sep 17 00:00:00 2001 From: fabios Date: Fri, 21 Jun 2013 15:45:31 -0400 Subject: [PATCH 38/71] Transaction region access strategy --- .../Access/ConcurrentRegionAccessStrategy.php | 193 ++++++++++++++++++ lib/Doctrine/ORM/Cache/ConcurrentRegion.php | 33 +-- lib/Doctrine/ORM/Cache/Lock.php | 29 ++- lib/Doctrine/ORM/Cache/Region.php | 5 +- .../ORM/Cache/Region/DefaultRegion.php | 3 +- lib/Doctrine/ORM/Cache/RegionAccess.php | 2 +- .../ORM/Persisters/BasicEntityPersister.php | 4 +- tests/Doctrine/Tests/Mocks/CacheEntryMock.php | 10 + tests/Doctrine/Tests/Mocks/CacheKeyMock.php | 19 ++ .../Tests/Mocks/ConcurrentRegionMock.php | 179 ++++++++++++++++ .../ORM/Cache/AbstractRegionAccessTest.php | 57 +++--- .../ORM/Cache/ConcurrentRegionAccessTest.php | 111 ++++++++++ .../Tests/ORM/Cache/DefaultRegionTest.php | 37 +--- .../ORM/Cache/ReadOnlyRegionAccessTest.php | 4 +- 14 files changed, 609 insertions(+), 77 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache/Access/ConcurrentRegionAccessStrategy.php create mode 100644 tests/Doctrine/Tests/Mocks/CacheEntryMock.php create mode 100644 tests/Doctrine/Tests/Mocks/CacheKeyMock.php create mode 100644 tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php diff --git a/lib/Doctrine/ORM/Cache/Access/ConcurrentRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/ConcurrentRegionAccessStrategy.php new file mode 100644 index 00000000000..c2b90bab0ec --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Access/ConcurrentRegionAccessStrategy.php @@ -0,0 +1,193 @@ +. + */ + +namespace Doctrine\ORM\Cache\Access; + +use Doctrine\ORM\Cache\ConcurrentRegionAccess; +use Doctrine\ORM\Cache\ConcurrentRegion; +use Doctrine\ORM\Cache\CacheException; +use Doctrine\ORM\Cache\LockException; +use Doctrine\ORM\Cache\CacheEntry; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\ORM\Cache\Lock; + +/** + * Region access strategies for concurrently managed data. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class ConcurrentRegionAccessStrategy implements ConcurrentRegionAccess +{ + /** + * @var \Doctrine\ORM\Cache\ConcurrentRegion + */ + private $region; + + /** + * @param \Doctrine\ORM\Cache\ConcurrentRegion $region + */ + public function __construct(ConcurrentRegion $region) + { + $this->region = $region; + } + + /** + * {@inheritdoc} + */ + public function getRegion() + { + return $this->region; + } + + /** + * {@inheritdoc} + */ + public function afterInsert(CacheKey $key, CacheEntry $entry) + { + $writeLock = null; + + try { + if ( ! ($writeLock = $this->region->writeLock($key))) { + return false; + } + + if ( ! $this->region->put($key, $entry, $writeLock)) { + return false; + } + + $this->region->writeUnlock($key, $writeLock); + + return true; + + } catch (LockException $exc) { + + if ($writeLock) { + $this->region->writeUnlock($key, $writeLock); + } + + throw new $exc; + } catch (\Exception $exc) { + + if ($writeLock) { + $this->region->writeUnlock($key, $writeLock); + } + + throw new CacheException($exc->getMessage(), $exc->getCode(), $exc); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $readLock = null) + { + $writeLock = null; + + try { + if ( ! ($writeLock = $this->region->writeLock($key, $readLock))) { + return false; + } + + if ( ! $this->region->put($key, $entry, $writeLock)) { + return false; + } + + $this->region->writeUnlock($key, $writeLock); + + return true; + + } catch (LockException $exc) { + + if ($readLock) { + $this->region->readUnlock($key, $writeLock); + } + + if ($writeLock) { + $this->region->writeUnlock($key, $writeLock); + } + + throw new $exc; + } catch (\Exception $exc) { + + if ($readLock) { + $this->region->readUnlock($key, $writeLock); + } + + if ($writeLock) { + $this->region->writeUnlock($key, $writeLock); + } + + throw new CacheException($exc->getMessage(), $exc->getCode(), $exc); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function lockItem(CacheKey $key) + { + return $this->region->readLock($key); + } + + /** + * {@inheritdoc} + */ + public function unlockItem(CacheKey $key, Lock $lock) + { + return $this->region->readUnlock($key, $lock); + } + + /** + * {@inheritdoc} + */ + public function get(CacheKey $key) + { + return $this->region->get($key); + } + + /** + * {@inheritdoc} + */ + public function put(CacheKey $key, CacheEntry $entry) + { + return $this->region->put($key, $entry); + } + + /** + * {@inheritdoc} + */ + public function evict(CacheKey $key) + { + return $this->region->evict($key); + } + + /** + * {@inheritdoc} + */ + public function evictAll() + { + return $this->region->evictAll(); + } +} diff --git a/lib/Doctrine/ORM/Cache/ConcurrentRegion.php b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php index bd5eba9223f..eec5af0052a 100644 --- a/lib/Doctrine/ORM/Cache/ConcurrentRegion.php +++ b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php @@ -20,6 +20,8 @@ namespace Doctrine\ORM\Cache; +use Doctrine\ORM\Cache\Lock; + /** * Defines contract for concurrently managed data region. * @@ -32,41 +34,46 @@ interface ConcurrentRegion extends Region * Attempts to write lock the mapping for the given key. * * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link writeLock} * - * @return string A lock identifier + * @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists. * - * @throws \Doctrine\ORM\Cache\LockException if the lock already exists. + * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. */ - public function writeLock($key); + public function writeLock(CacheKey $key, Lock $lock = null); /** * Attempts to write unlock the mapping for the given key. * * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. - * @param string $lock The lock identifier previously obtained from {@link writeLock} + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link writeLock} + * + * @return void * - * @throws \Doctrine\ORM\Cache\LockException + * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. */ - public function writeUnlock($key, $lock); + public function writeUnlock(CacheKey $key, Lock $lock); /** * Attempts to read lock the mapping for the given key. * * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. * - * @return string A lock identifier. + * @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists. * - * @throws \Doctrine\ORM\Cache\LockException if the lock already exists. + * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. */ - public function readLock($key); + public function readLock(CacheKey $key); /** * Attempts to read unlock the mapping for the given key. * - * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. - * @param string $lock The lock identifier previously obtained from {@link writeLock} + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link readLock} + * + * @return void * - * @throws \Doctrine\ORM\Cache\LockException + * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. */ - public function readUnlock($key, $lock); + public function readUnlock(CacheKey $key, Lock $lock); } diff --git a/lib/Doctrine/ORM/Cache/Lock.php b/lib/Doctrine/ORM/Cache/Lock.php index a45746e9c32..7f8cb144a65 100644 --- a/lib/Doctrine/ORM/Cache/Lock.php +++ b/lib/Doctrine/ORM/Cache/Lock.php @@ -28,6 +28,9 @@ */ class Lock { + const LOCK_READ = 1; + const LOCK_WRITE = 2; + /** * @var string */ @@ -39,13 +42,35 @@ class Lock public $time; /** - * @param string $value + * @var integer + */ + public $type; + + /** + * @param string $value + * @param integer $type * @param integer $time */ - public function __construct($value, $time = null) + public function __construct($value, $type, $time = null) { $this->value = $value; + $this->type = $type; $this->time = $time ? : time(); } + /** + * @return \Doctrine\ORM\Cache\Lock + */ + public static function createLockWrite() + { + return new self(uniqid(time() . self::LOCK_WRITE), self::LOCK_WRITE); + } + + /** + * @return \Doctrine\ORM\Cache\Lock + */ + public static function createLockRead() + { + return new self(uniqid(time() . self::LOCK_READ), self::LOCK_READ); + } } diff --git a/lib/Doctrine/ORM/Cache/Region.php b/lib/Doctrine/ORM/Cache/Region.php index 324912d6066..90c84e41576 100644 --- a/lib/Doctrine/ORM/Cache/Region.php +++ b/lib/Doctrine/ORM/Cache/Region.php @@ -20,6 +20,8 @@ namespace Doctrine\ORM\Cache; +use Doctrine\ORM\Cache\Lock; + /** * Defines a contract for accessing a particular named region. * @@ -60,10 +62,11 @@ public function get(CacheKey $key); * * @param \Doctrine\ORM\Cache\CacheKey $key The key under which to cache the item. * @param \Doctrine\ORM\Cache\CacheEntry $entry The entry to cache. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained. * * @throws \Doctrine\ORM\Cache\CacheException Indicates a problem accessing the region. */ - public function put(CacheKey $key, CacheEntry $entry); + public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null); /** * Remove an item from the cache. diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index eb75129b40d..cfc1606b36b 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Cache\Region; +use Doctrine\ORM\Cache\Lock; use Doctrine\ORM\Cache\Region; use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Cache\CacheKey; @@ -114,7 +115,7 @@ public function get(CacheKey $key) /** * {@inheritdoc} */ - public function put(CacheKey $key, CacheEntry $entry) + public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) { $entriesKey = $this->entriesMapKey(); $entryKey = $this->entryKey($key); diff --git a/lib/Doctrine/ORM/Cache/RegionAccess.php b/lib/Doctrine/ORM/Cache/RegionAccess.php index 6f96f3a9dab..e546134e5b2 100644 --- a/lib/Doctrine/ORM/Cache/RegionAccess.php +++ b/lib/Doctrine/ORM/Cache/RegionAccess.php @@ -21,7 +21,7 @@ namespace Doctrine\ORM\Cache; /** - * Region access strategy + * Interface for region access strategies. * * @since 2.5 * @author Fabio B. Silva diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 0ec4259cf1e..b4e9908ab98 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -457,7 +457,7 @@ public function update($entity) $this->assignDefaultVersionValue($entity, $id); } - if ($this->hasCache) { + if ($this->hasCache && ( ! $this->isConcurrentRegion || $cacheLock !== null)) { $this->queuedCache['update'][] = array( 'entity' => $entity, 'lock' => $cacheLock, @@ -2253,7 +2253,7 @@ public function afterTransactionComplete() $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); } - if ($this->isConcurrentRegion && $item['lock'] !== null) { + if ($item['lock'] !== null) { $this->cacheRegionAccess->unlockItem($key, $item['lock']); } } diff --git a/tests/Doctrine/Tests/Mocks/CacheEntryMock.php b/tests/Doctrine/Tests/Mocks/CacheEntryMock.php new file mode 100644 index 00000000000..702295cdada --- /dev/null +++ b/tests/Doctrine/Tests/Mocks/CacheEntryMock.php @@ -0,0 +1,10 @@ +hash = $hash; + } + + public function hash() + { + return $this->hash; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php new file mode 100644 index 00000000000..95f6fefe69a --- /dev/null +++ b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php @@ -0,0 +1,179 @@ +region = $region; + } + + private function throwException($method) + { + if (isset($this->exceptions[$method]) && ! empty($this->exceptions[$method])) { + $exception = array_shift($this->exceptions[$method]); + + if($exception != null) { + throw $exception; + } + } + } + + public function addException($method, \Exception $e) + { + $this->exceptions[$method][] = $e; + } + + public function contains(CacheKey $key) + { + $this->calls[__FUNCTION__][] = array('key' => $key); + + if (isset($this->locks[$key->hash()])) { + return false; + } + + $this->throwException(__FUNCTION__); + + return $this->region->contains($key); + } + + public function evict(CacheKey $key) + { + $this->calls[__FUNCTION__][] = array('key' => $key); + + $this->throwException(__FUNCTION__); + + return $this->region->evict($key); + } + + public function evictAll() + { + $this->calls[__FUNCTION__][] = array(); + + $this->throwException(__FUNCTION__); + + return $this->region->evictAll(); + } + + public function get(CacheKey $key) + { + $this->calls[__FUNCTION__][] = array('key' => $key); + + $this->throwException(__FUNCTION__); + + if (isset($this->locks[$key->hash()])) { + return null; + } + + return $this->region->get($key); + } + + public function getName() + { + $this->calls[__FUNCTION__][] = array(); + + $this->throwException(__FUNCTION__); + + return $this->region->getName(); + } + + public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) + { + $this->calls[__FUNCTION__][] = array('key' => $key, 'entry' => $entry); + + $this->throwException(__FUNCTION__); + + if (isset($this->locks[$key->hash()])) { + + if ($lock !== null && $this->locks[$key->hash()]->value === $lock->value) { + return $this->region->put($key, $entry); + } + + return false; + } + + return $this->region->put($key, $entry); + } + + public function readLock(CacheKey $key) + { + $this->calls[__FUNCTION__][] = array('key' => $key); + + $this->throwException(__FUNCTION__); + + if (isset($this->locks[$key->hash()])) { + return null; + } + + return $this->locks[$key->hash()] = Lock::createLockRead(); + } + + public function readUnlock(CacheKey $key, Lock $lock) + { + $this->calls[__FUNCTION__][] = array('key' => $key, 'lock' => $lock); + + $this->throwException(__FUNCTION__); + + if ( ! isset($this->locks[$key->hash()])) { + return; + } + + if ($this->locks[$key->hash()]->value !== $lock->value) { + throw LockException::unexpectedLockValue($lock); + } + + unset($this->locks[$key->hash()]); + } + + public function writeLock(CacheKey $key, Lock $lock = null) + { + $this->calls[__FUNCTION__][] = array('key' => $key, 'lock' => $lock); + + $this->throwException(__FUNCTION__); + + if (isset($this->locks[$key->hash()])) { + if ($lock !== null && $this->locks[$key->hash()]->value === $lock->value) { + return $this->locks[$key->hash()] = Lock::createLockWrite(); + } + + return null; + } + + return $this->locks[$key->hash()] = Lock::createLockWrite(); + } + + public function writeUnlock(CacheKey $key, Lock $lock) + { + $this->calls[__FUNCTION__][] = array('key' => $key, 'lock' => $lock); + + $this->throwException(__FUNCTION__); + + if ( ! isset($this->locks[$key->hash()])) { + return; + } + + if ($this->locks[$key->hash()]->value !== $lock->value) { + throw LockException::unexpectedLockValue($lock); + } + + unset($this->locks[$key->hash()]); + } +} diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php index 1f0f7e1ce64..0be3bccef76 100644 --- a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php @@ -5,8 +5,8 @@ use Doctrine\ORM\Cache\Region; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\ArrayCache; -use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\EntityCacheEntry; +use Doctrine\Tests\Mocks\CacheKeyMock; +use Doctrine\Tests\Mocks\CacheEntryMock; use Doctrine\ORM\Cache\Region\DefaultRegion; require_once __DIR__ . '/../../TestInit.php'; @@ -72,18 +72,16 @@ public function testGetRegion() static public function dataProviderCacheValues() { - $entityName = '\Doctrine\Tests\Models\Cache\Country'; - return array( - array(new EntityCacheKey($entityName, array('id'=>1)), new EntityCacheEntry($entityName, array('id'=>1, 'name' => 'bar'))), - array(new EntityCacheKey($entityName, array('id'=>2)), new EntityCacheEntry($entityName, array('id'=>2, 'name' => 'foo'))), + array(new CacheKeyMock('key.1'), new CacheEntryMock(array('id'=>1, 'name' => 'bar'))), + array(new CacheKeyMock('key.2'), new CacheEntryMock(array('id'=>2, 'name' => 'foo'))), ); } /** * @dataProvider dataProviderCacheValues */ - public function testPutGetAndEvict(EntityCacheKey $key, EntityCacheEntry $entry) + public function testPutGetAndEvict($key, $entry) { $this->assertNull($this->regionAccess->get($key)); @@ -98,18 +96,20 @@ public function testPutGetAndEvict(EntityCacheKey $key, EntityCacheEntry $entry) public function testEvictAll() { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - $class = '\Doctrine\Tests\Models\Cache\Country'; + $key1 = new CacheKeyMock('key.1'); + $key2 = new CacheKeyMock('key.2'); $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->put($key1, new EntityCacheEntry($class, array('value' => 'foo'))); - $this->regionAccess->put($key2, new EntityCacheEntry($class, array('value' => 'bar'))); + $this->regionAccess->put($key1, new CacheEntryMock(array('value' => 'foo'))); + $this->regionAccess->put($key2, new CacheEntryMock(array('value' => 'bar'))); + + $this->assertNotNull($this->regionAccess->get($key1)); + $this->assertNotNull($this->regionAccess->get($key2)); - $this->assertEquals(new EntityCacheEntry($class, array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new EntityCacheEntry($class, array('value' => 'bar')), $this->regionAccess->get($key2)); + $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); $this->regionAccess->evictAll(); @@ -119,33 +119,34 @@ public function testEvictAll() public function testAfterInsert() { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - $class = '\Doctrine\Tests\Models\Cache\Country'; + $key1 = new CacheKeyMock('key.1'); + $key2 = new CacheKeyMock('key.2'); $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->afterInsert($key1, new EntityCacheEntry($class, array('value' => 'foo'))); - $this->regionAccess->afterInsert($key2, new EntityCacheEntry($class, array('value' => 'bar'))); + $this->regionAccess->afterInsert($key1, new CacheEntryMock(array('value' => 'foo'))); + $this->regionAccess->afterInsert($key2, new CacheEntryMock(array('value' => 'bar'))); - $this->assertEquals(new EntityCacheEntry($class, array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new EntityCacheEntry($class, array('value' => 'bar')), $this->regionAccess->get($key2)); + $this->assertNotNull($this->regionAccess->get($key1)); + $this->assertNotNull($this->regionAccess->get($key2)); + + $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); } public function testAfterUpdate() { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); - $class = '\Doctrine\Tests\Models\Cache\Country'; + $key1 = new CacheKeyMock('key.1'); + $key2 = new CacheKeyMock('key.2'); $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); - $this->regionAccess->afterUpdate($key1, new EntityCacheEntry($class, array('value' => 'foo'))); - $this->regionAccess->afterUpdate($key2, new EntityCacheEntry($class, array('value' => 'bar'))); + $this->regionAccess->afterUpdate($key1, new CacheEntryMock(array('value' => 'foo'))); + $this->regionAccess->afterUpdate($key2, new CacheEntryMock(array('value' => 'bar'))); - $this->assertEquals(new EntityCacheEntry($class, array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new EntityCacheEntry($class, array('value' => 'bar')), $this->regionAccess->get($key2)); + $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); } } diff --git a/tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php new file mode 100644 index 00000000000..20c5ae10c46 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php @@ -0,0 +1,111 @@ + 'foo')); + $entry2 = new CacheEntryMock(array('value' => 'bar')); + + $this->regionAccess->put($key1, $entry1); + $this->regionAccess->put($key2, $entry2); + + $this->assertNotNull($this->regionAccess->get($key1)); + $this->assertNotNull($this->regionAccess->get($key2)); + + $lock1 = $this->regionAccess->lockItem($key1); + $lock2 = $this->regionAccess->lockItem($key1); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock1); + $this->assertNull($lock2); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNotNull($this->regionAccess->get($key2)); + + $this->regionAccess->unlockItem($key1, $lock1); + + $this->assertNotNull($this->regionAccess->get($key1)); + $this->assertNotNull($this->regionAccess->get($key2)); + + $this->assertEquals($entry1, $this->regionAccess->get($key1)); + $this->assertEquals($entry2, $this->regionAccess->get($key2)); + } + + public function testLockWriteOnUpdate() + { + $key = new CacheKeyMock('key.1'); + $entry = new CacheEntryMock(array('value' => 'foo')); + $entry1 = new CacheEntryMock(array('value' => 'foo')); + $region = $this->regionAccess->getRegion(); + + $this->assertTrue($this->regionAccess->afterInsert($key, $entry)); + $this->assertEquals($entry, $this->regionAccess->get($key)); + + $lock = $this->regionAccess->lockItem($key); + $this->assertNull($this->regionAccess->get($key)); + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock); + + $region->locks[$key->hash()] = Lock::createLockRead(); //Somehow another proc get the lock + + $this->assertFalse($this->regionAccess->afterUpdate($key, $entry1, $lock)); + } + + public function testExceptionShouldUnlockItem() + { + $key = new CacheKeyMock('key.1'); + $entry = new CacheEntryMock(array('value' => 'foo')); + $region = $this->regionAccess->getRegion(); + + $this->assertTrue($this->regionAccess->afterInsert($key, $entry)); + $this->assertEquals($entry, $this->regionAccess->get($key)); + + $lock = $this->regionAccess->lockItem($key); + $this->assertNull($this->regionAccess->get($key)); + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock); + + $region->addException('put', new \RuntimeException('Some concurrency exception')); + + try { + $this->regionAccess->afterUpdate($key, $entry, $lock); + + $this->fail('Expected Exception'); + + } catch (\Doctrine\ORM\Cache\CacheException $exc) { + $this->assertEquals('Some concurrency exception', $exc->getMessage()); + } + + $this->assertNotNull($this->regionAccess->get($key)); + $this->assertEquals($entry, $this->regionAccess->get($key)); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php index 27fe31a813a..aa218023e14 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -2,11 +2,11 @@ namespace Doctrine\Tests\ORM\Cache; -use Doctrine\Tests\OrmFunctionalTestCase; use Doctrine\ORM\Cache\Region\DefaultRegion; +use Doctrine\Tests\OrmFunctionalTestCase; +use Doctrine\Tests\Mocks\CacheEntryMock; +use Doctrine\Tests\Mocks\CacheKeyMock; use Doctrine\Common\Cache\ArrayCache; -use Doctrine\ORM\Cache\CacheEntry; -use Doctrine\ORM\Cache\CacheKey; /** * @group DDC-2183 @@ -40,8 +40,8 @@ public function testGetters() static public function dataProviderCacheValues() { return array( - array(new DefaultRegionTestKey('key.1'), new DefaultRegionTestEntry(array('id'=>1, 'name' => 'bar'))), - array(new DefaultRegionTestKey('key.2'), new DefaultRegionTestEntry(array('id'=>2, 'name' => 'foo'))), + array(new CacheKeyMock('key.1'), new CacheEntryMock(array('id'=>1, 'name' => 'bar'))), + array(new CacheKeyMock('key.2'), new CacheEntryMock(array('id'=>2, 'name' => 'foo'))), ); } @@ -67,14 +67,14 @@ public function testPutGetContainsEvict($key, $value) public function testEvictAll() { - $key1 = new DefaultRegionTestKey('key.1'); - $key2 = new DefaultRegionTestKey('key.2'); + $key1 = new CacheKeyMock('key.1'); + $key2 = new CacheKeyMock('key.2'); $this->assertFalse($this->region->contains($key1)); $this->assertFalse($this->region->contains($key2)); - $this->region->put($key1, new DefaultRegionTestEntry(array('value' => 'foo'))); - $this->region->put($key2, new DefaultRegionTestEntry(array('value' => 'bar'))); + $this->region->put($key1, new CacheEntryMock(array('value' => 'foo'))); + $this->region->put($key2, new CacheEntryMock(array('value' => 'bar'))); $this->assertTrue($this->region->contains($key1)); $this->assertTrue($this->region->contains($key2)); @@ -84,23 +84,4 @@ public function testEvictAll() $this->assertFalse($this->region->contains($key1)); $this->assertFalse($this->region->contains($key2)); } -} - -class DefaultRegionTestKey implements CacheKey -{ - - function __construct($hash) - { - $this->hash = $hash; - } - - public function hash() - { - return $this->hash; - } -} - -class DefaultRegionTestEntry extends \ArrayObject implements CacheEntry -{ - } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php index d9556557257..0a4d941077c 100644 --- a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyRegionAccessTest.php @@ -3,6 +3,8 @@ namespace Doctrine\Tests\ORM\Cache; use Doctrine\ORM\Cache\Region; +use Doctrine\Tests\Mocks\CacheKeyMock; +use Doctrine\Tests\Mocks\CacheEntryMock; use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess; /** @@ -21,6 +23,6 @@ protected function createRegionAccess(Region $region) */ public function testAfterUpdate() { - $this->regionAccess->afterUpdate(new DefaultRegionTestKey('key'), new DefaultRegionTestEntry(array('value' => 'foo'))); + $this->regionAccess->afterUpdate(new CacheKeyMock('key'), new CacheEntryMock(array('value' => 'foo'))); } } From 3348d6c4b84505f781aad628076311b748532697 Mon Sep 17 00:00:00 2001 From: fabios Date: Fri, 21 Jun 2013 17:46:19 -0400 Subject: [PATCH 39/71] test basic concurrent --- .../ORM/Cache/DefaultCacheFactory.php | 2 +- .../Tests/Mocks/ConcurrentRegionMock.php | 5 + .../ORM/Cache/ConcurrentRegionAccessTest.php | 3 +- .../SecondLevelCacheConcurrentTest.php | 166 ++++++++++++++++++ 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index a7eda60b54d..f3686fa0cdc 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -118,7 +118,7 @@ public function buildEntityEntryStructure(EntityManagerInterface $em) * @param string $regionName * @return \Doctrine\ORM\Cache\Region\DefaultRegion */ - public function createRegion($regionName) + private function createRegion($regionName) { return new DefaultRegion($regionName, $this->cache, array( 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($regionName) diff --git a/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php index 95f6fefe69a..451c5947d38 100644 --- a/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php +++ b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php @@ -42,6 +42,11 @@ public function addException($method, \Exception $e) $this->exceptions[$method][] = $e; } + public function setLock(CacheKey $key, Lock $lock) + { + $this->locks[$key->hash()] = $lock; + } + public function contains(CacheKey $key) { $this->calls[__FUNCTION__][] = array('key' => $key); diff --git a/tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php index 20c5ae10c46..6b8cdf43cb2 100644 --- a/tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/ConcurrentRegionAccessTest.php @@ -76,7 +76,8 @@ public function testLockWriteOnUpdate() $this->assertNull($this->regionAccess->get($key)); $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock); - $region->locks[$key->hash()] = Lock::createLockRead(); //Somehow another proc get the lock + //Somehow another proc get the lock + $region->setLock($key, Lock::createLockRead()); $this->assertFalse($this->regionAccess->afterUpdate($key, $entry1, $lock)); } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php new file mode 100644 index 00000000000..d8e062dfc1f --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php @@ -0,0 +1,166 @@ +enableSecondLevelCache(); + parent::setUp(); + + $this->cacheFactory = new CacheFactorySecondLevelCacheConcurrentTest(self::getSharedSecondLevelCacheDriverImpl()); + + $this->_em->getConfiguration()->setSecondLevelCacheFactory($this->cacheFactory); + } + + public function testBasicConcurrentReadLock() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $countryId = $this->countries[0]->getId(); + $cacheId = new EntityCacheKey(Country::CLASSNAME, array('id'=>$countryId)); + $region = $this->_em->getCache()->getEntityCacheRegionAccess(Country::CLASSNAME)->getRegion(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + + /** @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ + $region->setLock($cacheId, Lock::createLockRead()); // another proc lock the entity cache + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + + $queryCount = $this->getCurrentQueryCount(); + $country = $this->_em->find(Country::CLASSNAME, $countryId); + + $this->assertInstanceOf(Country::CLASSNAME, $country); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + } + + public function testBasicConcurrentWriteLock() + { + $this->loadFixturesCountries(); + $this->_em->clear(); + + $lock = Lock::createLockWrite(); + $countryId = $this->countries[0]->getId(); + $cacheKey = new EntityCacheKey(Country::CLASSNAME, array('id'=>$countryId)); + $region = $this->cache->getEntityCacheRegionAccess(Country::CLASSNAME)->getRegion(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + + /** @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ + $region->setLock($cacheKey, $lock); // another proc lock the entity cache + + $queryCount = $this->getCurrentQueryCount(); + $country = $this->_em->find(Country::CLASSNAME, $countryId); // Cache locked, goes straight to the database + + $this->assertInstanceOf(Country::CLASSNAME, $country); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + + $country->setName('Foo 1'); + $this->_em->persist($country); + $this->_em->flush(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $country = $this->_em->find(Country::CLASSNAME, $countryId); // Cache locked, goes straight to the database + + $this->assertInstanceOf(Country::CLASSNAME, $country); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals('Foo 1', $country->getName()); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + + /** @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ + $region->writeUnlock($cacheKey, $lock); // another proc unlock + $region->evict($cacheKey); // and clear the cache. + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $country = $this->_em->find(Country::CLASSNAME, $countryId); // No cache + + $this->assertInstanceOf(Country::CLASSNAME, $country); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals('Foo 1', $country->getName()); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + } +} + +class CacheFactorySecondLevelCacheConcurrentTest implements CacheFactory +{ + public function __construct(Cache $cache) + { + $this->cache = $cache; + } + + public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) + { + $regionName = $metadata->cache['region']; + $region = $this->createRegion($regionName); + $access = new ConcurrentRegionAccessStrategy($region); + + return $access; + } + + public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) + { + $mapping = $metadata->getAssociationMapping($fieldName); + $regionName = $mapping['cache']['region']; + $region = $this->createRegion($regionName); + $access = new ConcurrentRegionAccessStrategy($region); + + return $access; + } + + public function buildQueryCache(EntityManagerInterface $em, $regionName = null) + { + return new DefaultQueryCache($em, $this->createRegion($regionName ?: Cache::DEFAULT_QUERY_REGION_NAME)); + } + + public function buildCollectionEntryStructure(EntityManagerInterface $em) + { + return new DefaultCollectionEntryStructure($em); + } + + public function buildEntityEntryStructure(EntityManagerInterface $em) + { + return new DefaultEntityEntryStructure($em); + } + + private function createRegion($regionName) + { + $region = new DefaultRegion($regionName, $this->cache); + $mock = new ConcurrentRegionMock($region); + + return $mock; + } +} \ No newline at end of file From 77d8b7a5a2d01282f5162a8af4a1477fa893ff5b Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 24 Jun 2013 13:36:42 -0400 Subject: [PATCH 40/71] suport cached collection in a query cache --- lib/Doctrine/ORM/Cache/CacheKey.php | 1 - .../ORM/Cache/DefaultEntityEntryStructure.php | 2 + lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 127 +++++++++++++++--- lib/Doctrine/ORM/Query.php | 5 + lib/Doctrine/ORM/UnitOfWork.php | 5 + .../SecondLevelCacheQueryCacheTest.php | 6 - 6 files changed, 122 insertions(+), 24 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/CacheKey.php b/lib/Doctrine/ORM/Cache/CacheKey.php index e163ccbccdf..e613efe60d8 100644 --- a/lib/Doctrine/ORM/Cache/CacheKey.php +++ b/lib/Doctrine/ORM/Cache/CacheKey.php @@ -29,7 +29,6 @@ */ interface CacheKey { - /** * @return string Unique identifier */ diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php index 28ce62e2387..f95ee37315d 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -84,6 +84,8 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e */ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null) { + $hints[Query::HINT_CACHE_ENABLED] = true; + if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index feb5c983fe9..c29bb5d5148 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -20,10 +20,13 @@ namespace Doctrine\ORM\Cache; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\AbstractQuery; /** @@ -68,6 +71,8 @@ public function __construct(EntityManagerInterface $em, Region $region) /** * {@inheritdoc} + * + * @TODO - does not work recursively yet */ public function get(QueryCacheKey $key, AbstractQuery $query) { @@ -87,18 +92,61 @@ public function get(QueryCacheKey $key, AbstractQuery $query) $result = array(); $rsm = $query->getResultSetMapping(); $entityName = reset($rsm->aliasMap); //@TODO find root entity + $metadata = $this->em->getClassMetadata($entityName); $persister = $this->uow->getEntityPersister($entityName); $region = $persister->getCacheRegionAcess()->getRegion(); - - foreach ($entry->result as $index => $value) { - if ( ! $region->contains(new EntityCacheKey($entityName, $value))) { + // @TODO - move to cache hydration componente + foreach ($entry->result as $index => $entry) { + + if ( ! $region->contains(new EntityCacheKey($entityName, $entry['identifier']))) { return null; } - $result[$index] = $this->em->getReference($entityName, $value); + $entity = $this->em->getReference($entityName, $entry['identifier']); + $result[$index] = $entity; + + foreach ($entry['associations'] as $name => $assoc) { + + $assocPersister = $this->uow->getEntityPersister($assoc['rootEntityName']); + $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + + if ($assoc['type'] & ClassMetadata::TO_ONE) { + + if ( ! $assocRegion->contains(new EntityCacheKey($assoc['rootEntityName'], $assoc['identifier']))) { + return null; + } + + $metadata->setFieldValue($entity, $name, $this->em->getReference($assoc['entityName'], $assoc['identifier'])); + + continue; + } + + if ( ! isset($assoc['list']) || empty($assoc['list'])) { + continue; + } + + $oid = spl_object_hash($entity); + $targetClass = $this->em->getClassMetadata($assoc['rootEntityName']); + $relation = $metadata->associationMappings[$name]; + $collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); + + foreach ($assoc['list'] as $assocIndex => $assocItem) { + + if ( ! $assocRegion->contains(new EntityCacheKey($assoc['rootEntityName'], $assocItem['identifier']))) { + return null; + } + + $element = $this->em->getReference($assocItem['entityName'], $assocItem['identifier']); - //@TODO - handle associations ? + $collection->hydrateSet($assocIndex, $element); + } + + $collection->setInitialized(true); + $collection->setOwner($entity, $relation); + $metadata->setFieldValue($entity, $name, $collection); + $this->uow->setOriginalEntityProperty($oid, $name, $collection); + } } return $result; @@ -106,19 +154,23 @@ public function get(QueryCacheKey $key, AbstractQuery $query) /** * {@inheritdoc} + * + * @TODO - does not work recursively yet */ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) { $data = array(); $rsm = $query->getResultSetMapping(); $entityName = reset($rsm->aliasMap); //@TODO find root entity + $hasRelation = ( ! empty($rsm->relationMap)); $metadata = $this->em->getClassMetadata($entityName); $persister = $this->uow->getEntityPersister($entityName); $region = $persister->getCacheRegionAcess()->getRegion(); foreach ($result as $index => $entity) { - $identifier = $this->uow->getEntityIdentifier($entity); - $data[$index] = $identifier; + $identifier = $this->uow->getEntityIdentifier($entity); + $data[$index]['identifier'] = $identifier; + $data[$index]['associations'] = array(); if ($region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) { continue; @@ -129,7 +181,11 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) return; } - // @TODO - save relations into query cache + if ( ! $hasRelation) { + continue; + } + + // @TODO - move to cache hydration componente foreach ($rsm->relationMap as $name) { $assoc = $metadata->associationMappings[$name]; @@ -137,27 +193,64 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) continue; } - $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); + $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); + $assocPersister = $this->uow->getEntityPersister($assocMetadata->rootEntityName); + $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + // Handle *-to-one associations if ($assoc['type'] & ClassMetadata::TO_ONE) { - $assocPersister = $this->uow->getEntityPersister($assocMetadata->name); + $assocPersister = $this->uow->getEntityPersister($assocMetadata->rootEntityName); $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); - if ($assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { - continue; - } + if ( ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { - // Cancel put result if entity put fail - if ( ! $assocPersister->putEntityCache($assocValue, $entityKey)) { - return; + // Cancel put result if entity put fail + if ( ! $assocPersister->putEntityCache($assocValue, $entityKey)) { + return; + } } + $data[$index]['associations'][$name] = array( + 'rootEntityName'=> $assocMetadata->rootEntityName, + 'entityName' => $assocMetadata->name, + 'identifier' => $assocIdentifier, + 'type' => $assoc['type'] + ); + + continue; + } + + // Handle *-to-many associations + if (is_array($assocValue) && ! $assocValue instanceof Collection) { continue; } - throw new \RuntimeException('Second level cache query does not support collections yet.'); + $list = array(); + + foreach ($assocValue as $assocItemIndex => $assocItem) { + $assocIdentifier = $this->uow->getEntityIdentifier($assocItem); + + if ( ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { + + // Cancel put result if entity put fail + if ( ! $assocPersister->putEntityCache($assocItem, $entityKey)) { + return; + } + } + + $list[$assocItemIndex] = array( + 'entityName' => $assocMetadata->name, + 'identifier' => $assocIdentifier, + ); + } + + $data[$index]['associations'][$name] = array( + 'rootEntityName'=> $assocMetadata->rootEntityName, + 'type' => $assoc['type'], + 'list' => $list, + ); } } diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index 63bbea89f5c..ca39503069f 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -59,6 +59,11 @@ final class Query extends AbstractQuery */ const HINT_REFRESH = 'doctrine.refresh'; + /** + * @var string + */ + const HINT_CACHE_ENABLED = 'doctrine.cache.enabled'; + /** * Internal hint: is set to the proxy entity that is currently triggered for loading * diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 4a7bccf6524..24bbb23cbf8 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2757,6 +2757,11 @@ public function createEntity($className, array $data, &$hints = array()) break; default: + // Ignore if its a cached collection + if (isset($hints[Query::HINT_CACHE_ENABLED]) && $class->getFieldValue($entity, $field) instanceof PersistentCollection) { + break; + } + // Inject collection $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection); $pColl->setOwner($entity, $assoc); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 3e307d45237..6c1e263dbaa 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -239,10 +239,6 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals($queryCount + 2 , $this->getCurrentQueryCount()); } - /** - * @expectedException \RuntimeException - * @expectedExceptionMessage Second level cache query does not support collections yet. - */ public function testBasicQueryFetchJoinsOneToMany() { $this->loadFixturesCountries(); @@ -292,8 +288,6 @@ public function testBasicQueryFetchJoinsOneToMany() $this->assertCount(2, $result2[0]->getCities()); $this->assertCount(2, $result2[1]->getCities()); - $this->markTestIncomplete(); - $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(0)); $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(1)); $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(0)); From 6209a51e08c04ff3c1d7e48f04387f135fd7038c Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 24 Jun 2013 17:25:40 -0400 Subject: [PATCH 41/71] cache query limit --- lib/Doctrine/ORM/Query.php | 8 ++++ .../SecondLevelCacheQueryCacheTest.php | 43 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/lib/Doctrine/ORM/Query.php b/lib/Doctrine/ORM/Query.php index ca39503069f..de8411df0de 100644 --- a/lib/Doctrine/ORM/Query.php +++ b/lib/Doctrine/ORM/Query.php @@ -662,6 +662,14 @@ protected function _getQueryCacheId() ); } + /** + * {@inheritdoc} + */ + protected function getHash() + { + return sha1(parent::getHash(). '-'. $this->_firstResult . '-' . $this->_maxResults); + } + /** * Cleanup Query resource when clone is called. * diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 6c1e263dbaa..475db6d70ad 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -452,4 +452,47 @@ public function testBasicNativeQueryCache() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); } + + public function testQueryDependsOnFirstAndMaxResultResult() + { + $this->evictRegions(); + $this->loadFixturesCountries(); + + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->setFirstResult(1) + ->setMaxResults(1) + ->getResult(); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->_em->clear(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->setFirstResult(2) + ->setMaxResults(1) + ->getResult(); + + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + + $this->_em->clear(); + + $result3 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertEquals($queryCount + 3, $this->getCurrentQueryCount()); + $this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(3, $this->secondLevelCacheLogger->getMissCount()); + } } \ No newline at end of file From 81d2e90eaa16897e1bc833aae2eb3aa04ced370e Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 24 Jun 2013 18:06:37 -0400 Subject: [PATCH 42/71] test query life-time and query region --- lib/Doctrine/ORM/AbstractQuery.php | 25 ++++ lib/Doctrine/ORM/Cache/DefaultCache.php | 2 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 2 +- .../SecondLevelCacheQueryCacheTest.php | 120 ++++++++++++++++++ 4 files changed, 147 insertions(+), 2 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 9ad3d94c3dc..fc1d91c6919 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -140,6 +140,11 @@ abstract class AbstractQuery */ protected $cacheLogger; + /** + * @var integer + */ + protected $lifetime = 0; + /** * * Enable/disable second level query (result) caching for this query. @@ -191,6 +196,26 @@ protected function isCacheEnabled() return $this->cacheable && $this->_em->getConfiguration()->isSecondLevelCacheEnabled(); } + /** + * @return integer + */ + public function getLifetime() + { + return $this->lifetime; + } + + /** + * Sets the life-time for this query into second level cache. + * + * @param integer $lifetime + */ + public function setLifetime($lifetime) + { + $this->lifetime = $lifetime; + + return $this; + } + /** * Initializes a new instance of a class derived from AbstractQuery. * diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 170268472a4..1252c8b28af 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -284,7 +284,7 @@ public function getQueryCache($regionName = null) $this->defaultQueryCache = $this->cacheFactory->buildQueryCache($this->em); } - if (isset($this->queryCaches[$regionName])) { + if ( ! isset($this->queryCaches[$regionName])) { $this->queryCaches[$regionName] = $this->cacheFactory->buildQueryCache($this->em, $regionName); } diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index c29bb5d5148..d6becbead19 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -77,7 +77,7 @@ public function __construct(EntityManagerInterface $em, Region $region) public function get(QueryCacheKey $key, AbstractQuery $query) { $entry = $this->region->get($key); - $lifetime = $query->getResultCacheLifetime(); + $lifetime = $query->getLifetime(); if ( ! $entry instanceof QueryCacheEntry) { return null; diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 475db6d70ad..96545b0928b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -6,6 +6,7 @@ use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\City; +use Doctrine\ORM\Cache\QueryCacheKey; /** * @group DDC-2183 @@ -495,4 +496,123 @@ public function testQueryDependsOnFirstAndMaxResultResult() $this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount()); $this->assertEquals(3, $this->secondLevelCacheLogger->getMissCount()); } + + public function testQueryCacheLifetime() + { + $this->evictRegions(); + $this->loadFixturesCountries(); + + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $getHash = function(\Doctrine\ORM\AbstractQuery $query){ + $method = new \ReflectionMethod($query, 'getHash'); + $method->setAccessible(true); + + return $method->invoke($query); + }; + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $query = $this->_em->createQuery($dql); + $result1 = $query->setCacheable(true) + ->setLifetime(3600) + ->getResult(); + + $this->assertNotEmpty($result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->_em->clear(); + + $key = new QueryCacheKey($getHash($query)); + $entry = $this->cache->getQueryCache() + ->getRegion() + ->get($key); + + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheEntry', $entry); + $entry->time = $entry->time / 2; + + $this->cache->getQueryCache() + ->getRegion() + ->put($key, $entry); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->setLifetime(3600) + ->getResult(); + + $this->assertNotEmpty($result2); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + } + + public function testQueryCacheRegion() + { + $this->evictRegions(); + $this->loadFixturesCountries(); + + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $query = $this->_em->createQuery($dql); + + $query1 = clone $query; + $result1 = $query1->setCacheable(true) + ->setCacheRegion('foo_region') + ->getResult(); + + $this->assertNotEmpty($result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertEquals(0, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount('foo_region')); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount('foo_region')); + + $query2 = clone $query; + $result2 = $query2->setCacheable(true) + ->setCacheRegion('bar_region') + ->getResult(); + + $this->assertNotEmpty($result2); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + $this->assertEquals(0, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount('bar_region')); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount('bar_region')); + + $query3 = clone $query; + $result3 = $query3->setCacheable(true) + ->setCacheRegion('foo_region') + ->getResult(); + + $this->assertNotEmpty($result3); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount('foo_region')); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount('foo_region')); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount('foo_region')); + + $query4 = clone $query; + $result4 = $query4->setCacheable(true) + ->setCacheRegion('bar_region') + ->getResult(); + + $this->assertNotEmpty($result3); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount('bar_region')); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount('bar_region')); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount('bar_region')); + } } \ No newline at end of file From 9ff0e21cd74cc74ccdb58d2b1b86027670eab347 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 25 Jun 2013 13:57:07 -0400 Subject: [PATCH 43/71] check if cache is enabled into uow --- lib/Doctrine/ORM/AbstractQuery.php | 4 +++- .../ORM/Cache/DefaultEntityEntryStructure.php | 4 +++- lib/Doctrine/ORM/UnitOfWork.php | 19 +++++++++++++------ .../SecondLevelCacheConcurrentTest.php | 1 - 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index fc1d91c6919..b7d97cd4a67 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -1057,7 +1057,9 @@ protected function getHash() $params = array(); foreach ($this->parameters as $parameter) { - $params[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); + $value = $parameter->getValue(); + + $params[$parameter->getName()] = is_scalar($value) ? $value : $this->processParameterValue($value); } ksort($hints); diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php index f95ee37315d..3d8e2764687 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -84,7 +84,9 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e */ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null) { - $hints[Query::HINT_CACHE_ENABLED] = true; + $hints = array( + Query::HINT_CACHE_ENABLED => true + ); if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 24bbb23cbf8..a980f236ecc 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -266,6 +266,11 @@ class UnitOfWork implements PropertyChangedListener */ private $cachedPersisters = array(); + /** + * @var boolean + */ + protected $hasCache = false; + /** * Initializes a new UnitOfWork instance, bound to the given EntityManager. * @@ -276,6 +281,7 @@ public function __construct(EntityManager $em) $this->em = $em; $this->evm = $em->getEventManager(); $this->listenersInvoker = new ListenersInvoker($em); + $this->hasCache = $em->getConfiguration()->isSecondLevelCacheEnabled(); } /** @@ -365,7 +371,7 @@ public function commit($entity = null) $collectionPersister->delete($collectionToDelete); - if ($collectionPersister->hasCache()) { + if ($this->hasCache && $collectionPersister->hasCache()) { $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; } } @@ -376,7 +382,7 @@ public function commit($entity = null) $collectionPersister->update($collectionToUpdate); - if ($collectionPersister->hasCache()) { + if ($this->hasCache && $collectionPersister->hasCache()) { $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; } } @@ -817,7 +823,7 @@ private function computeAssociationChanges($entity, $assoc, $value) $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); // Handle colection cache - if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && isset($assoc['cache']) && count($value) > 0) { + if ($this->hasCache && $assoc['type'] === ClassMetadata::ONE_TO_MANY && isset($assoc['cache']) && count($value) > 0) { $collectionPersister = $this->getCollectionPersister($assoc); @@ -1021,7 +1027,7 @@ private function executeInserts($class) $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } - if ($persister->hasCache()) { + if ($this->hasCache && $persister->hasCache()) { $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -1060,7 +1066,7 @@ private function executeUpdates($class) $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke); } - if ($persister->hasCache()) { + if ($this->hasCache && $persister->hasCache()) { $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -1104,7 +1110,7 @@ private function executeDeletions($class) $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } - if ($persister->hasCache()) { + if ($this->hasCache && $persister->hasCache()) { $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -2417,6 +2423,7 @@ public function clear($entityName = null) $this->collectionUpdates = $this->extraUpdates = $this->readOnlyObjects = + $this->cachedPersisters = $this->visitedCollections = $this->orphanRemovals = array(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php index d8e062dfc1f..364381138b7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php @@ -17,7 +17,6 @@ use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Cache\Lock; - /** * @group DDC-2183 */ From f6a5efe395562295841a503dcc6f6ddccb22cbe2 Mon Sep 17 00:00:00 2001 From: fabios Date: Wed, 26 Jun 2013 17:02:34 -0400 Subject: [PATCH 44/71] Performance test and improvements --- .../ORM/Cache/CollectionCacheEntry.php | 8 +- .../Cache/DefaultCollectionEntryStructure.php | 15 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 40 ++-- lib/Doctrine/ORM/Cache/RegionAccess.php | 6 +- .../AbstractCollectionPersister.php | 2 +- .../SecondLevelCacheManyToManyTest.php | 5 +- .../SecondLevelCacheOneToManyTest.php | 5 +- .../SecondLevelCacheQueryCacheTest.php | 30 +-- .../ORM/Performance/SencondLevelCacheTest.php | 179 ++++++++++++++---- .../Doctrine/Tests/OrmFunctionalTestCase.php | 34 +++- 10 files changed, 213 insertions(+), 111 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php index a6dad8da1ef..ab1c993e49d 100644 --- a/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php @@ -31,13 +31,13 @@ class CollectionCacheEntry implements CacheEntry /** * @var array */ - public $dataList; + public $identifiers; /** - * @param array $dataList + * @param array $identifiers */ - public function __construct(array $dataList) + public function __construct(array $identifiers) { - $this->dataList = $dataList; + $this->identifiers = $identifiers; } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php index 88f96373736..4bbce26122f 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Cache; +use Doctrine\ORM\Query; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; @@ -44,6 +45,11 @@ class DefaultCollectionEntryStructure implements CollectionEntryStructure */ private $uow; + /** + * @var array + */ + private static $hints = array(Query::HINT_CACHE_ENABLED => true); + /** * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. */ @@ -77,14 +83,15 @@ public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $targetRegion = $targetPersister->getCacheRegionAcess()->getRegion(); $list = array(); - foreach ($entry->dataList as $index => $entry) { + foreach ($entry->identifiers as $index => $identifier) { + + $entityEntry = $targetRegion->get(new EntityCacheKey($targetEntity, $identifier)); - if ( ! $targetRegion->contains(new EntityCacheKey($targetEntity, $entry))) { + if ($entityEntry === null) { return null; } - $entity = $this->em->getReference($targetEntity, $entry); - $list[$index] = $entity; + $list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints); } array_walk($list, function($entity, $index) use ($collection){ diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index d6becbead19..ab5c940f61e 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -28,6 +28,7 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Query; /** * Default query cache implementation. @@ -57,6 +58,11 @@ class DefaultQueryCache implements QueryCache */ private $logger; + /** + * @var array + */ + private static $hints = array(Query::HINT_CACHE_ENABLED => true); + /** * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. * @param \Doctrine\ORM\Cache\Region $region The query region. @@ -99,25 +105,25 @@ public function get(QueryCacheKey $key, AbstractQuery $query) // @TODO - move to cache hydration componente foreach ($entry->result as $index => $entry) { - if ( ! $region->contains(new EntityCacheKey($entityName, $entry['identifier']))) { + if (($entityEntry = $region->get(new EntityCacheKey($entityName, $entry['identifier']))) === null) { return null; } - $entity = $this->em->getReference($entityName, $entry['identifier']); + $entity = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints); $result[$index] = $entity; foreach ($entry['associations'] as $name => $assoc) { - $assocPersister = $this->uow->getEntityPersister($assoc['rootEntityName']); + $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); if ($assoc['type'] & ClassMetadata::TO_ONE) { - if ( ! $assocRegion->contains(new EntityCacheKey($assoc['rootEntityName'], $assoc['identifier']))) { + if (($assocEntry = $assocRegion->get(new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) { return null; } - $metadata->setFieldValue($entity, $name, $this->em->getReference($assoc['entityName'], $assoc['identifier'])); + $metadata->setFieldValue($entity, $name, $this->uow->createEntity($assocEntry->class, $assocEntry->data, $hints)); continue; } @@ -127,17 +133,17 @@ public function get(QueryCacheKey $key, AbstractQuery $query) } $oid = spl_object_hash($entity); - $targetClass = $this->em->getClassMetadata($assoc['rootEntityName']); + $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); $relation = $metadata->associationMappings[$name]; $collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); - foreach ($assoc['list'] as $assocIndex => $assocItem) { + foreach ($assoc['list'] as $assocIndex => $assocId) { - if ( ! $assocRegion->contains(new EntityCacheKey($assoc['rootEntityName'], $assocItem['identifier']))) { + if (($assocEntry = $assocRegion->get(new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) { return null; } - $element = $this->em->getReference($assocItem['entityName'], $assocItem['identifier']); + $element = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);; $collection->hydrateSet($assocIndex, $element); } @@ -193,15 +199,13 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) continue; } - $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); - $assocPersister = $this->uow->getEntityPersister($assocMetadata->rootEntityName); + $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + $assocMetadata = $assocPersister->getClassMetadata(); // Handle *-to-one associations if ($assoc['type'] & ClassMetadata::TO_ONE) { - $assocPersister = $this->uow->getEntityPersister($assocMetadata->rootEntityName); - $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); if ( ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { @@ -213,8 +217,7 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) } $data[$index]['associations'][$name] = array( - 'rootEntityName'=> $assocMetadata->rootEntityName, - 'entityName' => $assocMetadata->name, + 'targetEntity' => $assocMetadata->rootEntityName, 'identifier' => $assocIdentifier, 'type' => $assoc['type'] ); @@ -240,14 +243,11 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) } } - $list[$assocItemIndex] = array( - 'entityName' => $assocMetadata->name, - 'identifier' => $assocIdentifier, - ); + $list[$assocItemIndex] = $assocIdentifier; } $data[$index]['associations'][$name] = array( - 'rootEntityName'=> $assocMetadata->rootEntityName, + 'targetEntity' => $assocMetadata->rootEntityName, 'type' => $assoc['type'], 'list' => $list, ); diff --git a/lib/Doctrine/ORM/Cache/RegionAccess.php b/lib/Doctrine/ORM/Cache/RegionAccess.php index e546134e5b2..086716d5a74 100644 --- a/lib/Doctrine/ORM/Cache/RegionAccess.php +++ b/lib/Doctrine/ORM/Cache/RegionAccess.php @@ -52,7 +52,7 @@ public function get(CacheKey $key); * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. * - * @return true if the object was successfully cached. + * @return TRUE if the object was successfully cached. * * @throws \Doctrine\ORM\Cache\CacheException */ @@ -64,7 +64,7 @@ public function put(CacheKey $key, CacheEntry $entry); * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. * - * @return boolean true If the contents of the cache actual were changed. + * @return boolean TRUE If the contents of the cache actual were changed. * * @throws \Doctrine\ORM\Cache\CacheException */ @@ -77,7 +77,7 @@ public function afterInsert(CacheKey $key, CacheEntry $entry); * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} * - * @return boolean true If the contents of the cache actual were changed. + * @return boolean TRUE If the contents of the cache actual were changed. * * @throws \Doctrine\ORM\Cache\CacheException */ diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index b90b2a33c75..7fbcb7f0cd1 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -298,7 +298,7 @@ public function saveCollectionCache(CollectionCacheKey $key, $elements) $targetRegion = $targetRegionAcess->getRegion(); $entry = $this->cacheEntryStructure->buildCacheEntry($this->targetEntity, $key, $elements); - foreach ($entry->dataList as $index => $identifier) { + foreach ($entry->identifiers as $index => $identifier) { $entityKey = new EntityCacheKey($this->targetEntity->rootEntityName, $identifier); if ($targetRegion->contains($entityKey)) { diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php index c0bf98d07da..08dcb2faf15 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -12,7 +12,7 @@ */ class SecondLevelCacheManyToManyTest extends SecondLevelCacheAbstractTest { - public function testPutOnPersist() + public function testPutManyToManyOnPersist() { $this->loadFixturesCountries(); $this->loadFixturesStates(); @@ -137,8 +137,7 @@ public function testPutAndLoadManyToManyRelation() $this->assertEquals($t2->getVisitedCities()->get(1)->getId(), $t4->getVisitedCities()->get(1)->getId()); $this->assertEquals($t2->getVisitedCities()->get(1)->getName(), $t4->getVisitedCities()->get(1)->getName()); - $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(City::CLASSNAME))); - $this->assertEquals(8, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index f39493df876..1289c99b852 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -133,8 +133,7 @@ public function testPutAndLoadOneToManyRelation() $this->assertEquals($s2->getCities()->get(1)->getId(), $s4->getCities()->get(1)->getId()); $this->assertEquals($s2->getCities()->get(1)->getName(), $s4->getCities()->get(1)->getName()); - $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(City::CLASSNAME))); - $this->assertEquals(8, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(4, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } @@ -175,7 +174,7 @@ public function testStoreOneToManyAssociationWhitCascade() $t1->removeTravel($t1->getTravels()->get(1)); $this->_em->persist($t1); - $this->_em->flush(); + $this->_em->flush($t1); $this->_em->clear(); $queryCount2 = $this->getCurrentQueryCount(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 96545b0928b..69ba62d7fcd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -57,8 +57,6 @@ public function testBasicQueryCache() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); @@ -69,13 +67,12 @@ public function testBasicQueryCache() $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); - $this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); - $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); } public function testBasicQueryCachePutEntityCache() @@ -124,8 +121,6 @@ public function testBasicQueryCachePutEntityCache() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); @@ -136,13 +131,12 @@ public function testBasicQueryCachePutEntityCache() $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); $this->assertEquals(3, $this->secondLevelCacheLogger->getPutCount()); - $this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); - $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); } public function testBasicQueryParamsParams() @@ -176,7 +170,6 @@ public function testBasicQueryParamsParams() $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); $this->assertCount(1, $result2); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); $this->assertEquals($result1[0]->getId(), $result2[0]->getId()); @@ -226,8 +219,6 @@ public function testLoadFromDatabaseWhenEntityMissing() $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); - $this->assertNotInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); - $this->assertNotInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); @@ -282,8 +273,6 @@ public function testBasicQueryFetchJoinsOneToMany() ->setCacheable(true) ->getResult(); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf(State::CLASSNAME, $result2[0]); $this->assertInstanceOf(State::CLASSNAME, $result2[1]); $this->assertCount(2, $result2[0]->getCities()); @@ -294,11 +283,6 @@ public function testBasicQueryFetchJoinsOneToMany() $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(0)); $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(1)); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]->getCities()->get(0)); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]->getCities()->get(1)); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]->getCities()->get(0)); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]->getCities()->get(1)); - $this->assertNotNull($result2[0]->getCities()->get(0)->getId()); $this->assertNotNull($result2[0]->getCities()->get(1)->getId()); $this->assertNotNull($result2[1]->getCities()->get(0)->getId()); @@ -361,11 +345,6 @@ public function testBasicQueryFetchJoinsManyToOne() $this->assertInstanceOf(State::CLASSNAME, $result2[0]->getState()); $this->assertInstanceOf(State::CLASSNAME, $result2[1]->getState()); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]->getState()); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]->getState()); - $this->assertNotNull($result2[0]->getId()); $this->assertNotNull($result2[0]->getId()); $this->assertNotNull($result2[1]->getState()->getId()); @@ -433,8 +412,6 @@ public function testBasicNativeQueryCache() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[0]); - $this->assertInstanceOf('Doctrine\Common\Proxy\Proxy', $result2[1]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[0]); $this->assertInstanceOf('Doctrine\Tests\Models\Cache\Country', $result2[1]); @@ -445,13 +422,12 @@ public function testBasicNativeQueryCache() $this->assertEquals($result1[1]->getName(), $result2[1]->getName()); $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); - $this->assertEquals(3, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getDefaultQueryRegionName())); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); - $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); } public function testQueryDependsOnFirstAndMaxResultResult() diff --git a/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php index fe1fcc37ec4..dbcd7ae0c75 100644 --- a/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php @@ -2,7 +2,10 @@ namespace Doctrine\Tests\ORM\Performance; +use Doctrine\Tests\OrmFunctionalTestCase; use Doctrine\Tests\Models\Cache\Country; +use Doctrine\Tests\Models\Cache\State; +use Doctrine\Tests\Models\Cache\City; require_once __DIR__ . '/../../TestInit.php'; @@ -10,77 +13,179 @@ * @group DDC-2183 * @group performance */ -class SencondLevelCacheTest extends \Doctrine\Tests\OrmFunctionalTestCase +class SencondLevelCacheTest extends OrmFunctionalTestCase { - - protected function setUp(){} - - public function testFindCountryWithoutCache() + protected function setUp() { parent::useModelSet('cache'); parent::setUp(); + } + + public function testFindEntityWithoutCache() + { + $this->findEntity(__FUNCTION__); + } + + public function testFindEntityWithCache() + { + parent::enableSecondLevelCache(false); + + $this->findEntity(__FUNCTION__); + } + + public function testFindEntityOneToManyWithoutCache() + { + + $this->findEntityOneToMany(__FUNCTION__); + } + + public function testFindEntityOneToManyWithCache() + { + parent::enableSecondLevelCache(false); + $this->findEntityOneToMany(__FUNCTION__); + } + + public function testQueryEntityWithoutCache() + { + $this->queryEntity(__FUNCTION__); + } + + public function testQueryEntityWithCache() + { + parent::enableSecondLevelCache(false); + + $this->queryEntity(__FUNCTION__); + } + + private function queryEntity($label) + { + $times = 500; $size = 500; - $countries = array(); $startPersist = microtime(true); - + $em = $this->_getEntityManager(); + for ($i = 0; $i < $size; $i++) { - $country = new Country("Country $i"); - - $this->_em->persist($country); + $em->persist(new Country("Country $i")); + } - $countries[] = $country; + $em->flush(); + $em->close(); + + printf("\n$label - [%s] persist %s countries\n", number_format(microtime(true) - $startPersist, 6), $size); + + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c WHERE c.name = :name OR c.id < :count'; + $startFind = microtime(true); + + for ($i = 0; $i < $times; $i++) { + $em = $this->_getEntityManager(); + + $em->createQuery($dql) + ->setParameter('name', "Country $i") + ->setParameter('count', $i) + ->setCacheable(true) + ->getResult(); + } + + printf("$label - [%s] select %s countries (%s times)\n", number_format(microtime(true) - $startFind, 6), $size, $times); + } + + public function findEntityOneToMany($label) + { + $times = 50; + $size = 30; + $states = array(); + $cities = array(); + $startPersist = microtime(true); + $em = $this->_getEntityManager(); + $country = new Country("Country"); + + $em->persist($country); + $em->flush(); + + for ($i = 0; $i < $size / 2; $i++) { + $state = new State("State $i", $country); + + $em->persist($state); + + $states[] = $state; + } + + $em->flush(); + + foreach ($states as $key => $state) { + for ($i = 0; $i < $size; $i++) { + $city = new City("City $key - $i", $state); + + $em->persist($city); + + $state->addCity($city); + + $cities[] = $city; + } } - $this->_em->flush(); + $em->flush(); + $em->clear(); - printf("NOCACHE - persist %s countries %s\n", number_format(microtime(true) - $startPersist, 6), $size); + $endPersist = microtime(true); + $format = "\n$label - [%s] persist %s states and %s cities\n"; - $this->_em->clear(); + printf($format, number_format($endPersist - $startPersist, 6), count($states), count($cities)); - $queryCount = count($this->_sqlLoggerStack->queries); $startFind = microtime(true); - foreach ($countries as $country) { - $this->_em->find('Doctrine\Tests\Models\Cache\Country', $country->getId()); + for ($i = 0; $i < $times; $i++) { + + $em = $this->_getEntityManager(); + + foreach ($states as $state) { + + $state = $em->find(State::CLASSNAME, $state->getId()); + + foreach ($state->getCities() as $city) { + $city->getName(); + } + } } - $this->assertEquals($queryCount + $size, count($this->_sqlLoggerStack->queries)); - printf("NOCACHE - find %s countries %s\n", number_format(microtime(true) - $startFind, 6), $size); + $endFind = microtime(true); + $format = "$label - [%s] find %s states and %s cities (%s times)\n"; + + printf($format, number_format($endFind - $startFind, 6), count($states), count($cities), $times); } - public function testFindCountryWithCache() + private function findEntity($label) { - parent::enableSecondLevelCache(); - parent::useModelSet('cache'); - parent::setUp(); - - $size = 500; - $countries = array(); - $startPersist = microtime(true); + $times = 50; + $size = 500; + $countries = array(); + $startPersist = microtime(true); + $em = $this->_getEntityManager(); for ($i = 0; $i < $size; $i++) { $country = new Country("Country $i"); - $this->_em->persist($country); + $em->persist($country); $countries[] = $country; } - $this->_em->flush(); - - printf("CACHE - persist %s countries %s\n", number_format(microtime(true) - $startPersist, 6), $size); + $em->flush(); + $em->close(); - $this->_em->clear(); + printf("\n$label - [%s] persist %s countries \n", number_format(microtime(true) - $startPersist, 6), $size); - $queryCount = count($this->_sqlLoggerStack->queries); $startFind = microtime(true); - foreach ($countries as $country) { - $this->_em->find('Doctrine\Tests\Models\Cache\Country', $country->getId()); + for ($i = 0; $i < $times; $i++) { + $em = $this->_getEntityManager(); + + foreach ($countries as $country) { + $em->find(Country::CLASSNAME, $country->getId()); + } } - $this->assertEquals($queryCount, count($this->_sqlLoggerStack->queries)); - printf("CACHE - find %s countries %s\n", number_format(microtime(true) - $startFind, 6), $size); + printf("$label - [%s] find %s countries (%s times)\n", number_format(microtime(true) - $startFind, 6), $size, $times); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index e049431b25a..30839167783 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -4,6 +4,7 @@ use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; use Doctrine\ORM\Cache\DefaultCacheFactory; +use Doctrine\Common\Cache\CacheProvider; /** * Base testcase class for all functional ORM testcases. @@ -17,11 +18,21 @@ abstract class OrmFunctionalTestCase extends OrmTestCase */ private $isSecondLevelCacheEnabled = false; + /** + * @var boolean + */ + private $isSecondLevelCacheLogEnabled = false; + /** * @var \Doctrine\ORM\Cache\CacheFactory */ private $secondLevelCacheFactory; + /** + * @var \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger + */ + protected $secondLevelCacheLogger; + /** * The metadata cache shared between all functional tests. * @@ -79,11 +90,6 @@ abstract class OrmFunctionalTestCase extends OrmTestCase */ protected static $_entityTablesCreated = array(); - /** - * @var \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger - */ - protected $secondLevelCacheLogger; - /** * List of model sets and their classes. * @@ -336,7 +342,13 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM cache_country'); } + $this->_em->clear(); + + if($this->isSecondLevelCacheEnabled && self::$sharedSecondLevelCacheDriverImpl instanceof CacheProvider) { + self::$sharedSecondLevelCacheDriverImpl->flushAll(); + } + } /** @@ -456,11 +468,14 @@ protected function _getEntityManager($config = null, $eventManager = null) { $factory = new DefaultCacheFactory($config, $cache); $this->secondLevelCacheFactory = $factory; - $this->secondLevelCacheLogger = new StatisticsCacheLogger(); + if ($this->isSecondLevelCacheLogEnabled) { + $this->secondLevelCacheLogger = new StatisticsCacheLogger(); + $config->setSecondLevelCacheLogger($this->secondLevelCacheLogger); + } + $config->setSecondLevelCacheEnabled(); $config->setSecondLevelCacheFactory($factory); - $config->setSecondLevelCacheLogger($this->secondLevelCacheLogger); } $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array( @@ -492,9 +507,10 @@ protected function _getEntityManager($config = null, $eventManager = null) { return \Doctrine\ORM\EntityManager::create($conn, $config); } - protected function enableSecondLevelCache() + protected function enableSecondLevelCache($log = true) { - $this->isSecondLevelCacheEnabled = true; + $this->isSecondLevelCacheEnabled = true; + $this->isSecondLevelCacheLogEnabled = $log; } /** From 96fe2df9535d8e04cd9d2cfbe1a1f3ae1a115205 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 27 Jun 2013 17:53:11 -0400 Subject: [PATCH 45/71] Cache join table inheritance --- lib/Doctrine/ORM/AbstractQuery.php | 4 +- .../ORM/Cache/DefaultEntityEntryStructure.php | 39 +++- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 33 +-- .../Cache/Logging/StatisticsCacheLogger.php | 24 +-- .../AbstractCollectionPersister.php | 4 +- .../ORM/Persisters/BasicEntityPersister.php | 5 +- lib/Doctrine/ORM/UnitOfWork.php | 11 + .../Tests/Models/Cache/Attraction.php | 25 ++- .../Models/Cache/AttractionContactInfo.php | 33 +++ .../Tests/Models/Cache/AttractionInfo.php | 54 +++++ .../Models/Cache/AttractionLocationInfo.php | 33 +++ .../SecondLevelCacheAbstractTest.php | 30 ++- ...condLevelCacheJoinTableInheritanceTest.php | 192 ++++++++++++++++++ .../SecondLevelCacheOneToManyTest.php | 2 +- ...ndLevelCacheSingleTableInheritanceTest.php | 28 +-- .../Doctrine/Tests/OrmFunctionalTestCase.php | 6 + 16 files changed, 455 insertions(+), 68 deletions(-) create mode 100644 tests/Doctrine/Tests/Models/Cache/AttractionContactInfo.php create mode 100644 tests/Doctrine/Tests/Models/Cache/AttractionInfo.php create mode 100644 tests/Doctrine/Tests/Models/Cache/AttractionLocationInfo.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index b7d97cd4a67..61d45549526 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -122,7 +122,7 @@ abstract class AbstractQuery protected $_hydrationCacheProfile; /** - * Whether to use second level cache, if available. Defaults to TRUE. + * Whether to use second level cache, if available. * * @var boolean */ @@ -150,6 +150,7 @@ abstract class AbstractQuery * Enable/disable second level query (result) caching for this query. * * @param boolean $cacheable + * * @return \Doctrine\ORM\Query */ public function setCacheable($cacheable) @@ -169,6 +170,7 @@ public function isCacheable() /** * @param string $cacheRegion + * * @return \Doctrine\ORM\Query */ public function setCacheRegion($cacheRegion) diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php index 3d8e2764687..e99de2822be 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\Query; +use Doctrine\Common\Proxy\Proxy; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; @@ -44,6 +45,11 @@ class DefaultEntityEntryStructure implements EntityEntryStructure */ private $uow; + /** + * @var array + */ + private static $hints = array(Query::HINT_CACHE_ENABLED => true); + /** * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. */ @@ -63,11 +69,11 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e foreach ($metadata->associationMappings as $name => $assoc) { - if ( ! isset($data[$name]) || $data[$name] === null) { + if ( ! isset($data[$name])) { continue; } - if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) { + if (isset($assoc['cache']) && $assoc['type'] & ClassMetadata::TO_ONE && ! $data[$name] instanceof Proxy) { $data[$name] = $this->uow->getEntityIdentifier($data[$name]); continue; @@ -84,15 +90,36 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e */ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null) { - $hints = array( - Query::HINT_CACHE_ENABLED => true - ); + $data = $entry->data; + $hints = self::$hints; if ($entity !== null) { $hints[Query::HINT_REFRESH] = true; $hints[Query::HINT_REFRESH_ENTITY] = $entity; } - return $this->uow->createEntity($entry->class, $entry->data, $hints); + foreach ($metadata->associationMappings as $name => $assoc) { + + if ( ! isset($assoc['cache']) || ! isset($data[$name])) { + continue; + } + + if ( ! $assoc['type'] & ClassMetadata::TO_ONE) { + continue; + } + + $assocId = $data[$name]; + $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); + $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + $assocEntry = $assocRegion->get(new EntityCacheKey($assoc['targetEntity'], $assocId)); + + if ($assocEntry === null) { + return null; + } + + $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints); + } + + return $this->uow->createEntity($entry->class, $data, $hints); } } diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index ab5c940f61e..ac23f559206 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -95,12 +95,13 @@ public function get(QueryCacheKey $key, AbstractQuery $query) return null; } - $result = array(); - $rsm = $query->getResultSetMapping(); - $entityName = reset($rsm->aliasMap); //@TODO find root entity - $metadata = $this->em->getClassMetadata($entityName); - $persister = $this->uow->getEntityPersister($entityName); - $region = $persister->getCacheRegionAcess()->getRegion(); + $result = array(); + $rsm = $query->getResultSetMapping(); + $entityName = reset($rsm->aliasMap); //@TODO find root entity + $hasRelation = ( ! empty($rsm->relationMap)); + $metadata = $this->em->getClassMetadata($entityName); + $persister = $this->uow->getEntityPersister($entityName); + $region = $persister->getCacheRegionAcess()->getRegion(); // @TODO - move to cache hydration componente foreach ($entry->result as $index => $entry) { @@ -109,8 +110,13 @@ public function get(QueryCacheKey $key, AbstractQuery $query) return null; } - $entity = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints); - $result[$index] = $entity; + if ( ! $hasRelation) { + $result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints); + + continue; + } + + $data = $entityEntry->data; foreach ($entry['associations'] as $name => $assoc) { @@ -123,7 +129,7 @@ public function get(QueryCacheKey $key, AbstractQuery $query) return null; } - $metadata->setFieldValue($entity, $name, $this->uow->createEntity($assocEntry->class, $assocEntry->data, $hints)); + $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints); continue; } @@ -132,9 +138,7 @@ public function get(QueryCacheKey $key, AbstractQuery $query) continue; } - $oid = spl_object_hash($entity); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - $relation = $metadata->associationMappings[$name]; $collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection()); foreach ($assoc['list'] as $assocIndex => $assocId) { @@ -148,11 +152,12 @@ public function get(QueryCacheKey $key, AbstractQuery $query) $collection->hydrateSet($assocIndex, $element); } + $data[$name] = $collection; + $collection->setInitialized(true); - $collection->setOwner($entity, $relation); - $metadata->setFieldValue($entity, $name, $collection); - $this->uow->setOriginalEntityProperty($oid, $name, $collection); } + + $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);; } return $result; diff --git a/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php index ebbff91ded9..48a1a9e878a 100644 --- a/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php +++ b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php @@ -202,13 +202,7 @@ public function clearStats() */ public function getPutCount() { - $total = 0; - - foreach ($this->cachePutCountMap as $count) { - $total = $total + $count; - } - - return $total; + return array_sum($this->cachePutCountMap); } /** @@ -218,13 +212,7 @@ public function getPutCount() */ public function getHitCount() { - $total = 0; - - foreach ($this->cacheHitCountMap as $count) { - $total = $total + $count; - } - - return $total; + return array_sum($this->cacheHitCountMap); } /** @@ -234,12 +222,6 @@ public function getHitCount() */ public function getMissCount() { - $total = 0; - - foreach ($this->cacheMissCountMap as $count) { - $total = $total + $count; - } - - return $total; + return array_sum($this->cacheMissCountMap); } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 7fbcb7f0cd1..7f3deb1c02a 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\EntityManager; +use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; @@ -306,7 +307,8 @@ public function saveCollectionCache(CollectionCacheKey $key, $elements) } $entity = $elements[$index]; - $entityEntry = $targetStructure->buildCacheEntry($this->targetEntity, $entityKey, $entity); + $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); + $entityEntry = $targetStructure->buildCacheEntry($class, $entityKey, $entity); $targetRegionAcess->put($entityKey, $entityEntry); } diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index b4e9908ab98..7553d6a0054 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -886,13 +886,13 @@ public function loadById(array $identifier, $entity = null) $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); $cacheEntry = $this->cacheRegionAccess->get($cacheKey); - if ($cacheEntry !== null) { + if ($cacheEntry !== null && ($entity = $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity)) !== null) { if ($this->cacheLogger) { $this->cacheLogger->entityCacheHit($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); } - return $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity); + return $entity; } } @@ -2207,7 +2207,6 @@ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targ public function putEntityCache($entity, EntityCacheKey $key) { $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); - $key = $key ?: new EntityCacheKey($class->rootEntityName, $this->em->getUnitOfWork()->getEntityIdentifier($item['entity'])); $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $entity); $cached = $this->cacheRegionAccess->put($key, $entry); diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index a980f236ecc..20404ca3cc4 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -2769,6 +2769,17 @@ public function createEntity($className, array $data, &$hints = array()) break; } + // use the given collection + if (isset($data[$field]) && $data[$field] instanceof PersistentCollection) { + + $data[$field]->setOwner($entity, $assoc); + + $class->reflFields[$field]->setValue($entity, $data[$field]); + $this->originalEntityData[$oid][$field] = $data[$field]; + + break; + } + // Inject collection $pColl = new PersistentCollection($this->em, $targetClass, new ArrayCollection); $pColl->setOwner($entity, $assoc); diff --git a/tests/Doctrine/Tests/Models/Cache/Attraction.php b/tests/Doctrine/Tests/Models/Cache/Attraction.php index e6e74cf31fc..08e84487ef4 100644 --- a/tests/Doctrine/Tests/Models/Cache/Attraction.php +++ b/tests/Doctrine/Tests/Models/Cache/Attraction.php @@ -2,6 +2,8 @@ namespace Doctrine\Tests\Models\Cache; +use Doctrine\Common\Collections\ArrayCollection; + /** * @Cache * @Entity @@ -36,10 +38,17 @@ abstract class Attraction */ protected $city; + /** + * @Cache + * @OneToMany(targetEntity="AttractionInfo", mappedBy="attraction") + */ + protected $infos; + public function __construct($name, City $city) { - $this->name = $name; - $this->city = $city; + $this->name = $name; + $this->city = $city; + $this->infos = new ArrayCollection(); } public function getId() @@ -71,4 +80,16 @@ public function setCity(City $city) { $this->city = $city; } + + public function getInfos() + { + return $this->infos; + } + + public function addInfo(AttractionInfo $info) + { + if ( ! $this->infos->contains($info)) { + $this->infos->add($info); + } + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/AttractionContactInfo.php b/tests/Doctrine/Tests/Models/Cache/AttractionContactInfo.php new file mode 100644 index 00000000000..76ef305a41c --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/AttractionContactInfo.php @@ -0,0 +1,33 @@ +setAttraction($attraction); + $this->setFone($fone); + } + + public function getFone() + { + return $this->fone; + } + + public function setFone($fone) + { + $this->fone = $fone; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/AttractionInfo.php b/tests/Doctrine/Tests/Models/Cache/AttractionInfo.php new file mode 100644 index 00000000000..418ef49b06c --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/AttractionInfo.php @@ -0,0 +1,54 @@ +id; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getAttraction() + { + return $this->attraction; + } + + public function setAttraction(Attraction $attraction) + { + $this->attraction = $attraction; + + $attraction->addInfo($this); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/AttractionLocationInfo.php b/tests/Doctrine/Tests/Models/Cache/AttractionLocationInfo.php new file mode 100644 index 00000000000..ebdb592aea9 --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/AttractionLocationInfo.php @@ -0,0 +1,33 @@ +setAttraction($attraction); + $this->setAddress($address); + } + + public function getAddress() + { + return $this->address; + } + + public function setAddress($address) + { + $this->address = $address; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php index bd099e98dc3..53c3ff37bf4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -15,6 +15,9 @@ use Doctrine\Tests\Models\Cache\Beach; use Doctrine\Tests\Models\Cache\Bar; +use Doctrine\Tests\Models\Cache\AttractionContactInfo; +use Doctrine\Tests\Models\Cache\AttractionLocationInfo; + require_once __DIR__ . '/../../TestInit.php'; /** @@ -22,12 +25,13 @@ */ abstract class SecondLevelCacheAbstractTest extends OrmFunctionalTestCase { - protected $countries = array(); - protected $states = array(); - protected $cities = array(); - protected $travels = array(); - protected $travelers = array(); - protected $attractions = array(); + protected $countries = array(); + protected $states = array(); + protected $cities = array(); + protected $travels = array(); + protected $travelers = array(); + protected $attractions = array(); + protected $attractionsInfo = array(); /** * @var \Doctrine\ORM\Cache @@ -163,6 +167,20 @@ protected function loadFixturesAttractions() $this->_em->flush(); } + protected function loadFixturesAttractionsInfo() + { + $this->attractionsInfo[] = new AttractionContactInfo('0000-0000', $this->attractions[0]); + $this->attractionsInfo[] = new AttractionContactInfo('1111-1111', $this->attractions[1]); + $this->attractionsInfo[] = new AttractionLocationInfo('Some St 1', $this->attractions[2]); + $this->attractionsInfo[] = new AttractionLocationInfo('Some St 2', $this->attractions[3]); + + foreach ($this->attractionsInfo as $info) { + $this->_em->persist($info); + } + + $this->_em->flush(); + } + protected function getEntityRegion($className) { return $this->cache->getEntityCacheRegionAccess($className)->getRegion()->getName(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php new file mode 100644 index 00000000000..2778695b279 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php @@ -0,0 +1,192 @@ +cache->getEntityCacheRegionAccess(AttractionInfo::CLASSNAME)->getRegion(); + $contactRegion = $this->cache->getEntityCacheRegionAccess(AttractionContactInfo::CLASSNAME)->getRegion(); + $locationRegion = $this->cache->getEntityCacheRegionAccess(AttractionLocationInfo::CLASSNAME)->getRegion(); + + $this->assertEquals($infoRegion->getName(), $contactRegion->getName()); + $this->assertEquals($infoRegion->getName(), $locationRegion->getName()); + } + + public function testPutOnPersistJoinTableInheritance() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + $this->loadFixturesAttractionsInfo(); + + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[0]->getId())); + $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[1]->getId())); + $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[2]->getId())); + $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[3]->getId())); + } + + public function testJoinTableCountaisRootClass() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + $this->loadFixturesAttractionsInfo(); + + $this->_em->clear(); + + foreach ($this->attractionsInfo as $info) { + $this->assertTrue($this->cache->containsEntity(AttractionInfo::CLASSNAME, $info->getId())); + $this->assertTrue($this->cache->containsEntity(get_class($info), $info->getId())); + } + } + + public function testPutAndLoadJoinTableEntities() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + $this->loadFixturesAttractionsInfo(); + + $this->_em->clear(); + + $this->cache->evictEntityRegion(AttractionInfo::CLASSNAME); + + $entityId1 = $this->attractionsInfo[0]->getId(); + $entityId2 = $this->attractionsInfo[1]->getId(); + + $this->assertFalse($this->cache->containsEntity(AttractionInfo::CLASSNAME, $entityId1)); + $this->assertFalse($this->cache->containsEntity(AttractionInfo::CLASSNAME, $entityId2)); + $this->assertFalse($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $entityId1)); + $this->assertFalse($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $entityId2)); + + $queryCount = $this->getCurrentQueryCount(); + $entity1 = $this->_em->find(AttractionInfo::CLASSNAME, $entityId1); + $entity2 = $this->_em->find(AttractionInfo::CLASSNAME, $entityId2); + + //load entity and relation whit sub classes + $this->assertEquals($queryCount + 4, $this->getCurrentQueryCount()); + + $this->assertTrue($this->cache->containsEntity(AttractionInfo::CLASSNAME, $entityId1)); + $this->assertTrue($this->cache->containsEntity(AttractionInfo::CLASSNAME, $entityId2)); + $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $entityId1)); + $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $entityId2)); + + $this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity1); + $this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity2); + $this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity1); + $this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity2); + + $this->assertEquals($this->attractionsInfo[0]->getId(), $entity1->getId()); + $this->assertEquals($this->attractionsInfo[0]->getFone(), $entity1->getFone()); + + $this->assertEquals($this->attractionsInfo[1]->getId(), $entity2->getId()); + $this->assertEquals($this->attractionsInfo[1]->getFone(), $entity2->getFone()); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $entity3 = $this->_em->find(AttractionInfo::CLASSNAME, $entityId1); + $entity4 = $this->_em->find(AttractionInfo::CLASSNAME, $entityId2); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity3); + $this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity4); + $this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity3); + $this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity4); + + $this->assertNotSame($entity1, $entity3); + $this->assertEquals($entity1->getId(), $entity3->getId()); + $this->assertEquals($entity1->getFone(), $entity3->getFone()); + + $this->assertNotSame($entity2, $entity4); + $this->assertEquals($entity2->getId(), $entity4->getId()); + $this->assertEquals($entity2->getFone(), $entity4->getFone()); + } + + public function testQueryCacheFindAllJoinTableEntities() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + $this->loadFixturesAttractionsInfo(); + $this->evictRegions(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT i, a FROM Doctrine\Tests\Models\Cache\AttractionInfo i JOIN i.attraction a'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(count($this->attractionsInfo), $result1); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->_em->clear(); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(count($this->attractionsInfo), $result2); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + foreach ($result2 as $entity) { + $this->assertInstanceOf(AttractionInfo::CLASSNAME, $entity); + } + } + + public function testOneToManyRelationJoinTable() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesAttractions(); + $this->loadFixturesAttractionsInfo(); + $this->evictRegions(); + $this->_em->clear(); + + $entity = $this->_em->find(Attraction::CLASSNAME, $this->attractions[0]->getId()); + + $this->assertInstanceOf(Attraction::CLASSNAME, $entity); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $entity->getInfos()); + $this->assertCount(1, $entity->getInfos()); + + $ownerId = $this->attractions[0]->getId(); + $queryCount = $this->getCurrentQueryCount(); + + $this->assertTrue($this->cache->containsEntity(Attraction::CLASSNAME, $ownerId)); + $this->assertTrue($this->cache->containsCollection(Attraction::CLASSNAME, 'infos', $ownerId)); + + $this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity->getInfos()->get(0)); + $this->assertEquals($this->attractionsInfo[0]->getFone(), $entity->getInfos()->get(0)->getFone()); + + $this->_em->clear(); + + $entity = $this->_em->find(Attraction::CLASSNAME, $this->attractions[0]->getId()); + + $this->assertInstanceOf(Attraction::CLASSNAME, $entity); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $entity->getInfos()); + $this->assertCount(1, $entity->getInfos()); + + $this->assertInstanceOf(AttractionContactInfo::CLASSNAME, $entity->getInfos()->get(0)); + $this->assertEquals($this->attractionsInfo[0]->getFone(), $entity->getInfos()->get(0)->getFone()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 1289c99b852..122be13ce8b 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -241,7 +241,7 @@ public function testLoadOnoToManyCollectionFromDatabaseWhenEntityMissing() public function testShoudNotPutOneToManyRelationOnPersistIfTheCollectionIsEmpty() { $this->loadFixturesCountries(); - $this->evictRegions(); + $this->cache->evictEntityRegion(State::CLASSNAME); $state = new State("State Foo", $this->countries[0]); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index fda55f65846..19429d540b8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -53,9 +53,6 @@ public function testCountaisRootClass() } } - /** - * @group menina - */ public function testPutAndLoadEntities() { $this->loadFixturesCountries(); @@ -168,7 +165,7 @@ public function testPutOneToManyRelationOnPersist() } } - public function testOneToManyRelation() + public function testOneToManyRelationSingleTable() { $this->loadFixturesCountries(); $this->loadFixturesStates(); @@ -178,8 +175,10 @@ public function testOneToManyRelation() $this->_em->clear(); $entity = $this->_em->find(City::CLASSNAME, $this->cities[0]->getId()); - $this->assertCount(2, $entity->getAttractions()); + + $this->assertInstanceOf(City::CLASSNAME, $entity); $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $entity->getAttractions()); + $this->assertCount(2, $entity->getAttractions()); $ownerId = $this->cities[0]->getId(); $queryCount = $this->getCurrentQueryCount(); @@ -192,16 +191,19 @@ public function testOneToManyRelation() $this->assertEquals($this->attractions[0]->getName(), $entity->getAttractions()->get(0)->getName()); $this->assertEquals($this->attractions[1]->getName(), $entity->getAttractions()->get(1)->getName()); - $city = $this->_em->find(City::CLASSNAME, $ownerId); + $this->_em->clear(); + + $entity = $this->_em->find(City::CLASSNAME, $ownerId); + + $this->assertInstanceOf(City::CLASSNAME, $entity); + $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $entity->getAttractions()); + $this->assertCount(2, $entity->getAttractions()); - $this->assertInstanceOf(City::CLASSNAME, $city); - $this->assertInstanceOf('Doctrine\ORM\PersistentCollection', $city->getAttractions()); - $this->assertCount(2, $city->getAttractions()); $this->assertEquals($queryCount, $this->getCurrentQueryCount()); - $this->assertInstanceOf(Bar::CLASSNAME, $city->getAttractions()->get(0)); - $this->assertInstanceOf(Bar::CLASSNAME, $city->getAttractions()->get(1)); - $this->assertEquals($this->attractions[0]->getName(), $city->getAttractions()->get(0)->getName()); - $this->assertEquals($this->attractions[1]->getName(), $city->getAttractions()->get(1)->getName()); + $this->assertInstanceOf(Bar::CLASSNAME, $entity->getAttractions()->get(0)); + $this->assertInstanceOf(Bar::CLASSNAME, $entity->getAttractions()->get(1)); + $this->assertEquals($this->attractions[0]->getName(), $entity->getAttractions()->get(0)->getName()); + $this->assertEquals($this->attractions[1]->getName(), $entity->getAttractions()->get(1)->getName()); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 30839167783..03eacccd8f3 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -196,6 +196,9 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\Cache\Restaurant', 'Doctrine\Tests\Models\Cache\Beach', 'Doctrine\Tests\Models\Cache\Bar', + 'Doctrine\Tests\Models\Cache\AttractionInfo', + 'Doctrine\Tests\Models\Cache\AttractionContactInfo', + 'Doctrine\Tests\Models\Cache\AttractionLocationInfo' ), ); @@ -333,6 +336,9 @@ protected function tearDown() } if (isset($this->_usedModelSets['cache'])) { + $conn->executeUpdate('DELETE FROM cache_attraction_location_info'); + $conn->executeUpdate('DELETE FROM cache_attraction_contact_info'); + $conn->executeUpdate('DELETE FROM cache_attraction_info'); $conn->executeUpdate('DELETE FROM cache_visited_cities'); $conn->executeUpdate('DELETE FROM cache_attraction'); $conn->executeUpdate('DELETE FROM cache_travel'); From fd2bee93e57c01d431e39519f75e6f8341cc4155 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 2 Jul 2013 11:31:08 -0400 Subject: [PATCH 46/71] test query cache exceptions --- lib/Doctrine/ORM/AbstractQuery.php | 4 -- lib/Doctrine/ORM/Cache/CacheException.php | 18 +++++++ lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 33 ++++++++---- .../SecondLevelCacheQueryCacheTest.php | 53 +++++++++++++++++++ 4 files changed, 94 insertions(+), 14 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 61d45549526..986e4ef6fa2 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -942,10 +942,6 @@ private function executeIgnoreQueryCache($parameters = null, $hydrationMode = nu */ private function executeUsingQueryCache($parameters = null, $hydrationMode = null) { - if ($this->getResultSetMapping()->isMixed) { - throw new ORMException("Second level cache does not suport mixed results yet."); - } - $querykey = new QueryCacheKey($this->getHash()); $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); $result = $queryCache->get($querykey, $this); diff --git a/lib/Doctrine/ORM/Cache/CacheException.php b/lib/Doctrine/ORM/Cache/CacheException.php index e04d0f0ed6f..068396c8d42 100644 --- a/lib/Doctrine/ORM/Cache/CacheException.php +++ b/lib/Doctrine/ORM/Cache/CacheException.php @@ -36,4 +36,22 @@ public static function updateReadOnlyobject() { return new self("Can't update a readonly object"); } + + /** + * @param string $entityName + * @return \Doctrine\ORM\Cache\CacheException + */ + public static function nonCacheableEntity($entityName) + { + return new self(sprintf('Entity "%s" not configured as part of the second-level cache.', $entityName)); + } + + /** + * @param string $entityName + * @return \Doctrine\ORM\Cache\CacheException + */ + public static function nonCacheableEntityAssociation($entityName, $field) + { + return new self(sprintf('Entity association field "%s#%s" not configured as part of the second-level cache.', $entityName, $field)); + } } diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index ac23f559206..8481489b616 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -27,6 +27,7 @@ use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Cache\CacheException; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\Query; @@ -170,26 +171,34 @@ public function get(QueryCacheKey $key, AbstractQuery $query) */ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) { - $data = array(); - $rsm = $query->getResultSetMapping(); + $data = array(); + $rsm = $query->getResultSetMapping(); + + if ($rsm->scalarMappings) { + throw new CacheException("Second level cache does not suport scalar results."); + } + $entityName = reset($rsm->aliasMap); //@TODO find root entity $hasRelation = ( ! empty($rsm->relationMap)); $metadata = $this->em->getClassMetadata($entityName); $persister = $this->uow->getEntityPersister($entityName); - $region = $persister->getCacheRegionAcess()->getRegion(); + + if ( ! $persister->hasCache()) { + throw CacheException::nonCacheableEntity($entityName); + } + + $region = $persister->getCacheRegionAcess()->getRegion(); foreach ($result as $index => $entity) { $identifier = $this->uow->getEntityIdentifier($entity); $data[$index]['identifier'] = $identifier; $data[$index]['associations'] = array(); - if ($region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) { - continue; - } - - // Cancel put result if entity put fail - if ( ! $persister->putEntityCache($entity, $entityKey)) { - return; + if ( ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) { + // Cancel put result if entity put fail + if ( ! $persister->putEntityCache($entity, $entityKey)) { + return; + } } if ( ! $hasRelation) { @@ -200,6 +209,10 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) foreach ($rsm->relationMap as $name) { $assoc = $metadata->associationMappings[$name]; + if ( ! isset($assoc['cache'])) { + throw CacheException::nonCacheableEntityAssociation($entityName, $name); + } + if (($assocValue = $metadata->getFieldValue($entity, $name)) === null) { continue; } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 69ba62d7fcd..86233724cd4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -591,4 +591,57 @@ public function testQueryCacheRegion() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount('bar_region')); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount('bar_region')); } + + /** + * @expectedException Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Entity "Doctrine\Tests\Models\Generic\BooleanModel" not configured as part of the second-level cache. + */ + public function testQueryNotCacheableEntityException() + { + try { + $this->_schemaTool->createSchema(array( + $this->_em->getClassMetadata('Doctrine\Tests\Models\Generic\BooleanModel'), + )); + } catch (\Doctrine\ORM\Tools\ToolsException $exc) { + } + + $dql = 'SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b'; + $query = $this->_em->createQuery($dql); + + $query->setCacheable(true) + ->getResult(); + } + + /** + * @expectedException Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Entity association field "Doctrine\Tests\Models\Cache\City#travels" not configured as part of the second-level cache. + */ + public function testQueryNotCacheableAssociationException() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesTraveler(); + $this->loadFixturesTravels(); + + $dql = 'SELECT c, t FROM Doctrine\Tests\Models\Cache\City c LEFT JOIN c.travels t'; + $query = $this->_em->createQuery($dql); + + $query->setCacheable(true) + ->getResult(); + } + + /** + * @expectedException Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Second level cache does not suport scalar results. + */ + public function testQueryScalarResultException() + { + $dql = 'SELECT c, c.id, c.name FROM Doctrine\Tests\Models\Cache\Country c'; + $query = $this->_em->createQuery($dql); + + $query->setCacheable(true) + ->getResult(); + } + } \ No newline at end of file From 4105a80df72a3e38a4cea32706e99c1e51b2ab41 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 2 Jul 2013 11:54:53 -0400 Subject: [PATCH 47/71] Configurable query cache validation strategy --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 16 ++++--- .../ORM/Cache/QueryCacheValidator.php | 43 ++++++++++++++++++ .../Cache/TimestampQueryCacheValidator.php | 45 +++++++++++++++++++ lib/Doctrine/ORM/Configuration.php | 20 +++++++++ 4 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache/QueryCacheValidator.php create mode 100644 lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 8481489b616..9f42844267f 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -59,6 +59,11 @@ class DefaultQueryCache implements QueryCache */ private $logger; + /** + * @var \Doctrine\ORM\Cache\QueryCacheValidator + */ + private $validator; + /** * @var array */ @@ -70,10 +75,11 @@ class DefaultQueryCache implements QueryCache */ public function __construct(EntityManagerInterface $em, Region $region) { - $this->em = $em; - $this->region = $region; - $this->uow = $em->getUnitOfWork(); - $this->logger = $em->getConfiguration()->getSecondLevelCacheLogger(); + $this->em = $em; + $this->region = $region; + $this->uow = $em->getUnitOfWork(); + $this->logger = $em->getConfiguration()->getSecondLevelCacheLogger(); + $this->validator = $em->getConfiguration()->getSecondLevelCacheQueryValidator(); } /** @@ -90,7 +96,7 @@ public function get(QueryCacheKey $key, AbstractQuery $query) return null; } - if ($lifetime > 0 && ($entry->time + $lifetime) < time()) { + if ( ! $this->validator->isValid($entry, $query)) { $this->region->evict($key); return null; diff --git a/lib/Doctrine/ORM/Cache/QueryCacheValidator.php b/lib/Doctrine/ORM/Cache/QueryCacheValidator.php new file mode 100644 index 00000000000..45abd5389ae --- /dev/null +++ b/lib/Doctrine/ORM/Cache/QueryCacheValidator.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Cache\QueryCacheEntry; +use Doctrine\ORM\AbstractQuery; + +/** + * Cache query validator interface. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface QueryCacheValidator +{ + /** + * Checks if the query entry is valid + * + * @param \Doctrine\ORM\Cache\QueryCacheEntry $entry + * @param \Doctrine\ORM\AbstractQuery $query + * + * @return boolean + */ + public function isValid(QueryCacheEntry $entry, AbstractQuery $query); +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php new file mode 100644 index 00000000000..f070a4b3276 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php @@ -0,0 +1,45 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +use Doctrine\ORM\Cache\QueryCacheEntry; +use Doctrine\ORM\AbstractQuery; + +/** + * @since 2.5 + * @author Fabio B. Silva + */ +class TimestampQueryCacheValidator implements QueryCacheValidator +{ + /** + * {@inheritdoc} + */ + public function isValid(QueryCacheEntry $entry, AbstractQuery $query) + { + $lifetime = $query->getLifetime(); + + if ($lifetime == 0) { + return true; + } + + return ($entry->time + $lifetime) > time(); + } +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 821d91d56f7..8ff80495946 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -37,6 +37,8 @@ use Doctrine\ORM\Repository\RepositoryFactory; use Doctrine\ORM\Cache\CacheFactory; use Doctrine\ORM\Cache\Logging\CacheLogger; +use Doctrine\ORM\Cache\QueryCacheValidator; +use Doctrine\ORM\Cache\TimestampQueryCacheValidator; /** * Configuration container for all configuration options of Doctrine. @@ -334,6 +336,24 @@ public function setSecondLevelCacheLogger(CacheLogger $logger) $this->_attributes['secondLevelCacheLogger'] = $logger; } + /** + * @return \Doctrine\ORM\Cache\QueryCacheValidator + */ + public function getSecondLevelCacheQueryValidator() + { + return isset($this->_attributes['secondLevelCacheQueryValidator']) + ? $this->_attributes['secondLevelCacheQueryValidator'] + : $this->_attributes['secondLevelCacheQueryValidator'] = new TimestampQueryCacheValidator(); + } + + /** + * @param \Doctrine\ORM\Cache\QueryCacheValidator $validator + */ + public function setSecondLevelQueryValidator(QueryCacheValidator $validator) + { + $this->_attributes['secondLevelCacheQueryValidator'] = $validator; + } + /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * From fef2ac7afe75a605d7ce813ac0bf75ba4c106a3a Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 2 Jul 2013 13:28:58 -0400 Subject: [PATCH 48/71] Implement xml/yml/php drivers --- doctrine-mapping.xsd | 18 +++++++ .../ORM/Mapping/ClassMetadataInfo.php | 8 --- lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php | 41 ++++++++++++++ .../ORM/Mapping/Driver/YamlDriver.php | 43 +++++++++++++++ tests/Doctrine/Tests/Models/Cache/City.php | 5 ++ .../ORM/Mapping/AbstractMappingDriverTest.php | 30 ++++++++++- .../php/Doctrine.Tests.Models.Cache.City.php | 54 +++++++++++++++++++ .../Doctrine.Tests.Models.Cache.City.dcm.xml | 23 ++++++++ .../Doctrine.Tests.Models.Cache.City.dcm.yml | 36 +++++++++++++ 9 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Cache.City.php create mode 100644 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Cache.City.dcm.xml create mode 100644 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Cache.City.dcm.yml diff --git a/doctrine-mapping.xsd b/doctrine-mapping.xsd index f9c774d5723..2175a909917 100644 --- a/doctrine-mapping.xsd +++ b/doctrine-mapping.xsd @@ -55,6 +55,14 @@ + + + + + + + + @@ -152,8 +160,14 @@ + + + + + + @@ -445,6 +459,7 @@ + @@ -462,6 +477,7 @@ + @@ -477,6 +493,7 @@ + @@ -495,6 +512,7 @@ + diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 1d767eb9cf5..9097a9bbe5d 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1013,10 +1013,6 @@ public function enableCache(array $cache) $cache['region'] = strtolower(str_replace('\\', '.', $this->rootEntityName)); } - if ( ! isset($cache['properties'])) { - $cache['properties'] = array(); - } - $this->cache = $cache; } @@ -1035,10 +1031,6 @@ public function enableAssociationCache($fieldName, array $cache) $cache['region'] = strtolower(str_replace('\\', '.', $this->rootEntityName)) . '::' . $fieldName; } - if ( ! isset($cache['properties'])) { - $cache['properties'] = array(); - } - $this->associationMappings[$fieldName]['cache'] = $cache; } diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php index ed4386ac4fa..dbdaf6e6487 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php @@ -81,6 +81,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) $metadata->setPrimaryTable($table); + // Evaluate second level cache + if (isset($xmlRoot->{'cache'})) { + $metadata->enableCache($this->cacheToArray($xmlRoot->{'cache'})); + } + // Evaluate named queries if (isset($xmlRoot->{'named-queries'})) { foreach ($xmlRoot->{'named-queries'}->{'named-query'} as $namedQueryElement) { @@ -349,6 +354,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapOneToOne($mapping); + + // Evaluate second level cache + if (isset($oneToOneElement->{'cache'})) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement->{'cache'})); + } } } @@ -388,6 +398,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapOneToMany($mapping); + + // Evaluate second level cache + if (isset($oneToManyElement->{'cache'})) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement->{'cache'})); + } } } @@ -428,6 +443,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapManyToOne($mapping); + + // Evaluate second level cache + if (isset($manyToOneElement->{'cache'})) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement->{'cache'})); + } } } @@ -493,6 +513,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapManyToMany($mapping); + + // Evaluate second level cache + if (isset($manyToManyElement->{'cache'})) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement->{'cache'})); + } } } @@ -701,6 +726,22 @@ private function columnToArray(SimpleXMLElement $fieldMapping) return $mapping; } + /** + * @param SimpleXMLElement $cacheMapping + * + * @return array + */ + private function cacheToArray(SimpleXMLElement $cacheMapping) + { + $region = isset($cacheMapping['region']) ? (string)$cacheMapping['region'] : null; + $usage = isset($cacheMapping['usage']) ? constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . strtoupper($cacheMapping['usage'])) : null; + + return array( + 'usage' => $usage, + 'region' => $region, + ); + } + /** * Gathers a list of cascade options found in the given cascade element. * diff --git a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php index 1e7aa33560d..80b2d6a2abf 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php @@ -72,9 +72,16 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) // Evaluate root level properties $table = array(); + if (isset($element['table'])) { $table['name'] = $element['table']; } + + // Evaluate second level cache + if (isset($element['cache'])) { + $metadata->enableCache($this->cacheToArray($element['cache'])); + } + $metadata->setPrimaryTable($table); // Evaluate named queries @@ -361,6 +368,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapOneToOne($mapping); + + // Evaluate second level cache + if (isset($oneToOneElement['cache'])) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToOneElement['cache'])); + } } } @@ -394,6 +406,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapOneToMany($mapping); + + // Evaluate second level cache + if (isset($oneToManyElement['cache'])) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($oneToManyElement['cache'])); + } } } @@ -438,6 +455,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapManyToOne($mapping); + + // Evaluate second level cache + if (isset($manyToOneElement['cache'])) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToOneElement['cache'])); + } } } @@ -506,6 +528,11 @@ public function loadMetadataForClass($className, ClassMetadata $metadata) } $metadata->mapManyToMany($mapping); + + // Evaluate second level cache + if (isset($manyToManyElement['cache'])) { + $metadata->enableAssociationCache($mapping['fieldName'], $this->cacheToArray($manyToManyElement['cache'])); + } } } @@ -704,6 +731,22 @@ private function columnToArray($fieldName, $column) return $mapping; } + /** + * @param array $cacheMapping + * + * @return array + */ + private function cacheToArray($cacheMapping) + { + $region = isset($cacheMapping['region']) ? (string)$cacheMapping['region'] : null; + $usage = isset($cacheMapping['usage']) ? constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . strtoupper($cacheMapping['usage'])) : null; + + return array( + 'usage' => $usage, + 'region' => $region, + ); + } + /** * {@inheritDoc} */ diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php index 7bf5513daa3..8d8ee9a54d0 100644 --- a/tests/Doctrine/Tests/Models/Cache/City.php +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -101,4 +101,9 @@ public function getAttractions() { return $this->attractions; } + + public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata) + { + include __DIR__ . '/../../ORM/Mapping/php/Doctrine.Tests.Models.Cache.City.php'; + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index 28e6f1ede6f..c00e77ec9b5 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -6,7 +6,7 @@ use Doctrine\ORM\Event\LifecycleEventArgs; use Doctrine\Tests\Models\Company\CompanyFixContract; use Doctrine\Tests\Models\Company\CompanyFlexContract; - +use Doctrine\Tests\Models\Cache\City; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadataInfo; @@ -873,6 +873,34 @@ public function testEntityListenersNamingConvention() $this->assertEquals(Events::postLoad, $postLoad['method']); $this->assertEquals(Events::preFlush, $preFlush['method']); } + + /** + * @group DDC-2183 + */ + public function testSecondLevelCacheMapping() + { + $em = $this->_getTestEntityManager(); + $factory = $this->createClassMetadataFactory($em); + $class = $factory->getMetadataFor(City::CLASSNAME); + $this->assertArrayHasKey('usage', $class->cache); + $this->assertArrayHasKey('region', $class->cache); + $this->assertEquals(ClassMetadata::CACHE_USAGE_READ_ONLY, $class->cache['usage']); + $this->assertEquals('doctrine.tests.models.cache.city', $class->cache['region']); + + $this->assertArrayHasKey('state', $class->associationMappings); + $this->assertArrayHasKey('cache', $class->associationMappings['state']); + $this->assertArrayHasKey('usage', $class->associationMappings['state']['cache']); + $this->assertArrayHasKey('region', $class->associationMappings['state']['cache']); + $this->assertEquals(ClassMetadata::CACHE_USAGE_READ_ONLY, $class->associationMappings['state']['cache']['usage']); + $this->assertEquals('doctrine.tests.models.cache.city::state', $class->associationMappings['state']['cache']['region']); + + $this->assertArrayHasKey('attractions', $class->associationMappings); + $this->assertArrayHasKey('cache', $class->associationMappings['attractions']); + $this->assertArrayHasKey('usage', $class->associationMappings['attractions']['cache']); + $this->assertArrayHasKey('region', $class->associationMappings['attractions']['cache']); + $this->assertEquals(ClassMetadata::CACHE_USAGE_READ_ONLY, $class->associationMappings['attractions']['cache']['usage']); + $this->assertEquals('doctrine.tests.models.cache.city::attractions', $class->associationMappings['attractions']['cache']['region']); + } } /** diff --git a/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Cache.City.php b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Cache.City.php new file mode 100644 index 00000000000..6dd477fa628 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Cache.City.php @@ -0,0 +1,54 @@ +setInheritanceType(ClassMetadataInfo::INHERITANCE_TYPE_NONE); +$metadata->setPrimaryTable(array('name' => 'cache_city')); +$metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_IDENTITY); +$metadata->setChangeTrackingPolicy(ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT); + +$metadata->enableCache(array( + 'usage' => ClassMetadataInfo::CACHE_USAGE_READ_ONLY +)); + +$metadata->mapField(array( + 'fieldName' => 'id', + 'type' => 'integer', + 'id' => true, + )); + +$metadata->mapField(array( + 'fieldName' => 'name', + 'type' => 'string', +)); + + +$metadata->mapOneToOne(array( + 'fieldName' => 'state', + 'targetEntity' => 'Doctrine\\Tests\\Models\\Cache\\State', + 'inversedBy' => 'cities', + 'joinColumns' => + array(array( + 'name' => 'state_id', + 'referencedColumnName' => 'id', + )) +)); +$metadata->enableAssociationCache('state', array( + 'usage' => ClassMetadataInfo::CACHE_USAGE_READ_ONLY +)); + +$metadata->mapManyToMany(array( + 'fieldName' => 'travels', + 'targetEntity' => 'Doctrine\\Tests\\Models\\Cache\\Travel', + 'mappedBy' => 'visitedCities', +)); + +$metadata->mapOneToMany(array( + 'fieldName' => 'attractions', + 'targetEntity' => 'Doctrine\\Tests\\Models\\Cache\\Attraction', + 'mappedBy' => 'city', + 'orderBy' => array('name' => 'ASC',), +)); +$metadata->enableAssociationCache('attractions', array( + 'usage' => ClassMetadataInfo::CACHE_USAGE_READ_ONLY +)); \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Cache.City.dcm.xml b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Cache.City.dcm.xml new file mode 100644 index 00000000000..84b786a7bd7 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Cache.City.dcm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Cache.City.dcm.yml b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Cache.City.dcm.yml new file mode 100644 index 00000000000..05286e0df56 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Cache.City.dcm.yml @@ -0,0 +1,36 @@ +Doctrine\Tests\Models\Cache\City: + type: entity + table: cache_city + cache: + usage : READ_ONLY + id: + id: + type: integer + id: true + generator: + strategy: IDENTITY + fields: + name: + type: string + manyToOne: + state: + targetEntity: Doctrine\Tests\Models\Cache\State + inversedBy: cities + joinColumns: + state_id: + referencedColumnName: id + cache: + usage : READ_ONLY + manyToMany: + travels: + targetEntity: Doctrine\Tests\Models\Cache\Travel + mappedBy: visitedCities + + oneToMany: + attractions: + targetEntity: Doctrine\Tests\Models\Cache\Attraction + mappedBy: city + cache: + usage : READ_ONLY + orderBy: + name: ASC \ No newline at end of file From 92a6a7b271802397ac0ac0116b1ed319a8828a64 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 2 Jul 2013 17:36:37 -0400 Subject: [PATCH 49/71] Refactoring collection cache --- lib/Doctrine/ORM/ORMException.php | 14 +++ .../AbstractCollectionPersister.php | 72 +++++++---- .../Persisters/JoinedSubclassPersister.php | 8 ++ lib/Doctrine/ORM/UnitOfWork.php | 43 +++---- ...condLevelCacheJoinTableInheritanceTest.php | 8 +- .../SecondLevelCacheManyToManyTest.php | 2 +- .../SecondLevelCacheOneToManyTest.php | 114 ++---------------- ...ndLevelCacheSingleTableInheritanceTest.php | 4 +- 8 files changed, 99 insertions(+), 166 deletions(-) diff --git a/lib/Doctrine/ORM/ORMException.php b/lib/Doctrine/ORM/ORMException.php index bc4bd15c1ad..8349c4f6da1 100644 --- a/lib/Doctrine/ORM/ORMException.php +++ b/lib/Doctrine/ORM/ORMException.php @@ -100,6 +100,20 @@ public static function unrecognizedField($field) return new self("Unrecognized field: $field"); } + /** + * + * @param string $class + * @param string $association + * @param string $given + * @param string $expected + * + * @return \Doctrine\ORM\ORMInvalidArgumentException + */ + static public function unexpectedAssociationValue($class, $association, $given, $expected) + { + return new self(sprintf('Found entity of type %s on association %s#%s, but expecting %s', $given, $class, $association, $expected)); + } + /** * @param string $className * @param string $field diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index 7f3deb1c02a..d90d2bc0745 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -167,12 +167,27 @@ public function delete(PersistentCollection $coll) return; // ignore inverse side } - $sql = $this->getDeleteSQL($coll); + $cacheKey = null; + $cacheLock = null; + $sql = $this->getDeleteSQL($coll); + + if ($this->hasCache) { + $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); + $cacheKey = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + } + + if ($this->isConcurrentRegion) { + $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); + } $this->conn->executeUpdate($sql, $this->getDeleteSQLParameters($coll)); if ($this->hasCache) { - $this->queuedCachedCollectionChange($coll->getOwner(), null); + $this->queuedCache[] = array( + 'list' => null, + 'key' => $cacheKey, + 'lock' => $cacheLock + ); } } @@ -211,26 +226,30 @@ public function update(PersistentCollection $coll) return; // ignore inverse side } + $cacheKey = null; + $cacheLock = null; + + if ($this->hasCache) { + $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); + $cacheKey = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + } + + if ($this->isConcurrentRegion) { + $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); + } + $this->deleteRows($coll); $this->insertRows($coll); if ($this->hasCache && $coll->isDirty()) { - $this->queuedCachedCollectionChange($coll->getOwner(), $coll); + $this->queuedCache[] = array( + 'list' => $coll, + 'key' => $cacheKey, + 'lock' => $cacheLock + ); } } - /** - * @param object $owner - * @param array|\Doctrine\Common\Collections\Collection $elements - */ - public function queuedCachedCollectionChange($owner, $elements) - { - $this->queuedCache[] = array( - 'owner' => $owner, - 'elements' => $elements, - ); - } - /** * Deletes rows. * @@ -327,22 +346,21 @@ public function saveCollectionCache(CollectionCacheKey $key, $elements) */ public function afterTransactionComplete() { - // @TODO - handle locks ? - - $uow = $this->em->getUnitOfWork(); - foreach ($this->queuedCache as $item) { - $elements = $item['elements']; - $ownerId = $uow->getEntityIdentifier($item['owner']); - $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + $list = $item['list']; + $key = $item['key']; - if ($elements === null) { + if ($list === null) { $this->cacheRegionAccess->evict($key); continue; } - $this->saveCollectionCache($key, $elements); + $this->saveCollectionCache($key, $list); + + if ($item['lock'] !== null) { + $this->cacheRegionAccess->unlockItem($key, $item['lock']); + } } $this->queuedCache = array(); @@ -355,14 +373,16 @@ public function afterTransactionComplete() */ public function afterTransactionRolledBack() { - // @TODO - handle locks ? - if ( ! $this->isConcurrentRegion) { $this->queuedCache = array(); return; } + foreach ($this->queuedCache as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + $this->queuedCache = array(); } diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 684d30571cc..b8a7431d595 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -204,6 +204,14 @@ public function executeInserts() $stmt->execute(); } + + if ($this->hasCache) { + $this->queuedCache['insert'][] = array( + 'entity' => $entity, + 'lock' => null, + 'key' => null + ); + } } $rootTableStmt->closeCursor(); diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 20404ca3cc4..3bb76fb841d 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -822,29 +822,11 @@ private function computeAssociationChanges($entity, $assoc, $value) $unwrappedValue = ($assoc['type'] & ClassMetadata::TO_ONE) ? array($value) : $value->unwrap(); $targetClass = $this->em->getClassMetadata($assoc['targetEntity']); - // Handle colection cache - if ($this->hasCache && $assoc['type'] === ClassMetadata::ONE_TO_MANY && isset($assoc['cache']) && count($value) > 0) { - - $collectionPersister = $this->getCollectionPersister($assoc); - - $collectionPersister->queuedCachedCollectionChange($entity, $value); - - $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; - } - foreach ($unwrappedValue as $key => $entry) { $state = $this->getEntityState($entry, self::STATE_NEW); if ( ! ($entry instanceof $assoc['targetEntity'])) { - throw new ORMException( - sprintf( - 'Found entity of type %s on association %s#%s, but expecting %s', - get_class($entry), - $assoc['sourceEntity'], - $assoc['fieldName'], - $targetClass->name - ) - ); + throw ORMException::unexpectedAssociationValue($assoc['sourceEntity'], $assoc['fieldName'], get_class($entry), $assoc['targetEntity']); } switch ($state) { @@ -986,6 +968,7 @@ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) */ private function executeInserts($class) { + $inserted = false; $entities = array(); $className = $class->name; $persister = $this->getEntityPersister($className); @@ -998,6 +981,8 @@ private function executeInserts($class) $persister->addInsert($entity); + $inserted = true; + unset($this->entityInsertions[$oid]); if ($invoke !== ListenersInvoker::INVOKE_NONE) { @@ -1027,7 +1012,7 @@ private function executeInserts($class) $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } - if ($this->hasCache && $persister->hasCache()) { + if ($this->hasCache && $inserted && $persister->hasCache()) { $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -1041,6 +1026,7 @@ private function executeInserts($class) */ private function executeUpdates($class) { + $updated = false; $className = $class->name; $persister = $this->getEntityPersister($className); $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate); @@ -1058,6 +1044,8 @@ private function executeUpdates($class) if ( ! empty($this->entityChangeSets[$oid])) { $persister->update($entity); + + $updated = true; } unset($this->entityUpdates[$oid]); @@ -1065,10 +1053,10 @@ private function executeUpdates($class) if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke); } + } - if ($this->hasCache && $persister->hasCache()) { - $this->cachedPersisters[spl_object_hash($persister)] = $persister; - } + if ($this->hasCache && $updated && $persister->hasCache()) { + $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -1081,6 +1069,7 @@ private function executeUpdates($class) */ private function executeDeletions($class) { + $deleted = false; $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove); @@ -1110,9 +1099,11 @@ private function executeDeletions($class) $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } - if ($this->hasCache && $persister->hasCache()) { - $this->cachedPersisters[spl_object_hash($persister)] = $persister; - } + $deleted = true; + } + + if ($this->hasCache && $deleted && $persister->hasCache()) { + $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php index 2778695b279..1c21b4305ff 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php @@ -33,10 +33,10 @@ public function testPutOnPersistJoinTableInheritance() $this->_em->clear(); - $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[0]->getId())); - $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[1]->getId())); - $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[2]->getId())); - $this->assertTrue($this->cache->containsEntity(AttractionContactInfo::CLASSNAME, $this->attractionsInfo[3]->getId())); + $this->assertTrue($this->cache->containsEntity(AttractionInfo::CLASSNAME, $this->attractionsInfo[0]->getId())); + $this->assertTrue($this->cache->containsEntity(AttractionInfo::CLASSNAME, $this->attractionsInfo[1]->getId())); + $this->assertTrue($this->cache->containsEntity(AttractionInfo::CLASSNAME, $this->attractionsInfo[2]->getId())); + $this->assertTrue($this->cache->containsEntity(AttractionInfo::CLASSNAME, $this->attractionsInfo[3]->getId())); } public function testJoinTableCountaisRootClass() diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php index 08dcb2faf15..c6eb014ebb4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -12,7 +12,7 @@ */ class SecondLevelCacheManyToManyTest extends SecondLevelCacheAbstractTest { - public function testPutManyToManyOnPersist() + public function testShouldPutManyToManyCollectionOwningSideOnPersist() { $this->loadFixturesCountries(); $this->loadFixturesStates(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 122be13ce8b..a96c53ee1d3 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -5,16 +5,13 @@ use Doctrine\Tests\Models\Cache\City; use Doctrine\Tests\Models\Cache\State; -use Doctrine\Tests\Models\Cache\Travel; -use Doctrine\Tests\Models\Cache\Country; -use Doctrine\Tests\Models\Cache\Traveler; /** * @group DDC-2183 */ class SecondLevelCacheOneToManyTest extends SecondLevelCacheAbstractTest { - public function testPutOnPersist() + public function testShouldNotPutCollectionInverseSideOnPersist() { $this->loadFixturesCountries(); $this->loadFixturesStates(); @@ -24,19 +21,8 @@ public function testPutOnPersist() $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); - $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); - $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId())); - - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(0)->getId())); - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(1)->getId())); - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(0)->getId())); - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->states[1]->getCities()->get(1)->getId())); - - $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); - $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(Country::CLASSNAME))); - $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); - $this->assertEquals(4, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(City::CLASSNAME))); - $this->assertEquals(12, $this->secondLevelCacheLogger->getPutCount()); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[1]->getId())); } public function testPutAndLoadOneToManyRelation() @@ -137,75 +123,15 @@ public function testPutAndLoadOneToManyRelation() $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } - public function testStoreOneToManyAssociationWhitCascade() - { - $this->cache->evictCollectionRegion(Traveler::CLASSNAME, 'travels'); - $this->cache->evictEntityRegion(Traveler::CLASSNAME); - $this->cache->evictEntityRegion(Travel::CLASSNAME); - - $traveler = new Traveler('Doctrine Bot'); - - $traveler->addTravel(new Travel($traveler)); - $traveler->addTravel(new Travel($traveler)); - $traveler->addTravel(new Travel($traveler)); - - $this->_em->persist($traveler); - $this->_em->flush(); - $this->_em->clear(); - - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getId())); - $this->assertTrue($this->cache->containsCollection(Traveler::CLASSNAME, 'travels', $traveler->getId())); - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(0)->getId())); - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(1)->getId())); - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(2)->getId())); - - $queryCount1 = $this->getCurrentQueryCount(); - $t1 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); - - $this->assertInstanceOf(Traveler::CLASSNAME, $t1); - $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(0)); - $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(1)); - $this->assertInstanceOf(Travel::CLASSNAME, $t1->getTravels()->get(2)); - $this->assertCount(3, $t1->getTravels()); - $this->assertNotSame($traveler, $t1); - - $this->assertEquals($queryCount1, $this->getCurrentQueryCount()); - - $t1->removeTravel($t1->getTravels()->get(1)); - - $this->_em->persist($t1); - $this->_em->flush($t1); - $this->_em->clear(); - - $queryCount2 = $this->getCurrentQueryCount(); - $t2 = $this->_em->find(Traveler::CLASSNAME, $traveler->getId()); - - $this->assertInstanceOf(Traveler::CLASSNAME, $t2); - $this->assertInstanceOf(Travel::CLASSNAME, $t2->getTravels()->get(0)); - $this->assertInstanceOf(Travel::CLASSNAME, $t2->getTravels()->get(2)); - $this->assertCount(2, $t1->getTravels()); - $this->assertNotSame($traveler, $t1); - - $this->assertEquals($queryCount2, $this->getCurrentQueryCount()); - - $this->assertCount(2, $t1->getTravels()); - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getId())); - $this->assertTrue($this->cache->containsCollection(Traveler::CLASSNAME, 'travels', $traveler->getId())); - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(0)->getId())); - $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(2)->getId())); - - $this->assertFalse($this->cache->containsEntity(Travel::CLASSNAME, $traveler->getTravels()->get(1)->getId())); - $this->assertNull($this->_em->find(Travel::CLASSNAME, $traveler->getTravels()->get(1)->getId())); - - $this->assertEquals($queryCount2 + 1, $this->getCurrentQueryCount()); - } - public function testLoadOnoToManyCollectionFromDatabaseWhenEntityMissing() { $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); $this->_em->clear(); + + //trigger lazy load from database + $this->assertCount(2, $this->_em->find(State::CLASSNAME, $this->states[0]->getId())->getCities()); $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); @@ -238,7 +164,7 @@ public function testLoadOnoToManyCollectionFromDatabaseWhenEntityMissing() } - public function testShoudNotPutOneToManyRelationOnPersistIfTheCollectionIsEmpty() + public function testShoudNotPutOneToManyRelationOnPersist() { $this->loadFixturesCountries(); $this->cache->evictEntityRegion(State::CLASSNAME); @@ -251,31 +177,5 @@ public function testShoudNotPutOneToManyRelationOnPersistIfTheCollectionIsEmpty( $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $state->getId())); $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $state->getId())); - - $state = $this->_em->find(State::CLASSNAME, $state); - $this->assertInstanceOf(State::CLASSNAME, $state); - - $city = new City("City Bar", $state); - - $state->addCity($city); - - $this->_em->persist($city); - $this->_em->persist($state); - $this->_em->flush(); - - $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $state->getId())); - $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $state->getId())); - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $city->getId())); - - $this->_em->clear(); - - $queryCount = $this->getCurrentQueryCount(); - $state = $this->_em->find(State::CLASSNAME, $state); - - $this->assertInstanceOf(State::CLASSNAME, $state); - $this->assertCount(1, $state->getCities()); - $this->assertInstanceOf(City::CLASSNAME, $state->getCities()->get(0)); - $this->assertEquals($city->getName(), $state->getCities()->get(0)->getName()); - $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index 19429d540b8..16f35cc7402 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -146,7 +146,7 @@ public function testQueryCacheFindAll() } } - public function testPutOneToManyRelationOnPersist() + public function testShouldNotPutOneToManyRelationOnPersist() { $this->loadFixturesCountries(); $this->loadFixturesStates(); @@ -157,7 +157,7 @@ public function testPutOneToManyRelationOnPersist() foreach ($this->cities as $city) { $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $city->getId())); - $this->assertTrue($this->cache->containsCollection(City::CLASSNAME, 'attractions', $city->getId())); + $this->assertFalse($this->cache->containsCollection(City::CLASSNAME, 'attractions', $city->getId())); } foreach ($this->attractions as $attraction) { From cb916c460eea9ae84e777a1ffd294a1597d10c7b Mon Sep 17 00:00:00 2001 From: fabios Date: Wed, 10 Jul 2013 17:21:00 -0400 Subject: [PATCH 50/71] Refactoring cache to use persister decorator --- lib/Doctrine/ORM/Cache/DefaultCache.php | 21 +- .../ORM/Cache/DefaultEntityEntryStructure.php | 2 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 3 +- .../AbstractCollectionPersister.php | 293 +--------- .../ORM/Persisters/BasicEntityPersister.php | 483 ++-------------- .../Persisters/CachedCollectionPersister.php | 361 ++++++++++++ .../ORM/Persisters/CachedEntityPersister.php | 522 ++++++++++++++++++ .../ORM/Persisters/CachedPersister.php | 6 - .../ORM/Persisters/CollectionPersister.php | 155 ++++++ .../ORM/Persisters/EntityPersister.php | 270 +++++++++ .../Persisters/JoinedSubclassPersister.php | 8 - lib/Doctrine/ORM/Proxy/ProxyFactory.php | 10 +- lib/Doctrine/ORM/UnitOfWork.php | 30 +- .../Cache/DefaultEntityEntryStructureTest.php | 1 + 14 files changed, 1399 insertions(+), 766 deletions(-) create mode 100644 lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php create mode 100644 lib/Doctrine/ORM/Persisters/CachedEntityPersister.php create mode 100644 lib/Doctrine/ORM/Persisters/CollectionPersister.php create mode 100644 lib/Doctrine/ORM/Persisters/EntityPersister.php diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 1252c8b28af..8efd17ce0af 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -26,6 +26,7 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Persisters\CachedPersister; use Doctrine\ORM\ORMInvalidArgumentException; /** @@ -79,7 +80,7 @@ public function getEntityCacheRegionAccess($className) $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return null; } @@ -94,7 +95,7 @@ public function getCollectionCacheRegionAccess($className, $association) $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return null; } @@ -110,7 +111,7 @@ public function containsEntity($className, $identifier) $persister = $this->uow->getEntityPersister($metadata->rootEntityName); $key = $this->buildEntityCacheKey($metadata, $identifier); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return false; } @@ -126,7 +127,7 @@ public function evictEntity($className, $identifier) $persister = $this->uow->getEntityPersister($metadata->rootEntityName); $key = $this->buildEntityCacheKey($metadata, $identifier); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return; } @@ -141,7 +142,7 @@ public function evictEntityRegion($className) $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return; } @@ -158,7 +159,7 @@ public function evictEntityRegions() foreach ($metadatas as $metadata) { $persister = $this->uow->getEntityPersister($metadata->rootEntityName); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { continue; } @@ -175,7 +176,7 @@ public function containsCollection($className, $association, $ownerIdentifier) $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return false; } @@ -191,7 +192,7 @@ public function evictCollection($className, $association, $ownerIdentifier) $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); $key = $this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return; } @@ -206,7 +207,7 @@ public function evictCollectionRegion($className, $association) $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { return; } @@ -229,7 +230,7 @@ public function evictCollectionRegions() $persister = $this->uow->getCollectionPersister($association); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { continue; } diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php index e99de2822be..33b3948d876 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -73,7 +73,7 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e continue; } - if (isset($assoc['cache']) && $assoc['type'] & ClassMetadata::TO_ONE && ! $data[$name] instanceof Proxy) { + if (isset($assoc['cache']) && $assoc['type'] & ClassMetadata::TO_ONE && ( ! $data[$name] instanceof Proxy || $data[$name]->__isInitialized__)) { $data[$name] = $this->uow->getEntityIdentifier($data[$name]); continue; diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 9f42844267f..3982f0c3355 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -21,6 +21,7 @@ namespace Doctrine\ORM\Cache; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\Persisters\CachedPersister; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; @@ -189,7 +190,7 @@ public function put(QueryCacheKey $key, AbstractQuery $query, array $result) $metadata = $this->em->getClassMetadata($entityName); $persister = $this->uow->getEntityPersister($entityName); - if ( ! $persister->hasCache()) { + if ( ! ($persister instanceof CachedPersister)) { throw CacheException::nonCacheableEntity($entityName); } diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index d90d2bc0745..dbbb737167b 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -20,11 +20,7 @@ namespace Doctrine\ORM\Persisters; use Doctrine\ORM\EntityManager; -use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\PersistentCollection; -use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\Cache\ConcurrentRegionAccess; /** * Base class for all collection persisters. @@ -32,7 +28,7 @@ * @since 2.0 * @author Roman Borschel */ -abstract class AbstractCollectionPersister implements CachedPersister +abstract class AbstractCollectionPersister implements CollectionPersister { /** * @var EntityManager @@ -78,36 +74,6 @@ abstract class AbstractCollectionPersister implements CachedPersister */ protected $association; - /** - * @var array - */ - protected $queuedCache = array(); - - /** - * @var boolean - */ - protected $hasCache = false; - - /** - * @var boolean - */ - protected $isConcurrentRegion = false; - - /** - * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess - */ - protected $cacheRegionAccess; - - /** - * @var \Doctrine\ORM\Cache\CollectionEntryStructure - */ - protected $cacheEntryStructure; - - /** - * @var \Doctrine\ORM\Cache\Logging\CacheLogger - */ - protected $cacheLogger; - /** * Initializes a new instance of a class derived from AbstractCollectionPersister. * @@ -120,44 +86,30 @@ public function __construct(EntityManager $em, array $association) $this->association = $association; $this->uow = $em->getUnitOfWork(); $this->conn = $em->getConnection(); - $configuration = $em->getConfiguration(); - $this->quoteStrategy = $configuration->getQuoteStrategy(); $this->platform = $this->conn->getDatabasePlatform(); + $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); $this->targetEntity = $em->getClassMetadata($association['targetEntity']); - $this->hasCache = isset($association['cache']) && $em->getConfiguration()->isSecondLevelCacheEnabled(); - - if ($this->hasCache) { - $cacheFactory = $configuration->getSecondLevelCacheFactory(); - $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); - $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($this->sourceEntity, $association['fieldName']); - $this->cacheEntryStructure = $cacheFactory->buildCollectionEntryStructure($em); - $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); - } } /** - * @return boolean + * {@inheritdoc} */ - public function hasCache() + public function getSourceEntityMetadata() { - return $this->hasCache; + return $this->sourceEntity; } /** - * @return \Doctrine\ORM\Cache\RegionAccess + * {@inheritdoc} */ - public function getCacheRegionAcess() + public function getTargetEntityMetadata() { - return $this->cacheRegionAccess; + return $this->targetEntity; } /** - * Deletes the persistent state represented by the given collection. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * - * @return void + * {@inheritdoc} */ public function delete(PersistentCollection $coll) { @@ -167,28 +119,7 @@ public function delete(PersistentCollection $coll) return; // ignore inverse side } - $cacheKey = null; - $cacheLock = null; - $sql = $this->getDeleteSQL($coll); - - if ($this->hasCache) { - $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); - $cacheKey = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - } - - if ($this->isConcurrentRegion) { - $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); - } - - $this->conn->executeUpdate($sql, $this->getDeleteSQLParameters($coll)); - - if ($this->hasCache) { - $this->queuedCache[] = array( - 'list' => null, - 'key' => $cacheKey, - 'lock' => $cacheLock - ); - } + $this->conn->executeUpdate($this->getDeleteSQL($coll), $this->getDeleteSQLParameters($coll)); } /** @@ -211,12 +142,7 @@ abstract protected function getDeleteSQL(PersistentCollection $coll); abstract protected function getDeleteSQLParameters(PersistentCollection $coll); /** - * Updates the given collection, synchronizing its state with the database - * by inserting, updating and deleting individual elements. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * - * @return void + * {@inheritdoc} */ public function update(PersistentCollection $coll) { @@ -226,36 +152,12 @@ public function update(PersistentCollection $coll) return; // ignore inverse side } - $cacheKey = null; - $cacheLock = null; - - if ($this->hasCache) { - $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); - $cacheKey = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - } - - if ($this->isConcurrentRegion) { - $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); - } - $this->deleteRows($coll); $this->insertRows($coll); - - if ($this->hasCache && $coll->isDirty()) { - $this->queuedCache[] = array( - 'list' => $coll, - 'key' => $cacheKey, - 'lock' => $cacheLock - ); - } } /** - * Deletes rows. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * - * @return void + * {@inheritdoc} */ public function deleteRows(PersistentCollection $coll) { @@ -268,11 +170,7 @@ public function deleteRows(PersistentCollection $coll) } /** - * Inserts rows. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * - * @return void + * {@inheritdoc} */ public function insertRows(PersistentCollection $coll) { @@ -285,115 +183,7 @@ public function insertRows(PersistentCollection $coll) } /** - * @param \Doctrine\ORM\PersistentCollection $collection - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key - * - * @return \Doctrine\ORM\PersistentCollection|null - */ - public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key) - { - - if (($cache = $this->cacheRegionAccess->get($key)) === null) { - return null; - } - - if (($cache = $this->cacheEntryStructure->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) { - return null; - } - - return $cache; - } - - /** - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key - * @param array|\Doctrine\Common\Collections\Collection $elements - * - * @return void - */ - public function saveCollectionCache(CollectionCacheKey $key, $elements) - { - $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); - $targetRegionAcess = $targetPersister->getCacheRegionAcess(); - $targetStructure = $targetPersister->getCacheEntryStructure(); - $targetRegion = $targetRegionAcess->getRegion(); - $entry = $this->cacheEntryStructure->buildCacheEntry($this->targetEntity, $key, $elements); - - foreach ($entry->identifiers as $index => $identifier) { - $entityKey = new EntityCacheKey($this->targetEntity->rootEntityName, $identifier); - - if ($targetRegion->contains($entityKey)) { - continue; - } - - $entity = $elements[$index]; - $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); - $entityEntry = $targetStructure->buildCacheEntry($class, $entityKey, $entity); - - $targetRegionAcess->put($entityKey, $entityEntry); - } - - $cached = $this->cacheRegionAccess->put($key, $entry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->collectionCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); - } - } - - /** - * Execute operations after transaction complete - * - * @return void - */ - public function afterTransactionComplete() - { - foreach ($this->queuedCache as $item) { - $list = $item['list']; - $key = $item['key']; - - if ($list === null) { - $this->cacheRegionAccess->evict($key); - - continue; - } - - $this->saveCollectionCache($key, $list); - - if ($item['lock'] !== null) { - $this->cacheRegionAccess->unlockItem($key, $item['lock']); - } - } - - $this->queuedCache = array(); - } - - /** - * Execute operations after transaction rollback - * - * @return void - */ - public function afterTransactionRolledBack() - { - if ( ! $this->isConcurrentRegion) { - $this->queuedCache = array(); - - return; - } - - foreach ($this->queuedCache as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - - $this->queuedCache = array(); - } - - /** - * Counts the size of this persistent collection. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * - * @return integer - * - * @throws \BadMethodCallException + * {@inheritdoc} */ public function count(PersistentCollection $coll) { @@ -401,15 +191,7 @@ public function count(PersistentCollection $coll) } /** - * Slices elements. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * @param integer $offset - * @param integer $length - * - * @return array - * - * @throws \BadMethodCallException + * {@inheritdoc} */ public function slice(PersistentCollection $coll, $offset, $length = null) { @@ -417,14 +199,7 @@ public function slice(PersistentCollection $coll, $offset, $length = null) } /** - * Checks for existence of an element. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * @param object $element - * - * @return boolean - * - * @throws \BadMethodCallException + * {@inheritdoc} */ public function contains(PersistentCollection $coll, $element) { @@ -432,14 +207,7 @@ public function contains(PersistentCollection $coll, $element) } /** - * Checks for existence of a key. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * @param mixed $key - * - * @return boolean - * - * @throws \BadMethodCallException + * {@inheritdoc} */ public function containsKey(PersistentCollection $coll, $key) { @@ -447,14 +215,7 @@ public function containsKey(PersistentCollection $coll, $key) } /** - * Removes an element. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * @param object $element - * - * @return mixed - * - * @throws \BadMethodCallException + * {@inheritdoc} */ public function removeElement(PersistentCollection $coll, $element) { @@ -462,14 +223,7 @@ public function removeElement(PersistentCollection $coll, $element) } /** - * Removes an element by key. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * @param mixed $key - * - * @return void - * - * @throws \BadMethodCallException + * {@inheritdoc} */ public function removeKey(PersistentCollection $coll, $key) { @@ -477,14 +231,7 @@ public function removeKey(PersistentCollection $coll, $key) } /** - * Gets an element by key. - * - * @param \Doctrine\ORM\PersistentCollection $coll - * @param mixed $index - * - * @return mixed - * - * @throws \BadMethodCallException + * {@inheritdoc} */ public function get(PersistentCollection $coll, $index) { diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 7553d6a0054..4982c7e9d3e 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -36,10 +36,6 @@ use Doctrine\Common\Collections\Criteria; use Doctrine\Common\Collections\Expr\Comparison; -use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\Cache\ConcurrentRegionAccess; - /** * A BasicEntityPersister maps an entity to a single table in a relational database. * @@ -82,7 +78,7 @@ * @author Fabio B. Silva * @since 2.0 */ -class BasicEntityPersister implements CachedPersister +class BasicEntityPersister implements EntityPersister { /** * @var array @@ -210,36 +206,6 @@ class BasicEntityPersister implements CachedPersister */ protected $quoteStrategy; - /** - * @var array - */ - protected $queuedCache = array(); - - /** - * @var boolean - */ - protected $hasCache = false; - - /** - * @var boolean - */ - private $isConcurrentRegion = false; - - /** - * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess - */ - protected $cacheRegionAccess; - - /** - * @var \Doctrine\ORM\Cache\EntityEntryStructure - */ - protected $cacheEntryStructure; - - /** - * @var \Doctrine\ORM\Cache\Logging\CacheLogger - */ - protected $cacheLogger; - /** * Initializes a new BasicEntityPersister that uses the given EntityManager * and persists instances of the class described by the given ClassMetadata descriptor. @@ -255,19 +221,10 @@ public function __construct(EntityManager $em, ClassMetadata $class) $configuration = $em->getConfiguration(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $configuration->getQuoteStrategy(); - $this->hasCache = ($class->cache !== null) && $em->getConfiguration()->isSecondLevelCacheEnabled(); - - if ($this->hasCache) { - $cacheFactory = $configuration->getSecondLevelCacheFactory(); - $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); - $this->cacheEntryStructure = $cacheFactory->buildEntityEntryStructure($em); - $this->cacheRegionAccess = $cacheFactory->buildEntityRegionAccessStrategy($this->class); - $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); - } } - /** - * @return \Doctrine\ORM\Mapping\ClassMetadata + /** + * {@inheritdoc} */ public function getClassMetadata() { @@ -275,50 +232,23 @@ public function getClassMetadata() } /** - * @return boolean - */ - public function hasCache() - { - return $this->hasCache; - } - - /** - * @return \Doctrine\ORM\Cache\RegionAccess + * {@inheritdoc} */ - public function getCacheRegionAcess() - { - return $this->cacheRegionAccess; - } - - /** - * @return \Doctrine\ORM\Cache\EntityEntryStructure - */ - public function getCacheEntryStructure() + public function addInsert($entity) { - return $this->cacheEntryStructure; + $this->queuedInserts[spl_object_hash($entity)] = $entity; } /** - * Adds an entity to the queued insertions. - * The entity remains queued until {@link executeInserts} is invoked. - * - * @param object $entity The entity to queue for insertion. - * - * @return void + * {@inheritdoc} */ - public function addInsert($entity) + public function getInserts() { - $this->queuedInserts[spl_object_hash($entity)] = $entity; + return $this->queuedInserts; } /** - * Executes all queued entity insertions and returns any generated post-insert - * identifiers that were created as a result of the insertions. - * - * If no inserts are queued, invoking this method is a NOOP. - * - * @return array An array of any generated post-insert IDs. This will be an empty array - * if the entity class does not use the IDENTITY generation strategy. + * {@inheritdoc} */ public function executeInserts() { @@ -356,14 +286,6 @@ public function executeInserts() if ($this->class->isVersioned) { $this->assignDefaultVersionValue($entity, $id); } - - if ($this->hasCache) { - $this->queuedCache['insert'][] = array( - 'entity' => $entity, - 'lock' => null, - 'key' => null - ); - } } $stmt->closeCursor(); @@ -415,25 +337,10 @@ protected function fetchVersionValue($versionedClass, $id) } /** - * Updates a managed entity. The entity is updated according to its current changeset - * in the running UnitOfWork. If there is no changeset, nothing is updated. - * - * The data to update is retrieved through {@link prepareUpdateData}. - * Subclasses that override this method are supposed to obtain the update data - * in the same way, through {@link prepareUpdateData}. - * - * Subclasses are also supposed to take care of versioning when overriding this method, - * if necessary. The {@link updateTable} method can be used to apply the data retrieved - * from {@prepareUpdateData} on the target tables, thereby optionally applying versioning. - * - * @param object $entity The entity to update. - * - * @return void + * {@inheritdoc} */ public function update($entity) { - $cacheKey = null; - $cacheLock = null; $tableName = $this->class->getTableName(); $updateData = $this->prepareUpdateData($entity); @@ -441,11 +348,6 @@ public function update($entity) return; } - if ($this->isConcurrentRegion) { - $cacheKey = new EntityCacheKey($this->class->rootEntityName, $this->em->getUnitOfWork()->getEntityIdentifier($entity)); - $cacheLock = $this->cacheRegionAccess->lockItem($cacheKey); - } - $isVersioned = $this->class->isVersioned; $quotedTableName = $this->quoteStrategy->getTableName($this->class, $this->platform); @@ -456,14 +358,6 @@ public function update($entity) $this->assignDefaultVersionValue($entity, $id); } - - if ($this->hasCache && ( ! $this->isConcurrentRegion || $cacheLock !== null)) { - $this->queuedCache['update'][] = array( - 'entity' => $entity, - 'lock' => $cacheLock, - 'key' => $cacheKey - ); - } } /** @@ -640,16 +534,7 @@ protected function deleteJoinTableRecords($identifier) } /** - * Deletes a managed entity. - * - * The entity to delete must be managed and have a persistent identifier. - * The deletion happens instantaneously. - * - * Subclasses may override this method to customize the semantics of entity deletion. - * - * @param object $entity The entity to delete. - * - * @return void + * {@inheritdoc} */ public function delete($entity) { @@ -660,7 +545,7 @@ public function delete($entity) $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); $id = array_combine($idColumns, $identifier); $types = array_map(function ($identifier) use ($class, $em) { - if (isset($class->fieldMappings[$identifier])) { + if (isset($class->fieldMappings[$identifier])) { return $class->fieldMappings[$identifier]['type']; } @@ -691,14 +576,6 @@ public function delete($entity) $this->deleteJoinTableRecords($identifier); $this->conn->delete($tableName, $id, $types); - - if ($this->hasCache) { - $this->queuedCache['delete'][] = array( - 'lock' => $cacheLock, - 'key' => $cacheKey, - 'entity' => null, - ); - } } /** @@ -821,15 +698,7 @@ protected function prepareInsertData($entity) } /** - * Gets the name of the table that owns the column the given field is mapped to. - * - * The default implementation in BasicEntityPersister always returns the name - * of the table the entity type of this persister is mapped to, since an entity - * is always persisted to a single table with a BasicEntityPersister. - * - * @param string $fieldName The field name. - * - * @return string The table name. + * {@inheritdoc} */ public function getOwningTable($fieldName) { @@ -837,19 +706,7 @@ public function getOwningTable($fieldName) } /** - * Loads an entity by a list of field criteria. - * - * @param array $criteria The criteria by which to load the entity. - * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. - * @param array|null $assoc The association that connects the entity to load to another entity, if any. - * @param array $hints Hints for entity creation. - * @param int $lockMode - * @param int|null $limit Limit number of results. - * @param array|null $orderBy Criteria to order by. - * - * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. - * - * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? + * {@inheritdoc} */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null) { @@ -869,65 +726,15 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint } /** - * Loads an entity by identifier. - * - * @param array $identifier The entity identifier. - * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. - * - * @return object The loaded and managed entity instance or NULL if the entity can not be found. - * - * @todo Check parameters + * {@inheritdoc} */ public function loadById(array $identifier, $entity = null) { - $cacheKey = null; - - if ($this->hasCache) { - $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); - $cacheEntry = $this->cacheRegionAccess->get($cacheKey); - - if ($cacheEntry !== null && ($entity = $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity)) !== null) { - - if ($this->cacheLogger) { - $this->cacheLogger->entityCacheHit($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); - } - - return $entity; - } - } - - $entity = $this->load($identifier, $entity); - - if ($this->hasCache && $entity !== null) { - $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); - $cacheEntry = $this->cacheEntryStructure->buildCacheEntry($class, $cacheKey, $entity); - $cached = $this->cacheRegionAccess->put($cacheKey, $cacheEntry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); - } - - if ($this->cacheLogger) { - $this->cacheLogger->entityCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); - } - } - - return $entity; + return $this->load($identifier, $entity);; } /** - * Loads an entity of this persister's mapped class as part of a single-valued - * association from another entity. - * - * @param array $assoc The association to load. - * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). - * @param array $identifier The identifier of the entity to load. Must be provided if - * the association to load represents the owning side, otherwise - * the identifier is derived from the $sourceEntity. - * - * @return object The loaded and managed entity instance or NULL if the entity can not be found. - * - * @throws \Doctrine\ORM\Mapping\MappingException + * {@inheritdoc} */ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array()) { @@ -993,14 +800,7 @@ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifie } /** - * Refreshes a managed entity. - * - * @param array $id The identifier of the entity as an associative array from - * column or field names to values. - * @param object $entity The entity to refresh. - * @param int $lockMode - * - * @return void + * {@inheritdoc} */ public function refresh(array $id, $entity, $lockMode = 0) { @@ -1013,11 +813,7 @@ public function refresh(array $id, $entity, $lockMode = 0) } /** - * Loads Entities matching the given Criteria object. - * - * @param \Doctrine\Common\Collections\Criteria $criteria - * - * @return array + * {@inheritdoc} */ public function loadCriteria(Criteria $criteria) { @@ -1071,14 +867,7 @@ private function expandCriteriaParameters(Criteria $criteria) } /** - * Loads a list of entities by a list of field criteria. - * - * @param array $criteria - * @param array|null $orderBy - * @param int|null $limit - * @param int|null $offset - * - * @return array + * {@inheritdoc} */ public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null) { @@ -1092,14 +881,7 @@ public function loadAll(array $criteria = array(), array $orderBy = null, $limit } /** - * Gets (sliced or full) elements of the given collection. - * - * @param array $assoc - * @param object $sourceEntity - * @param int|null $offset - * @param int|null $limit - * - * @return array + * {@inheritdoc} */ public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { @@ -1155,51 +937,13 @@ private function loadCollectionFromStatement($assoc, $stmt, $coll) } /** - * Loads a collection of entities of a many-to-many association. - * - * @param array $assoc The association mapping of the association being loaded. - * @param object $sourceEntity The entity that owns the collection. - * @param PersistentCollection $coll The collection to fill. - * - * @return array + * {@inheritdoc} */ public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { - $hasCache = false; - $key = null; - $persister = null; - - if ($this->hasCache) { - $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); - $hasCache = $persister->hasCache(); - - if ($hasCache) { - $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); - $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $persister->loadCollectionCache($coll, $key); - - if ($list !== null) { - - if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); - } - - return $list; - } - } - } - $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); - if ($hasCache && ! empty($list)) { - $persister->saveCollectionCache($key, $list); - - if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); - } - } - return $list; } @@ -1579,11 +1323,9 @@ protected function getSelectManyToManyJoinSQL(array $manyToMany) } /** - * Gets the INSERT SQL used by the persister to persist a new entity. - * - * @return string + * {@inheritdoc} */ - protected function getInsertSQL() + public function getInsertSQL() { if ($this->insertSql !== null) { return $this->insertSql; @@ -1717,12 +1459,7 @@ protected function getSQLTableAlias($className, $assocName = '') } /** - * Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode. - * - * @param array $criteria - * @param int $lockMode - * - * @return void + * {@inheritdoc} */ public function lock(array $criteria, $lockMode) { @@ -1785,14 +1522,7 @@ protected function getSelectConditionCriteriaSQL(Criteria $criteria) } /** - * Gets the SQL WHERE condition for matching a field with a given value. - * - * @param string $field - * @param mixed $value - * @param array|null $assoc - * @param string|null $comparison - * - * @return string + * {@inheritdoc} */ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null) { @@ -1895,14 +1625,7 @@ protected function getSelectConditionSQL(array $criteria, $assoc = null) } /** - * Returns an array with (sliced or full list) of elements in the specified collection. - * - * @param array $assoc - * @param object $sourceEntity - * @param int|null $offset - * @param int|null $limit - * - * @return array + * {@inheritdoc} */ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) { @@ -1912,51 +1635,13 @@ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = nu } /** - * Loads a collection of entities in a one-to-many association. - * - * @param array $assoc - * @param object $sourceEntity - * @param PersistentCollection $coll The collection to load/fill. - * - * @return array + * {@inheritdoc} */ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { - $hasCache = false; - $key = null; - $persister = null; - - if ($this->hasCache) { - $persister = $this->em->getUnitOfWork()->getCollectionPersister($assoc); - $hasCache = $persister->hasCache(); - - if ($hasCache) { - $ownerId = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); - $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); - $list = $persister->loadCollectionCache($coll, $key); - - if ($list !== null) { - - if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); - } - - return $list; - } - } - } - $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); - if ($hasCache && ! empty($list)) { - $persister->saveCollectionCache($key, $list); - - if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); - } - } - return $list; } @@ -2111,12 +1796,7 @@ private function getIndividualValue($value) } /** - * Checks whether the given managed entity exists in the database. - * - * @param object $entity - * @param array $extraConditions - * - * @return boolean TRUE if the entity exists in the database, FALSE otherwise. + * {@inheritdoc} */ public function exists($entity, array $extraConditions = array()) { @@ -2165,11 +1845,7 @@ protected function getJoinSQLForJoinColumns($joinColumns) } /** - * Gets an SQL column alias for a column name. - * - * @param string $columnName - * - * @return string + * {@inheritdoc} */ public function getSQLColumnAlias($columnName) { @@ -2197,101 +1873,4 @@ protected function generateFilterConditionSQL(ClassMetadata $targetEntity, $targ $sql = implode(' AND ', $filterClauses); return $sql ? "(" . $sql . ")" : ""; // Wrap again to avoid "X or Y and FilterConditionSQL" } - - /** - * @param object $entity - * @param \Doctrine\ORM\Cache\EntityCacheKey $key - * - * @return boolean - */ - public function putEntityCache($entity, EntityCacheKey $key) - { - $class = $this->em->getClassMetadata(ClassUtils::getClass($entity)); - $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $entity); - $cached = $this->cacheRegionAccess->put($key, $entry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); - } - - return $cached; - } - - /** - * Execute operations after transaction complete - * - * @return void - */ - public function afterTransactionComplete() - { - $uow = $this->em->getUnitOfWork(); - - if (isset($this->queuedCache['insert'])) { - foreach ($this->queuedCache['insert'] as $item) { - - $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); - $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); - $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); - $cached = $this->cacheRegionAccess->afterInsert($key, $entry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); - } - } - } - - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - - $class = $this->em->getClassMetadata(ClassUtils::getClass($item['entity'])); - $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $uow->getEntityIdentifier($item['entity'])); - $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); - $cached = $this->cacheRegionAccess->afterUpdate($key, $entry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); - } - - if ($item['lock'] !== null) { - $this->cacheRegionAccess->unlockItem($key, $item['lock']); - } - } - } - - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { - $this->cacheRegionAccess->evict($item['key']); - } - } - - $this->queuedCache = array(); - } - - /** - * Execute operations after transaction rollback - * - * @return void - */ - public function afterTransactionRolledBack() - { - if ( ! $this->isConcurrentRegion) { - $this->queuedCache = array(); - - return; - } - - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - $this->queuedCache = array(); - } } diff --git a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php new file mode 100644 index 00000000000..e204e8b22df --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php @@ -0,0 +1,361 @@ +. + */ + +namespace Doctrine\ORM\Persisters; + +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\ConcurrentRegionAccess; + +use Doctrine\Common\Util\ClassUtils; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\EntityManagerInterface; + +/** + * @author Fabio B. Silva + * @since 2.5 + */ +class CachedCollectionPersister implements CachedPersister, CollectionPersister +{ + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadataFactory + */ + private $metadataFactory; + + /** + * @var \Doctrine\ORM\Persisters\CollectionPersister + */ + private $persister; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + protected $sourceEntity; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + protected $targetEntity; + + /** + * @var array + */ + protected $association; + + /** + * @var array + */ + protected $queuedCache = array(); + + /** + * @var boolean + */ + private $isConcurrentRegion = false; + + /** + * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess + */ + protected $cacheRegionAccess; + + /** + * @var \Doctrine\ORM\Cache\CollectionEntryStructure + */ + protected $cacheEntryStructure; + + /** + * @var \Doctrine\ORM\Cache\Logging\CacheLogger + */ + protected $cacheLogger; + + public function __construct(CollectionPersister $persister, EntityManagerInterface $em, array $association) + { + $configuration = $em->getConfiguration(); + $cacheFactory = $configuration->getSecondLevelCacheFactory(); + + $this->persister = $persister; + $this->association = $association; + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->sourceEntity = $persister->getSourceEntityMetadata(); + $this->targetEntity = $persister->getTargetEntityMetadata(); + $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); + $this->cacheEntryStructure = $cacheFactory->buildCollectionEntryStructure($em); + $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); + $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($this->sourceEntity, $association['fieldName']); + } + + /** + * {@inheritdoc} + */ + public function getCacheRegionAcess() + { + return $this->cacheRegionAccess; + } + + /** + * {@inheritdoc} + */ + public function getSourceEntityMetadata() + { + return $this->sourceEntity; + } + + /** + * {@inheritdoc} + */ + public function getTargetEntityMetadata() + { + return $this->targetEntity; + } + + /** + * {@inheritdoc} + */ + public function afterTransactionComplete() + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + + $this->saveCollectionCache($item['key'], $item['list'], $item['lock']); + + if ($item['lock'] !== null) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->cacheRegionAccess->evict($item['key']); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionRolledBack() + { + if ( ! $this->isConcurrentRegion) { + $this->queuedCache = array(); + + return; + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + $this->queuedCache = array(); + } + + /** + * @param \Doctrine\ORM\PersistentCollection $collection + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * + * @return \Doctrine\ORM\PersistentCollection|null + */ + public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key) + { + + if (($cache = $this->cacheRegionAccess->get($key)) === null) { + return null; + } + + if (($cache = $this->cacheEntryStructure->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) { + return null; + } + + return $cache; + } + + /** + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * @param array|\Doctrine\Common\Collections\Collection $elements + * + * @return void + */ + public function saveCollectionCache(CollectionCacheKey $key, $elements) + { + $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); + $targetRegionAcess = $targetPersister->getCacheRegionAcess(); + $targetStructure = $targetPersister->getCacheEntryStructure(); + $targetRegion = $targetRegionAcess->getRegion(); + $entry = $this->cacheEntryStructure->buildCacheEntry($this->targetEntity, $key, $elements); + + foreach ($entry->identifiers as $index => $identifier) { + $entityKey = new EntityCacheKey($this->targetEntity->rootEntityName, $identifier); + + + if ($targetRegion->contains($entityKey)) { + continue; + } + + $class = $this->targetEntity; + $className = ClassUtils::getClass($elements[$index]); + + if ($className !== $this->targetEntity->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $entity = $elements[$index]; + $entityEntry = $targetStructure->buildCacheEntry($class, $entityKey, $entity); + + $targetRegionAcess->put($entityKey, $entityEntry); + } + + $cached = $this->cacheRegionAccess->put($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->collectionCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } + } + + /** + * {@inheritdoc} + */ + public function contains(PersistentCollection $collection, $element) + { + return $this->persister->contains($collection, $element); + } + + /** + * {@inheritdoc} + */ + public function containsKey(PersistentCollection $collection, $key) + { + return $this->persister->containsKey($collection, $key); + } + + /** + * {@inheritdoc} + */ + public function count(PersistentCollection $collection) + { + return $this->persister->count($collection); + } + + /** + * {@inheritdoc} + */ + public function delete(PersistentCollection $collection) + { + $lock = null; + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + + if ($this->isConcurrentRegion) { + $lock = $this->cacheRegionAccess->lockItem($key); + } + + $this->persister->delete($collection); + + $this->queuedCache['delete'][] = array( + 'list' => null, + 'key' => $key, + 'lock' => $lock + ); + } + + /** + * {@inheritdoc} + */ + public function update(PersistentCollection $collection) + { + $lock = null; + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + + if ($this->isConcurrentRegion) { + $lock = $this->cacheRegionAccess->lockItem($key); + } + + $this->persister->update($collection); + + $this->queuedCache['update'][] = array( + 'list' => $collection, + 'key' => $key, + 'lock' => $lock + ); + } + + /** + * {@inheritdoc} + */ + public function deleteRows(PersistentCollection $collection) + { + $this->persister->deleteRows($collection); + } + + /** + * {@inheritdoc} + */ + public function insertRows(PersistentCollection $collection) + { + $this->persister->insertRows($collection); + } + + /** + * {@inheritdoc} + */ + public function get(PersistentCollection $collection, $index) + { + return $this->persister->get($collection, $index); + } + + /** + * {@inheritdoc} + */ + public function removeElement(PersistentCollection $collection, $element) + { + return $this->persister->removeElement($collection, $element); + } + + /** + * {@inheritdoc} + */ + public function removeKey(PersistentCollection $collection, $key) + { + return $this->persister->removeKey($collection, $key); + } + + /** + * {@inheritdoc} + */ + public function slice(PersistentCollection $collection, $offset, $length = null) + { + return $this->persister->slice($collection, $offset, $length); + } +} diff --git a/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php b/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php new file mode 100644 index 00000000000..3baf4354829 --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php @@ -0,0 +1,522 @@ +. + */ + +namespace Doctrine\ORM\Persisters; + +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\ConcurrentRegionAccess; + +use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Collections\Criteria; + +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\EntityManagerInterface; + +/** + * @author Fabio B. Silva + * @since 2.5 + */ +class CachedEntityPersister implements CachedPersister, EntityPersister +{ + /** + * @var \Doctrine\ORM\UnitOfWork + */ + private $uow; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadataFactory + */ + private $metadataFactory; + + /** + * @var \Doctrine\ORM\Persisters\EntityPersister + */ + private $persister; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + protected $class; + + /** + * @var array + */ + protected $queuedCache = array(); + + /** + * @var boolean + */ + private $isConcurrentRegion = false; + + /** + * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess + */ + protected $cacheRegionAccess; + + /** + * @var \Doctrine\ORM\Cache\EntityEntryStructure + */ + protected $cacheEntryStructure; + + /** + * @var \Doctrine\ORM\Cache\Logging\CacheLogger + */ + protected $cacheLogger; + + public function __construct(EntityPersister $persister, EntityManagerInterface $em, ClassMetadata $class) + { + $config = $em->getConfiguration(); + $factory = $config->getSecondLevelCacheFactory(); + + $this->class = $class; + $this->persister = $persister; + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->cacheLogger = $config->getSecondLevelCacheLogger(); + $this->cacheEntryStructure = $factory->buildEntityEntryStructure($em); + $this->cacheRegionAccess = $factory->buildEntityRegionAccessStrategy($this->class); + $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); + } + + /** + * {@inheritdoc} + */ + public function addInsert($entity) + { + $this->persister->addInsert($entity); + } + + /** + * {@inheritdoc} + */ + public function getInserts() + { + return $this->persister->getInserts(); + } + + /** + * {@inheritdoc} + */ + public function getInsertSQL() + { + return $this->persister->getInsertSQL(); + } + + /** + * {@inheritdoc} + */ + public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null) + { + return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionComplete() + { + if (isset($this->queuedCache['insert'])) { + foreach ($this->queuedCache['insert'] as $item) { + + $class = $this->class; + $className = ClassUtils::getClass($item['entity']); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($item['entity'])); + $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); + $cached = $this->cacheRegionAccess->afterInsert($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } + } + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + + $class = $this->class; + $className = ClassUtils::getClass($item['entity']); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($item['entity'])); + $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); + $cached = $this->cacheRegionAccess->afterUpdate($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } + + if ($item['lock'] !== null) { + $this->cacheRegionAccess->unlockItem($key, $item['lock']); + } + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->cacheRegionAccess->evict($item['key']); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionRolledBack() + { + if ( ! $this->isConcurrentRegion) { + $this->queuedCache = array(); + + return; + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function delete($entity) + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = null; + + if ($this->isConcurrentRegion) { + $lock = $this->cacheRegionAccess->lockItem($key); + } + + $this->persister->delete($entity); + + $this->queuedCache['delete'][] = array( + 'entity' => $entity, + 'lock' => $lock, + 'key' => $key + ); + } + + /** + * {@inheritdoc} + */ + public function update($entity) + { + $key = null; + $lock = null; + + if ($this->isConcurrentRegion) { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = $this->cacheRegionAccess->lockItem($key); + } + + $this->persister->update($entity); + + $this->queuedCache['update'][] = array( + 'entity' => $entity, + 'lock' => $lock, + 'key' => $key + ); + } + + /** + * {@inheritdoc} + */ + public function executeInserts() + { + foreach ($this->persister->getInserts() as $entity) { + $this->queuedCache['insert'][] = array( + 'entity' => $entity, + 'lock' => null, + 'key' => null + ); + } + + return $this->persister->executeInserts(); + } + + /** + * {@inheritdoc} + */ + public function exists($entity, array $extraConditions = array()) + { + if (empty($extraConditions)) { + + $region = $this->cacheRegionAccess->getRegion(); + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + + if ($region->contains($key)) { + return true; + } + } + + return $this->persister->exists($entity, $extraConditions); + } + + /** + * {@inheritdoc} + */ + public function getCacheRegionAcess() + { + return $this->cacheRegionAccess; + } + + public function getCacheEntryStructure() + { + return $this->cacheEntryStructure; + } + + public function putEntityCache($entity, EntityCacheKey $key) + { + $class = $this->class; + $className = ClassUtils::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $entity); + $cached = $this->cacheRegionAccess->put($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + } + + return $cached; + } + + /** + * {@inheritdoc} + */ + public function getClassMetadata() + { + return $this->persister->getClassMetadata(); + } + + /** + * {@inheritdoc} + */ + public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) + { + return $this->persister->getManyToManyCollection($assoc, $sourceEntity, $offset, $limit); + } + + /** + * {@inheritdoc} + */ + public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null) + { + return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit); + } + + /** + * {@inheritdoc} + */ + public function getOwningTable($fieldName) + { + return $this->persister->getOwningTable($fieldName); + } + + /** + * {@inheritdoc} + */ + public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null) + { + return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + } + + /** + * {@inheritdoc} + */ + public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null) + { + return $this->persister->loadAll($criteria, $orderBy, $limit, $offset); + } + + /** + * {@inheritdoc} + */ + public function loadById(array $identifier, $entity = null) + { + $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); + $cacheEntry = $this->cacheRegionAccess->get($cacheKey); + + if ($cacheEntry !== null && ($entity = $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity)) !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->entityCacheHit($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } + + return $entity; + } + + $entity = $this->persister->loadById($identifier, $entity); + + if ($entity === null) { + return null; + } + + $class = $this->class; + $className = ClassUtils::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $cacheEntry = $this->cacheEntryStructure->buildCacheEntry($class, $cacheKey, $entity); + $cached = $this->cacheRegionAccess->put($cacheKey, $cacheEntry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } + + if ($this->cacheLogger) { + $this->cacheLogger->entityCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } + + return $entity; + } + + /** + * {@inheritdoc} + */ + public function loadCriteria(Criteria $criteria) + { + return $this->persister->loadCriteria($criteria); + } + + /** + * {@inheritdoc} + */ + public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) + { + $persister = $this->uow->getCollectionPersister($assoc); + $hasCache = ($persister instanceof CachedPersister); + $key = null; + + if ($hasCache) { + $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); + $list = $persister->loadCollectionCache($coll, $key); + + if ($list !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + } + + return $list; + } + } + + $list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll); + + if ($hasCache && ! empty($list)) { + $persister->saveCollectionCache($key, $list); + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + } + } + + return $list; + } + + /** + * {@inheritdoc} + */ + public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) + { + $persister = $this->uow->getCollectionPersister($assoc); + $hasCache = ($persister instanceof CachedPersister); + + if ($hasCache) { + $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); + $key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId); + $list = $persister->loadCollectionCache($coll, $key); + + if ($list !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + } + + return $list; + } + } + + $list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll); + + if ($hasCache && ! empty($list)) { + $persister->saveCollectionCache($key, $list); + + if ($this->cacheLogger) { + $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + } + } + + return $list; + } + + /** + * {@inheritdoc} + */ + public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array()) + { + return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier); + } + + /** + * {@inheritdoc} + */ + public function lock(array $criteria, $lockMode) + { + $this->persister->lock($criteria, $lockMode); + } + + /** + * {@inheritdoc} + */ + public function refresh(array $id, $entity, $lockMode = 0) + { + $this->persister->refresh($id, $entity, $lockMode); + } + +} diff --git a/lib/Doctrine/ORM/Persisters/CachedPersister.php b/lib/Doctrine/ORM/Persisters/CachedPersister.php index f3742c8a06b..863ad66240c 100644 --- a/lib/Doctrine/ORM/Persisters/CachedPersister.php +++ b/lib/Doctrine/ORM/Persisters/CachedPersister.php @@ -43,10 +43,4 @@ public function afterTransactionRolledBack(); */ public function getCacheRegionAcess(); - /** - * Returns TRUE if the persists is cacheable, FALSE otherwise - * - * @return boolean - */ - public function hasCache(); } diff --git a/lib/Doctrine/ORM/Persisters/CollectionPersister.php b/lib/Doctrine/ORM/Persisters/CollectionPersister.php new file mode 100644 index 00000000000..174e0fb127f --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/CollectionPersister.php @@ -0,0 +1,155 @@ +. + */ + +namespace Doctrine\ORM\Persisters; + +use Doctrine\ORM\PersistentCollection; + +/** + * Collection persister interface + * Define the behavior that should be implemented by all collection persisters. + * + * @author Fabio B. Silva + * @since 2.5 + */ +interface CollectionPersister +{ + /** + * The class name of the source entity. + * + * @return \Doctrine\ORM\Mapping\ClassMetadata + */ + public function getSourceEntityMetadata(); + + /** + * The class name of the target entity. + * + * @return \Doctrine\ORM\Mapping\ClassMetadata + */ + public function getTargetEntityMetadata(); + + /** + * Deletes the persistent state represented by the given collection. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * + * @return void + */ + public function delete(PersistentCollection $collection); + + /** + * Updates the given collection, synchronizing its state with the database + * by inserting, updating and deleting individual elements. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * + * @return void + */ + public function update(PersistentCollection $collection); + + /** + * Deletes rows. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * + * @return void + */ + public function deleteRows(PersistentCollection $collection); + + /** + * Inserts rows. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * + * @return void + */ + public function insertRows(PersistentCollection $collection); + + /** + * Counts the size of this persistent collection. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * + * @return integer + * + * @throws \BadMethodCallException + */ + public function count(PersistentCollection $collection); + + /** + * Slices elements. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * @param integer $offset + * @param integer $length + * + * @return array + */ + public function slice(PersistentCollection $collection, $offset, $length = null); + + /** + * Checks for existence of an element. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * @param object $element + * + * @return boolean + */ + public function contains(PersistentCollection $collection, $element); + + /** + * Checks for existence of a key. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * @param mixed $key + * + * @return boolean + */ + public function containsKey(PersistentCollection $collection, $key); + + /** + * Removes an element. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * @param object $element + * + * @return mixed + */ + public function removeElement(PersistentCollection $collection, $element); + + /** + * Removes an element by key. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * @param mixed $key + * + * @return void + */ + public function removeKey(PersistentCollection $collection, $key); + + /** + * Gets an element by key. + * + * @param \Doctrine\ORM\PersistentCollection $collection + * @param mixed $index + * + * @return mixed + */ + public function get(PersistentCollection $collection, $index); +} diff --git a/lib/Doctrine/ORM/Persisters/EntityPersister.php b/lib/Doctrine/ORM/Persisters/EntityPersister.php new file mode 100644 index 00000000000..169130a0796 --- /dev/null +++ b/lib/Doctrine/ORM/Persisters/EntityPersister.php @@ -0,0 +1,270 @@ +. + */ + +namespace Doctrine\ORM\Persisters; + +use Doctrine\ORM\PersistentCollection; +use Doctrine\Common\Collections\Criteria; + + +/** + * Entity persister interface + * Define the behavior that should be implemented by all entity persisters. + * + * @author Fabio B. Silva + * @since 2.5 + */ +interface EntityPersister +{ + /** + * @return \Doctrine\ORM\Mapping\ClassMetadata + */ + public function getClassMetadata(); + + /** + * Get all queued inserts. + * + * @return array + */ + public function getInserts(); + + /** + * @TODO - It should not be here. + * But its necessary since JoinedSubclassPersister#executeInserts invoke the root persister. + * + * Gets the INSERT SQL used by the persister to persist a new entity. + * + * @return string + */ + public function getInsertSQL(); + + /** + * Gets the SQL WHERE condition for matching a field with a given value. + * + * @param string $field + * @param mixed $value + * @param array|null $assoc + * @param string|null $comparison + * + * @return string + */ + public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null); + + /** + * Adds an entity to the queued insertions. + * The entity remains queued until {@link executeInserts} is invoked. + * + * @param object $entity The entity to queue for insertion. + * + * @return void + */ + public function addInsert($entity); + + /** + * Executes all queued entity insertions and returns any generated post-insert + * identifiers that were created as a result of the insertions. + * + * If no inserts are queued, invoking this method is a NOOP. + * + * @return array An array of any generated post-insert IDs. This will be an empty array + * if the entity class does not use the IDENTITY generation strategy. + */ + public function executeInserts(); + + /** + * Updates a managed entity. The entity is updated according to its current changeset + * in the running UnitOfWork. If there is no changeset, nothing is updated. + * + * @param object $entity The entity to update. + * + * @return void + */ + public function update($entity); + + /** + * Deletes a managed entity. + * + * The entity to delete must be managed and have a persistent identifier. + * The deletion happens instantaneously. + * + * Subclasses may override this method to customize the semantics of entity deletion. + * + * @param object $entity The entity to delete. + * + * @return void + */ + public function delete($entity); + + /** + * Gets the name of the table that owns the column the given field is mapped to. + * + * The default implementation in BasicEntityPersister always returns the name + * of the table the entity type of this persister is mapped to, since an entity + * is always persisted to a single table with a BasicEntityPersister. + * + * @param string $fieldName The field name. + * + * @return string The table name. + */ + public function getOwningTable($fieldName); + + /** + * Loads an entity by a list of field criteria. + * + * @param array $criteria The criteria by which to load the entity. + * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. + * @param array|null $assoc The association that connects the entity to load to another entity, if any. + * @param array $hints Hints for entity creation. + * @param int $lockMode + * @param int|null $limit Limit number of results. + * @param array|null $orderBy Criteria to order by. + * + * @return object|null The loaded and managed entity instance or NULL if the entity can not be found. + * + * @todo Check identity map? loadById method? Try to guess whether $criteria is the id? + */ + public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null); + + /** + * Loads an entity by identifier. + * + * @param array $identifier The entity identifier. + * @param object|null $entity The entity to load the data into. If not specified, a new entity is created. + * + * @return object The loaded and managed entity instance or NULL if the entity can not be found. + * + * @todo Check parameters + */ + public function loadById(array $identifier, $entity = null); + + /** + * Loads an entity of this persister's mapped class as part of a single-valued + * association from another entity. + * + * @param array $assoc The association to load. + * @param object $sourceEntity The entity that owns the association (not necessarily the "owning side"). + * @param array $identifier The identifier of the entity to load. Must be provided if + * the association to load represents the owning side, otherwise + * the identifier is derived from the $sourceEntity. + * + * @return object The loaded and managed entity instance or NULL if the entity can not be found. + * + * @throws \Doctrine\ORM\Mapping\MappingException + */ + public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array()); + + /** + * Refreshes a managed entity. + * + * @param array $id The identifier of the entity as an associative array from + * column or field names to values. + * @param object $entity The entity to refresh. + * @param int $lockMode + * + * @return void + */ + public function refresh(array $id, $entity, $lockMode = 0); + + /** + * Loads Entities matching the given Criteria object. + * + * @param \Doctrine\Common\Collections\Criteria $criteria + * + * @return array + */ + public function loadCriteria(Criteria $criteria); + + /** + * Loads a list of entities by a list of field criteria. + * + * @param array $criteria + * @param array|null $orderBy + * @param int|null $limit + * @param int|null $offset + * + * @return array + */ + public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null); + + /** + * Gets (sliced or full) elements of the given collection. + * + * @param array $assoc + * @param object $sourceEntity + * @param int|null $offset + * @param int|null $limit + * + * @return array + */ + public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null); + + /** + * Loads a collection of entities of a many-to-many association. + * + * @param array $assoc The association mapping of the association being loaded. + * @param object $sourceEntity The entity that owns the collection. + * @param PersistentCollection $collection The collection to fill. + * + * @return array + */ + public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection); + + /** + * Loads a collection of entities in a one-to-many association. + * + * @param array $assoc + * @param object $sourceEntity + * @param PersistentCollection $collection The collection to load/fill. + * + * @return array + */ + public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection); + + /** + * Locks all rows of this entity matching the given criteria with the specified pessimistic lock mode. + * + * @param array $criteria + * @param int $lockMode + * + * @return void + */ + public function lock(array $criteria, $lockMode); + + /** + * Returns an array with (sliced or full list) of elements in the specified collection. + * + * @param array $assoc + * @param object $sourceEntity + * @param int|null $offset + * @param int|null $limit + * + * @return array + */ + public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null); + + /** + * Checks whether the given managed entity exists in the database. + * + * @param object $entity + * @param array $extraConditions + * + * @return boolean TRUE if the entity exists in the database, FALSE otherwise. + */ + public function exists($entity, array $extraConditions = array()); +} diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index b8a7431d595..684d30571cc 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -204,14 +204,6 @@ public function executeInserts() $stmt->execute(); } - - if ($this->hasCache) { - $this->queuedCache['insert'][] = array( - 'entity' => $entity, - 'lock' => null, - 'key' => null - ); - } } $rootTableStmt->closeCursor(); diff --git a/lib/Doctrine/ORM/Proxy/ProxyFactory.php b/lib/Doctrine/ORM/Proxy/ProxyFactory.php index 363db8f98c3..d33a590f076 100644 --- a/lib/Doctrine/ORM/Proxy/ProxyFactory.php +++ b/lib/Doctrine/ORM/Proxy/ProxyFactory.php @@ -26,7 +26,7 @@ use Doctrine\Common\Proxy\Proxy as BaseProxy; use Doctrine\Common\Proxy\ProxyGenerator; use Doctrine\ORM\ORMInvalidArgumentException; -use Doctrine\ORM\Persisters\BasicEntityPersister; +use Doctrine\ORM\Persisters\EntityPersister; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityNotFoundException; @@ -107,13 +107,13 @@ protected function createProxyDefinition($className) * Creates a closure capable of initializing a proxy * * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata - * @param \Doctrine\ORM\Persisters\BasicEntityPersister $entityPersister + * @param \Doctrine\ORM\Persisters\EntityPersister $entityPersister * * @return \Closure * * @throws \Doctrine\ORM\EntityNotFoundException */ - private function createInitializer(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister) + private function createInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister) { if ($classMetadata->getReflectionClass()->hasMethod('__wakeup')) { return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) { @@ -183,13 +183,13 @@ private function createInitializer(ClassMetadata $classMetadata, BasicEntityPers * Creates a closure capable of finalizing state a cloned proxy * * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $classMetadata - * @param \Doctrine\ORM\Persisters\BasicEntityPersister $entityPersister + * @param \Doctrine\ORM\Persisters\EntityPersister $entityPersister * * @return \Closure * * @throws \Doctrine\ORM\EntityNotFoundException */ - private function createCloner(ClassMetadata $classMetadata, BasicEntityPersister $entityPersister) + private function createCloner(ClassMetadata $classMetadata, EntityPersister $entityPersister) { return function (BaseProxy $proxy) use ($entityPersister, $classMetadata) { if ($proxy->__isInitialized()) { diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 3bb76fb841d..b479d879c5c 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -38,6 +38,9 @@ use Doctrine\ORM\Event\PostFlushEventArgs; use Doctrine\ORM\Event\ListenersInvoker; +use Doctrine\ORM\Persisters\CachedPersister; +use Doctrine\ORM\Persisters\CachedCollectionPersister; +use Doctrine\ORM\Persisters\CachedEntityPersister; use Doctrine\ORM\Persisters\BasicEntityPersister; use Doctrine\ORM\Persisters\SingleTablePersister; use Doctrine\ORM\Persisters\JoinedSubclassPersister; @@ -371,7 +374,7 @@ public function commit($entity = null) $collectionPersister->delete($collectionToDelete); - if ($this->hasCache && $collectionPersister->hasCache()) { + if ($this->hasCache && ($collectionPersister instanceof CachedPersister)) { $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; } } @@ -382,7 +385,7 @@ public function commit($entity = null) $collectionPersister->update($collectionToUpdate); - if ($this->hasCache && $collectionPersister->hasCache()) { + if ($this->hasCache && ($collectionPersister instanceof CachedPersister)) { $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; } } @@ -720,7 +723,7 @@ public function computeChangeSet(ClassMetadata $class, $entity) continue; } - $this->computeAssociationChanges($entity, $assoc, $val); + $this->computeAssociationChanges($assoc, $val); if ( ! isset($this->entityChangeSets[$oid]) && $assoc['isOwningSide'] && @@ -791,7 +794,6 @@ public function computeChangeSets() /** * Computes the changes of an association. * - * @param mixed $entity The owner entity. * @param array $assoc The association mapping. * @param mixed $value The value of the association. * @@ -800,7 +802,7 @@ public function computeChangeSets() * * @return void */ - private function computeAssociationChanges($entity, $assoc, $value) + private function computeAssociationChanges($assoc, $value) { if ($value instanceof Proxy && ! $value->__isInitialized__) { return; @@ -1012,7 +1014,7 @@ private function executeInserts($class) $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } - if ($this->hasCache && $inserted && $persister->hasCache()) { + if ($this->hasCache && $inserted && ($persister instanceof CachedPersister)) { $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -1055,7 +1057,7 @@ private function executeUpdates($class) } } - if ($this->hasCache && $updated && $persister->hasCache()) { + if ($this->hasCache && $updated && ($persister instanceof CachedPersister)) { $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -1102,7 +1104,7 @@ private function executeDeletions($class) $deleted = true; } - if ($this->hasCache && $deleted && $persister->hasCache()) { + if ($this->hasCache && $deleted && ($persister instanceof CachedPersister)) { $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -3016,7 +3018,7 @@ public function size() * * @param string $entityName The name of the Entity. * - * @return \Doctrine\ORM\Persisters\BasicEntityPersister + * @return \Doctrine\ORM\Persisters\EntityPersister */ public function getEntityPersister($entityName) { @@ -3043,6 +3045,10 @@ public function getEntityPersister($entityName) throw new \RuntimeException('No persister found for entity.'); } + if ($this->hasCache && $class->cache !== null) { + $persister = new CachedEntityPersister($persister, $this->em, $class); + } + $this->persisters[$entityName] = $persister; return $this->persisters[$entityName]; @@ -3053,7 +3059,7 @@ public function getEntityPersister($entityName) * * @param array $association * - * @return \Doctrine\ORM\Persisters\AbstractCollectionPersister + * @return \Doctrine\ORM\Persisters\CollectionPersister */ public function getCollectionPersister(array $association) { @@ -3067,6 +3073,10 @@ public function getCollectionPersister(array $association) ? new OneToManyPersister($this->em, $association) : new ManyToManyPersister($this->em, $association); + if ($this->hasCache && isset($association['cache'])) { + $persister = new CachedCollectionPersister($persister, $this->em, $association); + } + $this->collectionPersisters[$role] = $persister; return $this->collectionPersisters[$role]; diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php index 9480e86b35b..59dbdee37c2 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php @@ -8,6 +8,7 @@ use Doctrine\ORM\Cache\EntityCacheEntry; use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Country; +use Doctrine\ORM\Cache\DefaultCacheFactory; use Doctrine\ORM\Cache\DefaultEntityEntryStructure; /** From 43e3b0ddd70b856532f8942457000a3f72e37aa4 Mon Sep 17 00:00:00 2001 From: fabios Date: Wed, 10 Jul 2013 18:15:32 -0400 Subject: [PATCH 51/71] reduce QueryCache dependence --- lib/Doctrine/ORM/AbstractQuery.php | 7 ++++--- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 16 ++++++---------- lib/Doctrine/ORM/Cache/QueryCache.php | 16 ++++++++-------- lib/Doctrine/ORM/Cache/QueryCacheKey.php | 19 ++++++++++++++++--- .../ORM/Cache/QueryCacheValidator.php | 5 ++--- .../Cache/TimestampQueryCacheValidator.php | 6 +++--- .../SecondLevelCacheQueryCacheTest.php | 2 +- 7 files changed, 40 insertions(+), 31 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index 986e4ef6fa2..e935d3f6511 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -942,9 +942,10 @@ private function executeIgnoreQueryCache($parameters = null, $hydrationMode = nu */ private function executeUsingQueryCache($parameters = null, $hydrationMode = null) { - $querykey = new QueryCacheKey($this->getHash()); + $rsm = $this->_resultSetMapping ?: $this->getResultSetMapping(); + $querykey = new QueryCacheKey($this->getHash(), $this->lifetime); $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); - $result = $queryCache->get($querykey, $this); + $result = $queryCache->get($querykey, $rsm); if ($result !== null) { @@ -956,7 +957,7 @@ private function executeUsingQueryCache($parameters = null, $hydrationMode = nul } $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode); - $cached = $queryCache->put($querykey, $this, $result); + $cached = $queryCache->put($querykey, $rsm, $result); if ($this->cacheLogger) { $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey); diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 3982f0c3355..b3c9cfa860a 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -24,12 +24,12 @@ use Doctrine\ORM\Persisters\CachedPersister; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\QueryCacheEntry; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Cache\CacheException; -use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\Query; /** @@ -88,23 +88,21 @@ public function __construct(EntityManagerInterface $em, Region $region) * * @TODO - does not work recursively yet */ - public function get(QueryCacheKey $key, AbstractQuery $query) + public function get(QueryCacheKey $key, ResultSetMapping $rsm) { - $entry = $this->region->get($key); - $lifetime = $query->getLifetime(); + $entry = $this->region->get($key); if ( ! $entry instanceof QueryCacheEntry) { return null; } - if ( ! $this->validator->isValid($entry, $query)) { + if ( ! $this->validator->isValid($key, $entry)) { $this->region->evict($key); return null; } $result = array(); - $rsm = $query->getResultSetMapping(); $entityName = reset($rsm->aliasMap); //@TODO find root entity $hasRelation = ( ! empty($rsm->relationMap)); $metadata = $this->em->getClassMetadata($entityName); @@ -176,15 +174,13 @@ public function get(QueryCacheKey $key, AbstractQuery $query) * * @TODO - does not work recursively yet */ - public function put(QueryCacheKey $key, AbstractQuery $query, array $result) + public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) { - $data = array(); - $rsm = $query->getResultSetMapping(); - if ($rsm->scalarMappings) { throw new CacheException("Second level cache does not suport scalar results."); } + $data = array(); $entityName = reset($rsm->aliasMap); //@TODO find root entity $hasRelation = ( ! empty($rsm->relationMap)); $metadata = $this->em->getClassMetadata($entityName); diff --git a/lib/Doctrine/ORM/Cache/QueryCache.php b/lib/Doctrine/ORM/Cache/QueryCache.php index 668b386de55..bb00e2f8026 100644 --- a/lib/Doctrine/ORM/Cache/QueryCache.php +++ b/lib/Doctrine/ORM/Cache/QueryCache.php @@ -20,7 +20,7 @@ namespace Doctrine\ORM\Cache; -use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Query\ResultSetMapping; /** * Defines the contract for caches capable of storing query results. @@ -37,21 +37,21 @@ interface QueryCache public function clear(); /** - * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\AbstractQuery $query - * @param array $result + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\ResultSetMapping $rsm + * @param array $result * * @return boolean */ - public function put(QueryCacheKey $key, AbstractQuery $query, array $result); + public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result); /** - * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\AbstractQuery $query + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\ResultSetMapping $rsm * * @return void */ - public function get(QueryCacheKey $key, AbstractQuery $query); + public function get(QueryCacheKey $key, ResultSetMapping $rsm); /** * @return \Doctrine\ORM\Cache\Region diff --git a/lib/Doctrine/ORM/Cache/QueryCacheKey.php b/lib/Doctrine/ORM/Cache/QueryCacheKey.php index b1a4c3b365c..1f65d66c302 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheKey.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheKey.php @@ -35,11 +35,19 @@ class QueryCacheKey implements CacheKey private $hash; /** - * @param string $hash The result cache id + * @var integer */ - public function __construct($hash) + private $lifetime; + + /** + * + * @param string $hash Result cache id + * @param integer $lifetime Query lifetime + */ + public function __construct($hash, $lifetime) { - $this->hash = $hash; + $this->hash = $hash; + $this->lifetime = $lifetime; } /** @@ -49,4 +57,9 @@ public function hash() { return $this->hash; } + + public function getLifetime() + { + return $this->lifetime; + } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/QueryCacheValidator.php b/lib/Doctrine/ORM/Cache/QueryCacheValidator.php index 45abd5389ae..dcd50517689 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheValidator.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheValidator.php @@ -21,7 +21,6 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache\QueryCacheEntry; -use Doctrine\ORM\AbstractQuery; /** * Cache query validator interface. @@ -34,10 +33,10 @@ interface QueryCacheValidator /** * Checks if the query entry is valid * + * @param \Doctrine\ORM\Cache\QueryCacheEntry $key * @param \Doctrine\ORM\Cache\QueryCacheEntry $entry - * @param \Doctrine\ORM\AbstractQuery $query * * @return boolean */ - public function isValid(QueryCacheEntry $entry, AbstractQuery $query); + public function isValid(QueryCacheKey $key, QueryCacheEntry $entry); } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php index f070a4b3276..778f24f1884 100644 --- a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php +++ b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php @@ -21,7 +21,7 @@ namespace Doctrine\ORM\Cache; use Doctrine\ORM\Cache\QueryCacheEntry; -use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\Cache\QueryCacheKey; /** * @since 2.5 @@ -32,9 +32,9 @@ class TimestampQueryCacheValidator implements QueryCacheValidator /** * {@inheritdoc} */ - public function isValid(QueryCacheEntry $entry, AbstractQuery $query) + public function isValid(QueryCacheKey $key, QueryCacheEntry $entry) { - $lifetime = $query->getLifetime(); + $lifetime = $key->getLifetime(); if ($lifetime == 0) { return true; diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 86233724cd4..12573969251 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -502,7 +502,7 @@ public function testQueryCacheLifetime() $this->_em->clear(); - $key = new QueryCacheKey($getHash($query)); + $key = new QueryCacheKey($getHash($query), 3600); $entry = $this->cache->getQueryCache() ->getRegion() ->get($key); From 4ff7a6465edfe632d88e98144e33669b26ddb47e Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 11 Jul 2013 12:08:28 -0400 Subject: [PATCH 52/71] Improve one-to-many collection coverage --- .../Persisters/CachedCollectionPersister.php | 18 +-- .../SecondLevelCacheOneToManyTest.php | 127 ++++++++++++++++++ 2 files changed, 136 insertions(+), 9 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php index e204e8b22df..7e5ca732895 100644 --- a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php @@ -52,22 +52,22 @@ class CachedCollectionPersister implements CachedPersister, CollectionPersister /** * @var \Doctrine\ORM\Mapping\ClassMetadata */ - protected $sourceEntity; + private $sourceEntity; /** * @var \Doctrine\ORM\Mapping\ClassMetadata */ - protected $targetEntity; + private $targetEntity; /** * @var array */ - protected $association; + private $association; /** * @var array */ - protected $queuedCache = array(); + private $queuedCache = array(); /** * @var boolean @@ -77,17 +77,17 @@ class CachedCollectionPersister implements CachedPersister, CollectionPersister /** * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess */ - protected $cacheRegionAccess; + private $cacheRegionAccess; /** * @var \Doctrine\ORM\Cache\CollectionEntryStructure */ - protected $cacheEntryStructure; + private $cacheEntryStructure; /** * @var \Doctrine\ORM\Cache\Logging\CacheLogger */ - protected $cacheLogger; + private $cacheLogger; public function __construct(CollectionPersister $persister, EntityManagerInterface $em, array $association) { @@ -282,7 +282,7 @@ public function delete(PersistentCollection $collection) $this->persister->delete($collection); - $this->queuedCache['delete'][] = array( + $this->queuedCache['delete'][spl_object_hash($collection)] = array( 'list' => null, 'key' => $key, 'lock' => $lock @@ -304,7 +304,7 @@ public function update(PersistentCollection $collection) $this->persister->update($collection); - $this->queuedCache['update'][] = array( + $this->queuedCache['update'][spl_object_hash($collection)] = array( 'list' => $collection, 'key' => $key, 'lock' => $lock diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index a96c53ee1d3..6fa5da60c84 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -178,4 +178,131 @@ public function testShoudNotPutOneToManyRelationOnPersist() $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $state->getId())); $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $state->getId())); } + + public function testOneToManyRemove() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); + $this->evictRegions(); + + $this->assertFalse($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $this->states[0]->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(0)->getId())); + $this->assertFalse($this->cache->containsEntity(City::CLASSNAME, $this->states[0]->getCities()->get(1)->getId())); + + $entity = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getEntityRegion(State::CLASSNAME))); + + //trigger lazy load + $this->assertCount(2, $entity->getCities()); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getPutCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + + $this->assertInstanceOf(City::CLASSNAME, $entity->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $entity->getCities()->get(1)); + + $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + + //trigger lazy load from cache + $this->assertCount(2, $state->getCities()); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + + $city0 = $state->getCities()->get(0); + $city1 = $state->getCities()->get(1); + + $this->assertInstanceOf(City::CLASSNAME, $city0); + $this->assertInstanceOf(City::CLASSNAME, $city1); + + $this->assertEquals($entity->getCities()->get(0)->getName(), $city0->getName()); + $this->assertEquals($entity->getCities()->get(1)->getName(), $city1->getName()); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $state->getCities()->removeElement($city0); + + $this->_em->remove($city0); + $this->_em->persist($state); + $this->_em->flush(); + + $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + + //trigger lazy load from cache + $this->assertCount(1, $state->getCities()); + + $city1 = $state->getCities()->get(0); + $this->assertInstanceOf(City::CLASSNAME, $city1); + $this->assertEquals($entity->getCities()->get(1)->getName(), $city1->getName()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $state->getCities()->remove(0); + + $this->_em->remove($city1); + $this->_em->persist($state); + $this->_em->flush(); + + $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + + $this->assertCount(0, $state->getCities()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(0, $this->secondLevelCacheLogger->getRegionHitCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + } + + public function testOneToManyCount() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->secondLevelCacheLogger->clearStats(); + $this->evictRegions(); + $this->_em->clear(); + + $entitiId = $this->states[0]->getId(); + $queryCount = $this->getCurrentQueryCount(); + $entity = $this->_em->find(State::CLASSNAME, $entitiId); + + $this->assertEquals(2, $entity->getCities()->count()); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $entity = $this->_em->find(State::CLASSNAME, $entitiId); + + $this->assertEquals(2, $entity->getCities()->count()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + } } \ No newline at end of file From 9771f05119d8d13b82cae5df299d42967e6e1786 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 11 Jul 2013 12:41:39 -0400 Subject: [PATCH 53/71] Test collection lock --- .../SecondLevelCacheConcurrentTest.php | 129 +++++++++++++++++- 1 file changed, 127 insertions(+), 2 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php index 364381138b7..a7154d1d5a8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php @@ -12,7 +12,10 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\Tests\Models\Cache\Country; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\Tests\Models\Cache\State; +use Doctrine\Tests\Models\Cache\City; use Doctrine\ORM\Cache\CacheFactory; use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Cache\Lock; @@ -37,7 +40,7 @@ protected function setUp() $this->_em->getConfiguration()->setSecondLevelCacheFactory($this->cacheFactory); } - public function testBasicConcurrentReadLock() + public function testBasicConcurrentEntityReadLock() { $this->loadFixturesCountries(); $this->_em->clear(); @@ -61,7 +64,7 @@ public function testBasicConcurrentReadLock() $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); } - public function testBasicConcurrentWriteLock() + public function testBasicConcurrentEntityWriteLock() { $this->loadFixturesCountries(); $this->_em->clear(); @@ -112,6 +115,128 @@ public function testBasicConcurrentWriteLock() $this->assertEquals('Foo 1', $country->getName()); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); } + + public function testBasicConcurrentCollectionReadLock() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->_em->clear(); + $this->evictRegions(); + + $stateId = $this->states[0]->getId(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertCount(2, $state->getCities()); + + $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); + + $stateId = $this->states[0]->getId(); + $cacheId = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>$stateId)); + $region = $this->_em->getCache()->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')->getRegion(); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + + /* @var $region \Doctrine\Tests\Mocks\ConcurrentRegionMock */ + $region->setLock($cacheId, Lock::createLockRead()); // another proc lock the entity cache + + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertEquals(0, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + + $this->assertEquals(0, $this->secondLevelCacheLogger->getRegionMissCount($this->getEntityRegion(State::CLASSNAME))); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(State::CLASSNAME))); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertCount(2, $state->getCities()); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getCollectionRegion(State::CLASSNAME, 'cities'))); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + } + + public function testBasicConcurrentCollectionWriteLock() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->_em->clear(); + $this->evictRegions(); + + $lock = Lock::createLockWrite(); + $stateId = $this->states[0]->getId(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertCount(2, $state->getCities()); + + $this->_em->clear(); + $this->secondLevelCacheLogger->clearStats(); + + $stateId = $this->states[0]->getId(); + $cacheKey = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>$stateId)); + $region = $this->_em->getCache()->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')->getRegion(); + + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + + /* @var $region \Doctrine\Tests\Mocks\ConcurrentRegionMock */ + $region->setLock($cacheKey, $lock); // another proc lock entity cache + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertCount(2, $state->getCities()); // Cache locked, goes straight to database + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + + $city0 = $state->getCities()->get(0); + $city1 = $state->getCities()->get(1); + + $state->getCities()->remove(0); + + $this->_em->remove($city1); + $this->_em->persist($state); + $this->_em->flush(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertCount(1, $state->getCities()); // Cache locked, goes straight to database + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + + /* @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ + $region->writeUnlock($cacheKey, $lock); // another proc unlock + $region->evict($cacheKey); // and clear the cache. + + $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $state = $this->_em->find(State::CLASSNAME, $stateId); + + $this->assertInstanceOf(State::CLASSNAME, $state); + $this->assertCount(1, $state->getCities()); //No Cache, goes to database + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); + } } class CacheFactorySecondLevelCacheConcurrentTest implements CacheFactory From 907820e0f4257eda4840cb8a594f1395d51f2df8 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 11 Jul 2013 15:29:54 -0400 Subject: [PATCH 54/71] Query cache mode --- lib/Doctrine/ORM/AbstractQuery.php | 52 +++++-- lib/Doctrine/ORM/Cache.php | 22 +++ lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 22 ++- lib/Doctrine/ORM/Cache/QueryCacheKey.php | 25 +-- .../Cache/TimestampQueryCacheValidator.php | 6 +- .../SecondLevelCacheQueryCacheTest.php | 142 ++++++++++++++++++ 6 files changed, 234 insertions(+), 35 deletions(-) diff --git a/lib/Doctrine/ORM/AbstractQuery.php b/lib/Doctrine/ORM/AbstractQuery.php index e935d3f6511..2b021b28588 100644 --- a/lib/Doctrine/ORM/AbstractQuery.php +++ b/lib/Doctrine/ORM/AbstractQuery.php @@ -26,6 +26,7 @@ use Doctrine\ORM\Cache\QueryCacheKey; use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\ORM\Cache; use Doctrine\ORM\Query\QueryException; use Doctrine\ORM\ORMInvalidArgumentException; @@ -135,6 +136,13 @@ abstract class AbstractQuery */ protected $cacheRegion; + /** + * Second level query cache mode. + * + * @var integer + */ + protected $cacheMode; + /** * @var \Doctrine\ORM\Cache\Logging\CacheLogger */ @@ -145,13 +153,25 @@ abstract class AbstractQuery */ protected $lifetime = 0; - /** + /** + * Initializes a new instance of a class derived from AbstractQuery. + * + * @param \Doctrine\ORM\EntityManager $em + */ + public function __construct(EntityManager $em) + { + $this->_em = $em; + $this->parameters = new ArrayCollection(); + $this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheLogger(); + } + + /** * * Enable/disable second level query (result) caching for this query. * * @param boolean $cacheable * - * @return \Doctrine\ORM\Query + * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setCacheable($cacheable) { @@ -170,8 +190,8 @@ public function isCacheable() /** * @param string $cacheRegion - * - * @return \Doctrine\ORM\Query + * + * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setCacheRegion($cacheRegion) { @@ -210,6 +230,7 @@ public function getLifetime() * Sets the life-time for this query into second level cache. * * @param integer $lifetime + * @return \Doctrine\ORM\AbstractQuery This query instance. */ public function setLifetime($lifetime) { @@ -219,15 +240,22 @@ public function setLifetime($lifetime) } /** - * Initializes a new instance of a class derived from AbstractQuery. - * - * @param \Doctrine\ORM\EntityManager $em + * @return integer */ - public function __construct(EntityManager $em) + public function getCacheMode() { - $this->_em = $em; - $this->parameters = new ArrayCollection(); - $this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheLogger(); + return $this->cacheMode; + } + + /** + * @param integer $cacheMode + * @return \Doctrine\ORM\AbstractQuery This query instance. + */ + public function setCacheMode($cacheMode) + { + $this->cacheMode = $cacheMode; + + return $this; } /** @@ -943,7 +971,7 @@ private function executeIgnoreQueryCache($parameters = null, $hydrationMode = nu private function executeUsingQueryCache($parameters = null, $hydrationMode = null) { $rsm = $this->_resultSetMapping ?: $this->getResultSetMapping(); - $querykey = new QueryCacheKey($this->getHash(), $this->lifetime); + $querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL); $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion); $result = $queryCache->get($querykey, $rsm); diff --git a/lib/Doctrine/ORM/Cache.php b/lib/Doctrine/ORM/Cache.php index b88989c1d93..0fbe68f2344 100644 --- a/lib/Doctrine/ORM/Cache.php +++ b/lib/Doctrine/ORM/Cache.php @@ -32,6 +32,28 @@ interface Cache { const DEFAULT_QUERY_REGION_NAME = 'query.cache.region'; + /** + * May read items from the cache, but will not add items. + */ + const MODE_GET = 1; + + /** + * Will never read items from the cache, + * but will add items to the cache as it reads them from the database. + */ + const MODE_PUT = 2; + + /** + * May read items from the cache, and add items to the cache. + */ + const MODE_NORMAL = 3; + + /** + * The session will never read items from the cache, + * but will refresh items to the cache as it reads them from the database. + */ + const MODE_REFRESH = 4; + /** * Construct * diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index b3c9cfa860a..4a91947a2f7 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Cache\CacheException; +use Doctrine\ORM\Cache; use Doctrine\ORM\Query; /** @@ -90,6 +91,10 @@ public function __construct(EntityManagerInterface $em, Region $region) */ public function get(QueryCacheKey $key, ResultSetMapping $rsm) { + if ( ! ($key->cacheMode & Cache::MODE_GET)) { + return null; + } + $entry = $this->region->get($key); if ( ! $entry instanceof QueryCacheEntry) { @@ -105,7 +110,6 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) $result = array(); $entityName = reset($rsm->aliasMap); //@TODO find root entity $hasRelation = ( ! empty($rsm->relationMap)); - $metadata = $this->em->getClassMetadata($entityName); $persister = $this->uow->getEntityPersister($entityName); $region = $persister->getCacheRegionAcess()->getRegion(); @@ -180,6 +184,10 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) throw new CacheException("Second level cache does not suport scalar results."); } + if ( ! ($key->cacheMode & Cache::MODE_PUT)) { + return false; + } + $data = array(); $entityName = reset($rsm->aliasMap); //@TODO find root entity $hasRelation = ( ! empty($rsm->relationMap)); @@ -197,10 +205,10 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) $data[$index]['identifier'] = $identifier; $data[$index]['associations'] = array(); - if ( ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) { + if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) { // Cancel put result if entity put fail if ( ! $persister->putEntityCache($entity, $entityKey)) { - return; + return false; } } @@ -229,11 +237,11 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); - if ( ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { + if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { // Cancel put result if entity put fail if ( ! $assocPersister->putEntityCache($assocValue, $entityKey)) { - return; + return false; } } @@ -256,11 +264,11 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) foreach ($assocValue as $assocItemIndex => $assocItem) { $assocIdentifier = $this->uow->getEntityIdentifier($assocItem); - if ( ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { + if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { // Cancel put result if entity put fail if ( ! $assocPersister->putEntityCache($assocItem, $entityKey)) { - return; + return false; } } diff --git a/lib/Doctrine/ORM/Cache/QueryCacheKey.php b/lib/Doctrine/ORM/Cache/QueryCacheKey.php index 1f65d66c302..f20092ffe6a 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheKey.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheKey.php @@ -37,17 +37,23 @@ class QueryCacheKey implements CacheKey /** * @var integer */ - private $lifetime; + public $lifetime; /** - * - * @param string $hash Result cache id - * @param integer $lifetime Query lifetime + * @var integer + */ + public $cacheMode; + + /** + * @param string $hash Result cache id + * @param integer $lifetime Query lifetime + * @param integer $cacheMode Query cache mode */ - public function __construct($hash, $lifetime) + public function __construct($hash, $lifetime, $cacheMode = 3) { - $this->hash = $hash; - $this->lifetime = $lifetime; + $this->hash = $hash; + $this->lifetime = $lifetime; + $this->cacheMode = $cacheMode; } /** @@ -57,9 +63,4 @@ public function hash() { return $this->hash; } - - public function getLifetime() - { - return $this->lifetime; - } } \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php index 778f24f1884..d2fdac92755 100644 --- a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php +++ b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php @@ -34,12 +34,10 @@ class TimestampQueryCacheValidator implements QueryCacheValidator */ public function isValid(QueryCacheKey $key, QueryCacheEntry $entry) { - $lifetime = $key->getLifetime(); - - if ($lifetime == 0) { + if ($key->lifetime == 0) { return true; } - return ($entry->time + $lifetime) > time(); + return ($entry->time + $key->lifetime) > time(); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 12573969251..9b873feecbf 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -7,6 +7,9 @@ use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\City; use Doctrine\ORM\Cache\QueryCacheKey; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\EntityCacheEntry; +use Doctrine\ORM\Cache; /** * @group DDC-2183 @@ -75,6 +78,145 @@ public function testBasicQueryCache() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); } + public function testQueryCacheModeGet() + { + $this->evictRegions(); + $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())); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $queryGet = $this->_em->createQuery($dql) + ->setCacheMode(Cache::MODE_GET) + ->setCacheable(true); + + // MODE_GET should never add items to the cache. + $this->assertCount(2, $queryGet->getResult()); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->assertCount(2, $queryGet->getResult()); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + + $result = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(2, $result); + $this->assertEquals($queryCount + 3, $this->getCurrentQueryCount()); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + // MODE_GET should read items if exists. + $this->assertCount(2, $queryGet->getResult()); + $this->assertEquals($queryCount + 3, $this->getCurrentQueryCount()); + } + + public function testQueryCacheModePut() + { + $this->evictRegions(); + $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())); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $result = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $this->assertCount(2, $result); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $queryPut = $this->_em->createQuery($dql) + ->setCacheMode(Cache::MODE_PUT) + ->setCacheable(true); + + // MODE_PUT should never read itens from cache. + $this->assertCount(2, $queryPut->getResult()); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $this->assertCount(2, $queryPut->getResult()); + $this->assertEquals($queryCount + 3, $this->getCurrentQueryCount()); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + } + + public function testQueryCacheModeRefresh() + { + $this->evictRegions(); + $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())); + + $region = $this->cache->getEntityCacheRegionAccess(Country::CLASSNAME)->getRegion(); + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; + $result = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $this->assertCount(2, $result); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $countryId1 = $this->countries[0]->getId(); + $countryId2 = $this->countries[1]->getId(); + $countryName1 = $this->countries[0]->getName(); + $countryName2 = $this->countries[1]->getName(); + + $key1 = new EntityCacheKey(Country::CLASSNAME, array('id'=>$countryId1)); + $key2 = new EntityCacheKey(Country::CLASSNAME, array('id'=>$countryId2)); + $entry1 = new EntityCacheEntry(Country::CLASSNAME, array('id'=>$countryId1, 'name'=>'outdated')); + $entry2 = new EntityCacheEntry(Country::CLASSNAME, array('id'=>$countryId2, 'name'=>'outdated')); + + $region->put($key1, $entry1); + $region->put($key2, $entry2); + $this->_em->clear(); + + $queryRefresh = $this->_em->createQuery($dql) + ->setCacheMode(Cache::MODE_REFRESH) + ->setCacheable(true); + + // MODE_REFRESH should never read itens from cache. + $result1 = $queryRefresh->getResult(); + $this->assertCount(2, $result1); + $this->assertEquals($countryName1, $result1[0]->getName()); + $this->assertEquals($countryName2, $result1[1]->getName()); + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + + $this->_em->clear(); + + $result2 = $queryRefresh->getResult(); + $this->assertCount(2, $result2); + $this->assertEquals($countryName1, $result2[0]->getName()); + $this->assertEquals($countryName2, $result2[1]->getName()); + $this->assertEquals($queryCount + 3, $this->getCurrentQueryCount()); + } + public function testBasicQueryCachePutEntityCache() { $this->evictRegions(); From 7396e56e6a6ad6ae96940f65a173ce096b716a47 Mon Sep 17 00:00:00 2001 From: fabios Date: Fri, 12 Jul 2013 12:48:44 -0400 Subject: [PATCH 55/71] Basic documentation for second level cache --- docs/en/index.rst | 1 + docs/en/reference/second-level-cache.rst | 852 +++++++++++++++++++++++ docs/en/toc.rst | 12 +- 3 files changed, 861 insertions(+), 4 deletions(-) create mode 100644 docs/en/reference/second-level-cache.rst diff --git a/docs/en/index.rst b/docs/en/index.rst index 72cac572716..7df56526d1a 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -78,6 +78,7 @@ Advanced Topics * :doc:`Change Tracking Policies ` * :doc:`Best Practices ` * :doc:`Metadata Drivers ` + * :doc:`Second Level Cache ` Tutorials --------- diff --git a/docs/en/reference/second-level-cache.rst b/docs/en/reference/second-level-cache.rst new file mode 100644 index 00000000000..0159eb3246b --- /dev/null +++ b/docs/en/reference/second-level-cache.rst @@ -0,0 +1,852 @@ +The Second Level Cache +====================== + +The Second Level Cache is designed to reduces the amount of necessary database access. +It sits between your application and the database to avoid the number of database hits as many as possible. + +When this is turned on, entities will be first searched in cache and if they are not found, +a database query will be fired an then the entity result will be stored in a cache provider. + +There are some flavors of caching available, but is better to cache read-only data. + +Be aware that caches are not aware of changes made to the persistent store by another application. +They can, however, be configured to regularly expire cached data. + + +Caching Regions +--------------- + +Second level cache does not store instances of an entity, instead it caches only entity identifier and values. +Each entity class, collection association and query has its region, where values of each instance are stored. + +Caching Regions are specific region into the cache provider that might store entities, collection or queries. +Each cache region resides in a specific cache namespace and has its own lifetime configuration. + +Something like below for an entity region : + +.. code-block:: php + + ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null], + 'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]], + 'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]] + ]; + + +If the entity holds a collection that also needs to be cached. +An collection region could look something like : + +.. code-block:: php + + ['ownerId'=> 1, 'list' => [1, 2, 3]], + 'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]], + 'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]] + ]; + +A query region might be something like : + +.. code-block:: php + + ['list' => [1, 2, 3]], + 'region_name:query_2_hash' => ['list' => [2, 3]], + 'region_name:query_3_hash' => ['list' => [2, 4]] + ]; + + +.. note:: + + Notice that when caching collection and queries only identifiers are stored. + The entity values will be stored in its own region + + +``Doctrine\ORM\Cache\Region\DefaultRegion`` Its the default implementation. + A simplest cache region compatible with all doctrine-cache drivers but does not support locking. + +``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion`` +Defines contracts that should be implemented by a cache provider. + +It allows you to provide your own cache implementation that might take advantage of specific cache driver. + +If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise. + + + +``Doctrine\ORM\Cache\Region`` + +Defines a contract for accessing a particular cache region. + +.. code-block:: php + + null + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function get(CacheKey $key); + + /** + * Attempt to cache an object, after loading from the database. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. + * + * @return TRUE if the object was successfully cached. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function put(CacheKey $key, CacheEntry $entry); + + /** + * Called after an item has been inserted (after the transaction completes). + * + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. + * + * @return boolean TRUE If the contents of the cache actual were changed. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function afterInsert(CacheKey $key, CacheEntry $entry); + + /** + * Called after an item has been updated (after the transaction completes). + * + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. + * @param \Doctrine\ORM\Cache\Lock $lock The lock obtained from lockItem + * + * @return boolean TRUE If the contents of the cache actual were changed. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null); + + /** + * Forcibly evict an item from the cache immediately without regard for locks. + * + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to remove. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function evict(CacheKey $key); + + /** + * Forcibly evict all items from the cache immediately without regard for locks. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function evictAll(); + } + + +``Doctrine\ORM\Cache\RegionAccess`` + +Defines contract for regions which hold concurrently managed data. + +.. code-block:: php + + setSecondLevelCacheEnabled(); + + //Cache factory + $config->setSecondLevelCacheFactory($factory); + + +Cache Factory +~~~~~~~~~~~~~ + +Cache Factory is the main point of extension. + +It allows you to provide a specific implementation of the following components : + +* ``QueryCache`` Store and retrieve query cache results. +* ``RegionAccess`` Store query entities and collection. +* ``EntityEntryStructure`` Transform an entity into a cache entry and cache entry into entities +* ``CollectionEntryStructure`` Transform a collection into a cache entry and cache entry into collection + +.. code-block:: php + + setSecondLevelCacheRegionLifetime('my_entity_region', 3600); + $config->setSecondLevelCacheDefaultRegionLifetime(7200); + + +Cache Log +~~~~~~~~~ +By providing a cache logger you should be able to get information about all cache operations such as hits, miss put. + +``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics. + + .. code-block:: php + + setSecondLevelCacheLogger($logger); + + + // Collect cache statistics + + // Get the number of entries successfully retrieved from a specific region. + $logger->getRegionHitCount('my_entity_region'); + + // Get the number of cached entries *not* found in a specific region. + $logger->getRegionMissCount('my_entity_region'); + + // Get the number of cacheable entries put in cache. + $logger->getRegionPutCount('my_entity_region'); + + // Get the total number of put in all regions. + $logger->getPutCount(); + + // Get the total number of entries successfully retrieved from all regions. + $logger->getHitCount(); + + // Get the total number of cached entries *not* found in all regions. + $logger->getMissCount(); + +If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``. +and collect all information you want. + + .. code-block:: php + + + + + + + + + + + + + .. code-block:: yaml + + Country: + type: entity + cache: + usage : READ_ONLY + region : my_entity_region + id: + id: + type: integer + id: true + generator: + strategy: IDENTITY + fields: + name: + type: string + + +Association cache definition +---------------------------- +The most common use case is to cache entities. But we can also cache relationships. +It caches the primary keys of association and cache each element will be cached into its region. + + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + State: + type: entity + cache: + usage : NONSTRICT_READ_WRITE + id: + id: + type: integer + id: true + generator: + strategy: IDENTITY + fields: + name: + type: string + + manyToOne: + state: + targetEntity: Country + joinColumns: + country_id: + referencedColumnName: id + cache: + usage : NONSTRICT_READ_WRITE + + oneToMany: + cities: + targetEntity:City + mappedBy: state + cache: + usage : NONSTRICT_READ_WRITE + + +Cache usage +~~~~~~~~~~~ + +Basic entity cache + +.. code-block:: php + + persist(new Country($name)); + $em->flush(); // Hit database to insert the row and put into cache + + $em->clear(); // Clear entity manager + + $country = $em->find('Country', 1); // Retrieve item from cache + + $country->setName("New Name"); + $em->persist($state); + $em->flush(); // Hit database to update the row and update cache + + $em->clear(); // Clear entity manager + + $country = $em->find('Country', 1); // Retrieve item from cache + + +Association cache + +.. code-block:: php + + persist(new State($name, $country)); + $em->flush(); + + // Clear entity manager + $em->clear(); + + // Retrieve item from cache + $state = $em->find('State', 1); + + // Hit database to update the row and update cache entry + $state->setName("New Name"); + $em->persist($state); + $em->flush(); + + // Create a new collection item + $city = new City($name, $state); + $state->addCity($city); + + // Hit database to insert new collection item, + // put entity and collection cache into cache. + $em->persist($city); + $em->persist($state); + $em->flush(); + + // Clear entity manager + $em->clear(); + + // Retrieve item from cache + $state = $em->find('State', 1); + + // Retrieve association from cache + $country = $state->getCountry(); + + // Retrieve collection from cache + $cities = $state->getCities(); + + echo $country->getName(); + echo $state->getName(); + + // Retrieve each collection item from cache + foreach ($cities as $city) { + echo $city->getName(); + } + +.. note:: + + Notice that all entities should be marked as cacheable. + +Using the query cache +--------------------- + +The second level cache stores the entities, associations and collections. +The query cache stores the results of the query but as identifiers, +The entity values are actually stored in the 2nd level cache. +So, query cache is useless without a 2nd level cache. + +.. code-block:: php + + createQuery('SELECT c FROM Country c ORDER BY c.name') + ->setCacheable(true) + ->getResult(); + + // Check if query result is valid and load entities from cache + $result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name') + ->setCacheable(true) + ->getResult(); + + +Cache API +--------- + +Caches are not aware of changes made by another application. +however, you can use the cache API to check / invalidate cache entries. + +.. code-block:: php + + getCache(); + + $cache->containsEntity('State', 1) // Check if the cache exists + $cache)->evictEntity('State', 1); // Remove an entity from cache + $cache->evictEntityRegion('State'); // Remove all entities from cache + + $cache->containsCollection('State', 'cities', 1); // Check if the cache exists + $cache->evictCollection('State', 'cities', 1); // Remove an entity collection from cache + $cache->evictCollectionRegion('State', 'cities'); // Remove all collections from cache + +Limitations +----------- \ No newline at end of file diff --git a/docs/en/toc.rst b/docs/en/toc.rst index 1a331fa2384..9d5553704d3 100644 --- a/docs/en/toc.rst +++ b/docs/en/toc.rst @@ -53,10 +53,13 @@ Reference Guide reference/metadata-drivers reference/best-practices reference/limitations-and-known-issues - reference/filters.rst - reference/namingstrategy.rst - reference/advanced-configuration.rst - + tutorials/pagination + reference/filters + reference/namingstrategy + reference/installation + reference/advanced-configuration + reference/second-level-cache + Cookbook -------- @@ -81,4 +84,5 @@ Cookbook cookbook/mysql-enums cookbook/advanced-field-value-conversion-using-custom-mapping-types cookbook/entities-in-session + cookbook/resolve-target-entity-listener From 453fa0a6688a8bfe9d61318df1865efb957f255d Mon Sep 17 00:00:00 2001 From: fabios Date: Fri, 12 Jul 2013 15:24:56 -0400 Subject: [PATCH 56/71] Support entity repository --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 9 +- .../ORM/Persisters/BasicEntityPersister.php | 29 +- .../ORM/Persisters/CachedEntityPersister.php | 123 +++++++- .../ORM/Persisters/EntityPersister.php | 30 ++ .../Persisters/JoinedSubclassPersister.php | 2 +- .../SecondLevelCacheRepositoryTest.php | 128 ++++++++ .../ORM/Performance/SecondLevelCacheTest.php | 279 ++++++++++++++++++ .../ORM/Performance/SencondLevelCacheTest.php | 191 ------------ 8 files changed, 576 insertions(+), 215 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php create mode 100644 tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 4a91947a2f7..7ceae3cd8ce 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Cache\CacheException; +use Doctrine\Common\Proxy\Proxy; use Doctrine\ORM\Cache; use Doctrine\ORM\Query; @@ -220,12 +221,12 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) foreach ($rsm->relationMap as $name) { $assoc = $metadata->associationMappings[$name]; - if ( ! isset($assoc['cache'])) { - throw CacheException::nonCacheableEntityAssociation($entityName, $name); + if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) { + continue; } - if (($assocValue = $metadata->getFieldValue($entity, $name)) === null) { - continue; + if ( ! isset($assoc['cache'])) { + throw CacheException::nonCacheableEntityAssociation($entityName, $name); } $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index 4982c7e9d3e..cd871460036 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -231,6 +231,14 @@ public function getClassMetadata() return $this->class; } + /** + * {@inheritdoc} + */ + public function getResultSetMapping() + { + return $this->rsm; + } + /** * {@inheritdoc} */ @@ -1015,18 +1023,9 @@ private function getManyToManyStatement(array $assoc, $sourceEntity, $offset = n } /** - * Gets the SELECT SQL to select one or more entities by a set of field criteria. - * - * @param array|\Doctrine\Common\Collections\Criteria $criteria - * @param array|null $assoc - * @param int $lockMode - * @param int|null $limit - * @param int|null $offset - * @param array|null $orderBy - * - * @return string + * {@inheritdoc} */ - protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) + public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $lockSql = ''; $joinSql = ''; @@ -1688,13 +1687,9 @@ private function getOneToManyStatement(array $assoc, $sourceEntity, $offset = nu } /** - * Expands the parameters from the given criteria and use the correct binding types if found. - * - * @param array $criteria - * - * @return array + * {@inheritdoc} */ - private function expandParameters($criteria) + public function expandParameters($criteria) { $params = array(); $types = array(); diff --git a/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php b/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php index 3baf4354829..46a39a8d3f9 100644 --- a/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php @@ -31,6 +31,9 @@ use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Cache\QueryCacheKey; +use Doctrine\ORM\Cache; + /** * @author Fabio B. Silva * @since 2.5 @@ -77,11 +80,21 @@ class CachedEntityPersister implements CachedPersister, EntityPersister */ protected $cacheEntryStructure; + /** + * @var \Doctrine\ORM\Cache + */ + protected $cache; + /** * @var \Doctrine\ORM\Cache\Logging\CacheLogger */ protected $cacheLogger; + /** + * @var string + */ + protected $cacheRegionName; + public function __construct(EntityPersister $persister, EntityManagerInterface $em, ClassMetadata $class) { $config = $em->getConfiguration(); @@ -89,11 +102,13 @@ public function __construct(EntityPersister $persister, EntityManagerInterface $ $this->class = $class; $this->persister = $persister; + $this->cache = $em->getCache(); $this->uow = $em->getUnitOfWork(); $this->metadataFactory = $em->getMetadataFactory(); $this->cacheLogger = $config->getSecondLevelCacheLogger(); $this->cacheEntryStructure = $factory->buildEntityEntryStructure($em); $this->cacheRegionAccess = $factory->buildEntityRegionAccessStrategy($this->class); + $this->cacheRegionName = $this->cacheRegionAccess->getRegion()->getName(); $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); } @@ -113,6 +128,14 @@ public function getInserts() return $this->persister->getInserts(); } + /** + * {@inheritdoc} + */ + public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) + { + return $this->persister->getSelectSQL($orderBy, $assoc, $lockMode, $limit, $offset, $orderBy); + } + /** * {@inheritdoc} */ @@ -121,6 +144,14 @@ public function getInsertSQL() return $this->persister->getInsertSQL(); } + /** + * {@inheritdoc} + */ + public function getResultSetMapping() + { + return $this->persister->getResultSetMapping(); + } + /** * {@inheritdoc} */ @@ -298,11 +329,19 @@ public function getCacheRegionAcess() return $this->cacheRegionAccess; } + /** + * @return \Doctrine\ORM\Cache\EntityEntryStructure + */ public function getCacheEntryStructure() { return $this->cacheEntryStructure; } + /** + * @param object $entity + * @param \Doctrine\ORM\Cache\EntityCacheKey $key + * @return boolean + */ public function putEntityCache($entity, EntityCacheKey $key) { $class = $this->class; @@ -322,6 +361,26 @@ public function putEntityCache($entity, EntityCacheKey $key) return $cached; } + /** + * Generates a string of currently query + * + * @return string + */ + protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null) + { + list($params) = $this->expandParameters($criteria); + + return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset); + } + + /** + * {@inheritdoc} + */ + public function expandParameters($criteria) + { + return $this->persister->expandParameters($criteria); + } + /** * {@inheritdoc} */ @@ -359,7 +418,40 @@ public function getOwningTable($fieldName) */ public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = 0, $limit = null, array $orderBy = null) { - return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + //@TODO - Should throw exception ? + if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== 0) { + return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + } + + //handle only EntityRepository#findOneBy + $query = $this->persister->getSelectSQL($criteria, null, 0, $limit, 0, $orderBy); + $hash = $this->getHash($query, $criteria); + $rsm = $this->getResultSetMapping(); + $querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL); + $queryCache = $this->cache->getQueryCache($this->cacheRegionName); + $result = $queryCache->get($querykey, $rsm); + + if ($result !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->queryCacheHit($this->cacheRegionName, $querykey); + } + + return $result[0]; + } + + $result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + $cached = $queryCache->put($querykey, $rsm, array($result)); + + if ($this->cacheLogger && $result) { + $this->cacheLogger->queryCacheMiss($this->cacheRegionName, $querykey); + } + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->queryCachePut($this->cacheRegionName, $querykey); + } + + return $result; } /** @@ -367,7 +459,34 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint */ public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null) { - return $this->persister->loadAll($criteria, $orderBy, $limit, $offset); + $query = $this->persister->getSelectSQL($criteria, null, 0, $limit, $offset, $orderBy); + $hash = $this->getHash($query, $criteria); + $rsm = $this->getResultSetMapping(); + $querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL); + $queryCache = $this->cache->getQueryCache($this->cacheRegionName); + $result = $queryCache->get($querykey, $rsm); + + if ($result !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->queryCacheHit($this->cacheRegionName, $querykey); + } + + return $result; + } + + $result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset); + $cached = $queryCache->put($querykey, $rsm, $result); + + if ($this->cacheLogger && $result) { + $this->cacheLogger->queryCacheMiss($this->cacheRegionName, $querykey); + } + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->queryCachePut($this->cacheRegionName, $querykey); + } + + return $result; } /** diff --git a/lib/Doctrine/ORM/Persisters/EntityPersister.php b/lib/Doctrine/ORM/Persisters/EntityPersister.php index 169130a0796..a27a59d1452 100644 --- a/lib/Doctrine/ORM/Persisters/EntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/EntityPersister.php @@ -37,6 +37,13 @@ interface EntityPersister */ public function getClassMetadata(); + /** + * Gets the ResultSetMapping used for hydration. + * + * @return \Doctrine\ORM\Query\ResultSetMapping + */ + public function getResultSetMapping(); + /** * Get all queued inserts. * @@ -54,6 +61,29 @@ public function getInserts(); */ public function getInsertSQL(); + /** + * Gets the SELECT SQL to select one or more entities by a set of field criteria. + * + * @param array|\Doctrine\Common\Collections\Criteria $criteria + * @param array|null $assoc + * @param int $lockMode + * @param int|null $limit + * @param int|null $offset + * @param array|null $orderBy + * + * @return string + */ + public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null); + + /** + * Expands the parameters from the given criteria and use the correct binding types if found. + * + * @param $criteria + * + * @return array + */ + public function expandParameters($criteria); + /** * Gets the SQL WHERE condition for matching a field with a given value. * diff --git a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php index 684d30571cc..9e2ce474362 100644 --- a/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php +++ b/lib/Doctrine/ORM/Persisters/JoinedSubclassPersister.php @@ -296,7 +296,7 @@ public function delete($entity) /** * {@inheritdoc} */ - protected function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) + public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { $joinSql = ''; $identifierColumn = $this->class->getIdentifierColumnNames(); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php new file mode 100644 index 00000000000..813da64e1fa --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php @@ -0,0 +1,128 @@ +evictRegions(); + $this->loadFixturesCountries(); + + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); + + $queryCount = $this->getCurrentQueryCount(); + $repository = $this->_em->getRepository(Country::CLASSNAME); + $country1 = $repository->find($this->countries[0]->getId()); + $country2 = $repository->find($this->countries[1]->getId()); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->assertInstanceOf(Country::CLASSNAME, $country1); + $this->assertInstanceOf(Country::CLASSNAME, $country2); + + $this->assertEquals(2, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(0, $this->secondLevelCacheLogger->getMissCount()); + $this->assertEquals(2, $this->secondLevelCacheLogger->getRegionHitCount($this->getEntityRegion(Country::CLASSNAME))); + + } + + public function testRepositoryCacheFindAll() + { + $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->assertInstanceOf(Country::CLASSNAME, $countries[0]); + $this->assertInstanceOf(Country::CLASSNAME, $countries[1]); + + $this->assertEquals(1, $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 testRepositoryCacheFindBy() + { + $this->loadFixturesCountries(); + $this->evictRegions(); + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + + $criteria = array('name'=>$this->countries[0]->getName()); + $repository = $this->_em->getRepository(Country::CLASSNAME); + $queryCount = $this->getCurrentQueryCount(); + + $this->assertCount(1, $repository->findBy($criteria)); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $queryCount = $this->getCurrentQueryCount(); + $countries = $repository->findBy($criteria); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->assertCount(1, $countries); + $this->assertInstanceOf(Country::CLASSNAME, $countries[0]); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + } + + public function testRepositoryCacheFindOneBy() + { + $this->loadFixturesCountries(); + $this->evictRegions(); + $this->secondLevelCacheLogger->clearStats(); + $this->_em->clear(); + + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + + $criteria = array('name'=>$this->countries[0]->getName()); + $repository = $this->_em->getRepository(Country::CLASSNAME); + $queryCount = $this->getCurrentQueryCount(); + + $this->assertNotNull($repository->findOneBy($criteria)); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $queryCount = $this->getCurrentQueryCount(); + $country = $repository->findOneBy($criteria); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->assertInstanceOf(Country::CLASSNAME, $country); + + $this->assertEquals(1, $this->secondLevelCacheLogger->getHitCount()); + $this->assertEquals(1, $this->secondLevelCacheLogger->getMissCount()); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php new file mode 100644 index 00000000000..02accbaebcf --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php @@ -0,0 +1,279 @@ +_getEntityManager(); + + $em->getConnection()->getConfiguration()->setSQLLogger($logger); + $em->getConfiguration()->setSQLLogger($logger); + + return $em; + } + + /** + * @param \Doctrine\ORM\EntityManagerInterface $em + * @return integer + */ + public function countQuery(EntityManagerInterface $em) + { + return count($em->getConfiguration()->getSQLLogger()->queries); + } + + public function testFindEntityWithoutCache() + { + $em = $this->createEntityManager(); + + $this->findEntity($em, __FUNCTION__); + + $this->assertEquals(1002, $this->countQuery($em)); + } + + public function testFindEntityWithCache() + { + parent::enableSecondLevelCache(false); + + $em = $this->createEntityManager(); + + $this->findEntity($em, __FUNCTION__); + + $this->assertEquals(502, $this->countQuery($em)); + } + + public function testFindAllEntityWithoutCache() + { + $em = $this->createEntityManager(); + + $this->findAllEntity($em, __FUNCTION__); + + $this->assertEquals(152, $this->countQuery($em)); + } + + public function testFindAllEntityWithCache() + { + parent::enableSecondLevelCache(false); + + $em = $this->createEntityManager(); + + $this->findAllEntity($em, __FUNCTION__); + + $this->assertEquals(103, $this->countQuery($em)); + } + + public function testFindEntityOneToManyWithoutCache() + { + $em = $this->createEntityManager(); + + $this->findEntityOneToMany($em, __FUNCTION__); + + $this->assertEquals(502, $this->countQuery($em)); + } + + public function testFindEntityOneToManyWithCache() + { + parent::enableSecondLevelCache(false); + + $em = $this->createEntityManager(); + + $this->findEntityOneToMany($em, __FUNCTION__); + + $this->assertEquals(487, $this->countQuery($em)); + } + + public function testQueryEntityWithoutCache() + { + $em = $this->createEntityManager(); + + $this->queryEntity($em, __FUNCTION__); + + $this->assertEquals(602, $this->countQuery($em)); + } + + public function testQueryEntityWithCache() + { + parent::enableSecondLevelCache(false); + + $em = $this->createEntityManager(); + + $this->queryEntity($em, __FUNCTION__); + + $this->assertEquals(503, $this->countQuery($em)); + } + + private function queryEntity(EntityManagerInterface $em, $label) + { + $times = 100; + $size = 500; + $startPersist = microtime(true); + + echo PHP_EOL . $label; + + for ($i = 0; $i < $size; $i++) { + $em->persist(new Country("Country $i")); + } + + $em->flush(); + $em->clear(); + + printf("\n[%s] persist %s countries", number_format(microtime(true) - $startPersist, 6), $size); + + $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c WHERE c.name LIKE :name'; + $startFind = microtime(true); + + for ($i = 0; $i < $times; $i++) { + $em->createQuery($dql) + ->setParameter('name', "%Country%") + ->setCacheable(true) + ->getResult(); + } + + printf("\n[%s] select %s countries (%s times)", number_format(microtime(true) - $startFind, 6), $size, $times); + printf("\n%s\n", str_repeat('-', 50)); + } + + public function findEntityOneToMany(EntityManagerInterface $em, $label) + { + $times = 50; + $size = 30; + $states = array(); + $cities = array(); + $startPersist = microtime(true); + $country = new Country("Country"); + + echo PHP_EOL . $label; + + $em->persist($country); + $em->flush(); + + for ($i = 0; $i < $size / 2; $i++) { + $state = new State("State $i", $country); + + $em->persist($state); + + $states[] = $state; + } + + $em->flush(); + + foreach ($states as $key => $state) { + for ($i = 0; $i < $size; $i++) { + $city = new City("City $key - $i", $state); + + $em->persist($city); + + $state->addCity($city); + + $cities[] = $city; + } + } + + $em->flush(); + $em->clear(); + + printf("\n[%s] persist %s states and %s cities", number_format( microtime(true) - $startPersist, 6), count($states), count($cities)); + + $startFind = microtime(true); + + for ($i = 0; $i < $times; $i++) { + + foreach ($states as $state) { + + $state = $em->find(State::CLASSNAME, $state->getId()); + + foreach ($state->getCities() as $city) { + $city->getName(); + } + } + } + + printf("\n[%s] find %s states and %s cities (%s times)", number_format(microtime(true) - $startFind, 6), count($states), count($cities), $times); + printf("\n%s\n", str_repeat('-', 50)); + } + + private function findEntity(EntityManagerInterface $em, $label) + { + $times = 50; + $size = 500; + $countries = array(); + $startPersist = microtime(true); + + echo PHP_EOL . $label; + + for ($i = 0; $i < $size; $i++) { + $country = new Country("Country $i"); + + $em->persist($country); + + $countries[] = $country; + } + + $em->flush(); + $em->clear(); + + printf("\n[%s] persist %s countries", number_format(microtime(true) - $startPersist, 6), $size); + + $startFind = microtime(true); + + for ($i = 0; $i < $times; $i++) { + foreach ($countries as $country) { + $em->find(Country::CLASSNAME, $country->getId()); + } + } + + printf("\n[%s] find %s countries (%s times)", number_format(microtime(true) - $startFind, 6), $size, $times); + printf("\n%s\n", str_repeat('-', 50)); + } + + private function findAllEntity(EntityManagerInterface $em, $label) + { + $times = 50; + $size = 100; + $startPersist = microtime(true); + $rep = $em->getRepository(Country::CLASSNAME); + + echo PHP_EOL . $label; + + for ($i = 0; $i < $size; $i++) { + $em->persist(new Country("Country $i")); + } + + $em->flush(); + + printf("\n[%s] persist %s countries", number_format(microtime(true) - $startPersist, 6), $size); + + $startFind = microtime(true); + + for ($i = 0; $i < $times; $i++) { + $rep->findAll(); + } + + printf("\n[%s] find %s countries (%s times)", number_format(microtime(true) - $startFind, 6), $size, $times); + printf("\n%s\n", str_repeat('-', 50)); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php deleted file mode 100644 index dbcd7ae0c75..00000000000 --- a/tests/Doctrine/Tests/ORM/Performance/SencondLevelCacheTest.php +++ /dev/null @@ -1,191 +0,0 @@ -findEntity(__FUNCTION__); - } - - public function testFindEntityWithCache() - { - parent::enableSecondLevelCache(false); - - $this->findEntity(__FUNCTION__); - } - - public function testFindEntityOneToManyWithoutCache() - { - - $this->findEntityOneToMany(__FUNCTION__); - } - - public function testFindEntityOneToManyWithCache() - { - parent::enableSecondLevelCache(false); - - $this->findEntityOneToMany(__FUNCTION__); - } - - public function testQueryEntityWithoutCache() - { - $this->queryEntity(__FUNCTION__); - } - - public function testQueryEntityWithCache() - { - parent::enableSecondLevelCache(false); - - $this->queryEntity(__FUNCTION__); - } - - private function queryEntity($label) - { - $times = 500; - $size = 500; - $startPersist = microtime(true); - $em = $this->_getEntityManager(); - - for ($i = 0; $i < $size; $i++) { - $em->persist(new Country("Country $i")); - } - - $em->flush(); - $em->close(); - - printf("\n$label - [%s] persist %s countries\n", number_format(microtime(true) - $startPersist, 6), $size); - - $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c WHERE c.name = :name OR c.id < :count'; - $startFind = microtime(true); - - for ($i = 0; $i < $times; $i++) { - $em = $this->_getEntityManager(); - - $em->createQuery($dql) - ->setParameter('name', "Country $i") - ->setParameter('count', $i) - ->setCacheable(true) - ->getResult(); - } - - printf("$label - [%s] select %s countries (%s times)\n", number_format(microtime(true) - $startFind, 6), $size, $times); - } - - public function findEntityOneToMany($label) - { - $times = 50; - $size = 30; - $states = array(); - $cities = array(); - $startPersist = microtime(true); - $em = $this->_getEntityManager(); - $country = new Country("Country"); - - $em->persist($country); - $em->flush(); - - for ($i = 0; $i < $size / 2; $i++) { - $state = new State("State $i", $country); - - $em->persist($state); - - $states[] = $state; - } - - $em->flush(); - - foreach ($states as $key => $state) { - for ($i = 0; $i < $size; $i++) { - $city = new City("City $key - $i", $state); - - $em->persist($city); - - $state->addCity($city); - - $cities[] = $city; - } - } - - $em->flush(); - $em->clear(); - - $endPersist = microtime(true); - $format = "\n$label - [%s] persist %s states and %s cities\n"; - - printf($format, number_format($endPersist - $startPersist, 6), count($states), count($cities)); - - $startFind = microtime(true); - - for ($i = 0; $i < $times; $i++) { - - $em = $this->_getEntityManager(); - - foreach ($states as $state) { - - $state = $em->find(State::CLASSNAME, $state->getId()); - - foreach ($state->getCities() as $city) { - $city->getName(); - } - } - } - - $endFind = microtime(true); - $format = "$label - [%s] find %s states and %s cities (%s times)\n"; - - printf($format, number_format($endFind - $startFind, 6), count($states), count($cities), $times); - } - - private function findEntity($label) - { - $times = 50; - $size = 500; - $countries = array(); - $startPersist = microtime(true); - $em = $this->_getEntityManager(); - - for ($i = 0; $i < $size; $i++) { - $country = new Country("Country $i"); - - $em->persist($country); - - $countries[] = $country; - } - - $em->flush(); - $em->close(); - - printf("\n$label - [%s] persist %s countries \n", number_format(microtime(true) - $startPersist, 6), $size); - - $startFind = microtime(true); - - for ($i = 0; $i < $times; $i++) { - $em = $this->_getEntityManager(); - - foreach ($countries as $country) { - $em->find(Country::CLASSNAME, $country->getId()); - } - } - - printf("$label - [%s] find %s countries (%s times)\n", number_format(microtime(true) - $startFind, 6), $size, $times); - } -} \ No newline at end of file From b8f368e5e50b2c8b1f285dbb8a291d6343e5d9c9 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 28 Jul 2013 14:14:03 -0400 Subject: [PATCH 57/71] Run test suite using second-level-cache --- .travis.yml | 9 +- .../Cache/DefaultCollectionEntryStructure.php | 6 +- .../ORM/Cache/DefaultEntityEntryStructure.php | 36 +++++++- lib/Doctrine/ORM/Cache/EntityCacheKey.php | 2 +- .../ORM/Persisters/BasicEntityPersister.php | 19 ++--- .../Persisters/CachedCollectionPersister.php | 37 ++++++++- .../ORM/Persisters/CachedEntityPersister.php | 30 ++++--- lib/Doctrine/ORM/UnitOfWork.php | 16 +++- .../EventListener/CacheMetadataListener.php | 35 ++++++++ .../Tests/ORM/Cache/DefaultCacheTest.php | 22 +++-- .../Functional/AdvancedAssociationTest.php | 3 + .../ORM/Functional/DefaultValuesTest.php | 3 + .../ORM/Functional/DetachedEntityTest.php | 3 + .../Functional/ExtraLazyCollectionTest.php | 6 ++ .../JoinedTableCompositeKeyTest.php | 2 +- .../ORM/Functional/MappedSuperclassTest.php | 1 + .../Functional/OneToOneEagerLoadingTest.php | 9 ++ .../Tests/ORM/Functional/SQLFilterTest.php | 2 + .../SecondLevelCacheConcurrentTest.php | 2 +- ...econdLevelCacheExtraLazyCollectionTest.php | 82 +++++++++++++++++++ .../SecondLevelCacheManyToManyTest.php | 5 +- .../SecondLevelCacheOneToManyTest.php | 45 +++++++++- .../SecondLevelCacheQueryCacheTest.php | 5 +- .../ORM/Functional/SecondLevelCacheTest.php | 24 ++++-- .../ORM/Functional/Ticket/DDC117Test.php | 8 ++ .../ORM/Functional/Ticket/DDC1301Test.php | 2 + .../ORM/Functional/Ticket/DDC1595Test.php | 1 + .../ORM/Functional/Ticket/DDC2090Test.php | 1 + .../ORM/Functional/Ticket/DDC2494Test.php | 1 + .../ORM/Functional/Ticket/DDC742Test.php | 3 + .../Doctrine/Tests/OrmFunctionalTestCase.php | 43 ++-------- tests/Doctrine/Tests/OrmTestCase.php | 57 +++++++++++-- 32 files changed, 415 insertions(+), 105 deletions(-) create mode 100644 tests/Doctrine/Tests/EventListener/CacheMetadataListener.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheExtraLazyCollectionTest.php diff --git a/.travis.yml b/.travis.yml index adbf643c2a9..9f3d6e6ab6c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,9 +6,12 @@ php: - 5.5 env: - - DB=mysql - - DB=pgsql - - DB=sqlite + - DB=mysql ENABLE_SECOND_LEVEL_CACHE=1 + - DB=pgsql ENABLE_SECOND_LEVEL_CACHE=1 + - DB=sqlite ENABLE_SECOND_LEVEL_CACHE=1 + - DB=mysql ENABLE_SECOND_LEVEL_CACHE=0 + - DB=pgsql ENABLE_SECOND_LEVEL_CACHE=0 + - DB=sqlite ENABLE_SECOND_LEVEL_CACHE=0 before_script: - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS doctrine_tests;' -U postgres; fi" diff --git a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php index 4bbce26122f..b7e30007db2 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php @@ -78,14 +78,14 @@ public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key */ public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection) { - $targetEntity = $metadata->associationMappings[$key->association]['targetEntity']; - $targetPersister = $this->uow->getEntityPersister($targetEntity); + $assoc = $metadata->associationMappings[$key->association]; + $targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']); $targetRegion = $targetPersister->getCacheRegionAcess()->getRegion(); $list = array(); foreach ($entry->identifiers as $index => $identifier) { - $entityEntry = $targetRegion->get(new EntityCacheKey($targetEntity, $identifier)); + $entityEntry = $targetRegion->get(new EntityCacheKey($assoc['targetEntity'], $identifier)); if ($entityEntry === null) { return null; diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php index 33b3948d876..734fe743e8f 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -73,13 +73,37 @@ public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $e continue; } - if (isset($assoc['cache']) && $assoc['type'] & ClassMetadata::TO_ONE && ( ! $data[$name] instanceof Proxy || $data[$name]->__isInitialized__)) { + if ( ! isset($assoc['cache']) || + ($assoc['type'] & ClassMetadata::TO_ONE) === 0 || + ($data[$name] instanceof Proxy && ! $data[$name]->__isInitialized__)) { + + unset($data[$name]); + + continue; + } + + if ( ! isset($assoc['id'])) { $data[$name] = $this->uow->getEntityIdentifier($data[$name]); continue; } - unset($data[$name]); + // handle association identifier + $targetId = is_object($data[$name]) && $this->em->getMetadataFactory()->hasMetadataFor(get_class($data[$name])) + ? $this->uow->getEntityIdentifier($data[$name]) + : $data[$name]; + + // @TODO - fix it ! + // hande UnitOfWork#createEntity hash generation + if ( ! is_array($targetId)) { + + $data[reset($assoc['joinColumnFieldNames'])] = $targetId; + + $targetEntity = $this->em->getClassMetadata($assoc['targetEntity']); + $targetId = array($targetEntity->identifier[0] => $targetId); + } + + $data[$name] = $targetId; } return new EntityCacheEntry($metadata->name, $data); @@ -117,7 +141,13 @@ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, Ent return null; } - $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints); + $data[$name] = $assoc['fetch'] === ClassMetadata::FETCH_EAGER + ? $this->uow->createEntity($assocEntry->class, $assocEntry->data, $hints) + : $this->em->getReference($assocEntry->class, $assocId); + } + + if ($entity !== null) { + $this->uow->registerManaged($entity, $key->identifier, $data); } return $this->uow->createEntity($entry->class, $data, $hints); diff --git a/lib/Doctrine/ORM/Cache/EntityCacheKey.php b/lib/Doctrine/ORM/Cache/EntityCacheKey.php index a44fe4e6a4d..969cb8c9508 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheKey.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheKey.php @@ -61,7 +61,7 @@ public function hash() if ($this->hash === null) { ksort($this->identifier); - return sprintf('%s[%s]', str_replace('\\', '.', strtolower($this->entityClass)) , implode(' ', $this->identifier)); + $this->hash = sprintf('%s[%s]', str_replace('\\', '.', strtolower($this->entityClass)) , implode(' ', $this->identifier)); } return $this->hash; diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index cd871460036..c148eb19e85 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -546,14 +546,16 @@ protected function deleteJoinTableRecords($identifier) */ public function delete($entity) { - $em = $this->em; $class = $this->class; + $em = $this->em; + $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity); $tableName = $this->quoteStrategy->getTableName($class, $this->platform); $idColumns = $this->quoteStrategy->getIdentifierColumnNames($class, $this->platform); $id = array_combine($idColumns, $identifier); $types = array_map(function ($identifier) use ($class, $em) { - if (isset($class->fieldMappings[$identifier])) { + + if (isset($class->fieldMappings[$identifier])) { return $class->fieldMappings[$identifier]['type']; } @@ -571,17 +573,6 @@ public function delete($entity) }, $class->identifier); - $cacheKey = null; - $cacheLock = null; - - if ($this->hasCache) { - $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); - } - - if ($this->isConcurrentRegion) { - $cacheLock = $this->cacheRegionAccess->lockItem(); - } - $this->deleteJoinTableRecords($identifier); $this->conn->delete($tableName, $id, $types); } @@ -738,7 +729,7 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint */ public function loadById(array $identifier, $entity = null) { - return $this->load($identifier, $entity);; + return $this->load($identifier, $entity); } /** diff --git a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php index 7e5ca732895..5b7a51372bc 100644 --- a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php @@ -264,6 +264,14 @@ public function containsKey(PersistentCollection $collection, $key) */ public function count(PersistentCollection $collection) { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + $entry = $this->cacheRegionAccess->get($key); + + if ($entry !== null) { + return count($entry->identifiers); + } + return $this->persister->count($collection); } @@ -294,6 +302,13 @@ public function delete(PersistentCollection $collection) */ public function update(PersistentCollection $collection) { + $isInitialized = $collection->isInitialized(); + $isDirty = $collection->isDirty(); + + if ( ! $isInitialized && ! $isDirty) { + return; + } + $lock = null; $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); @@ -302,13 +317,27 @@ public function update(PersistentCollection $collection) $lock = $this->cacheRegionAccess->lockItem($key); } - $this->persister->update($collection); - - $this->queuedCache['update'][spl_object_hash($collection)] = array( - 'list' => $collection, + $data = array( + 'list' => null, 'key' => $key, 'lock' => $lock ); + + // Invalidate non initialized collections OR odered collection + if ($isDirty && ! $isInitialized || isset($this->association['orderBy'])) { + + $this->persister->update($collection); + + $this->queuedCache['delete'][spl_object_hash($collection)] = $data; + + return; + } + + $this->persister->update($collection); + + $data['list'] = $collection; + + $this->queuedCache['update'][spl_object_hash($collection)] = $data; } /** diff --git a/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php b/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php index 46a39a8d3f9..3213c675c2e 100644 --- a/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php @@ -23,6 +23,8 @@ use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\ConcurrentRegionAccess; +use Doctrine\ORM\Cache\QueryCacheKey; +use Doctrine\ORM\Cache; use Doctrine\Common\Util\ClassUtils; use Doctrine\Common\Collections\Criteria; @@ -31,9 +33,6 @@ use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Cache\QueryCacheKey; -use Doctrine\ORM\Cache; - /** * @author Fabio B. Silva * @since 2.5 @@ -133,7 +132,7 @@ public function getInserts() */ public function getSelectSQL($criteria, $assoc = null, $lockMode = 0, $limit = null, $offset = null, array $orderBy = null) { - return $this->persister->getSelectSQL($orderBy, $assoc, $lockMode, $limit, $offset, $orderBy); + return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy); } /** @@ -311,7 +310,7 @@ public function exists($entity, array $extraConditions = array()) if (empty($extraConditions)) { $region = $this->cacheRegionAccess->getRegion(); - $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity)); if ($region->contains($key)) { return true; @@ -440,7 +439,10 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint return $result[0]; } - $result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy); + if(($result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy)) === null) { + return null; + } + $cached = $queryCache->put($querykey, $rsm, array($result)); if ($this->cacheLogger && $result) { @@ -496,14 +498,22 @@ public function loadById(array $identifier, $entity = null) { $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); $cacheEntry = $this->cacheRegionAccess->get($cacheKey); + $class = $this->class; - if ($cacheEntry !== null && ($entity = $this->cacheEntryStructure->loadCacheEntry($this->class, $cacheKey, $cacheEntry, $entity)) !== null) { + if ($cacheEntry !== null) { - if ($this->cacheLogger) { - $this->cacheLogger->entityCacheHit($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + if ($cacheEntry->class !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($cacheEntry->class); } - return $entity; + if (($entity = $this->cacheEntryStructure->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) { + + if ($this->cacheLogger) { + $this->cacheLogger->entityCacheHit($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + } + + return $entity; + } } $entity = $this->persister->loadById($identifier, $entity); diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index b479d879c5c..bb66888ad80 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -409,6 +409,10 @@ public function commit($entity = null) throw $e; } + foreach ($this->cachedPersisters as $persister) { + $persister->afterTransactionComplete(); + } + // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { $coll->takeSnapshot(); @@ -416,10 +420,6 @@ public function commit($entity = null) $this->dispatchPostFlushEvent(); - foreach ($this->cachedPersisters as $persister) { - $persister->afterTransactionComplete(); - } - // Clear up $this->entityInsertions = $this->entityUpdates = @@ -2651,6 +2651,14 @@ public function createEntity($className, array $data, &$hints = array()) continue 2; } + // use the entity association + if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) { + $class->reflFields[$field]->setValue($entity, $data[$field]); + $this->originalEntityData[$oid][$field] = $data[$field]; + + continue; + } + $associatedId = array(); // TODO: Is this even computed right in all cases of composite keys? diff --git a/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php new file mode 100644 index 00000000000..9f5f4cc3971 --- /dev/null +++ b/tests/Doctrine/Tests/EventListener/CacheMetadataListener.php @@ -0,0 +1,35 @@ +getClassMetadata(); + $cache = array( + 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE + ); + + /* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ + if (strstr($metadata->name, 'Doctrine\Tests\Models\Cache')) { + return; + } + + if ($metadata->isVersioned) { + return; + } + + $metadata->enableCache($cache); + + foreach ($metadata->associationMappings as $mapping) { + $metadata->enableAssociationCache($mapping['fieldName'], $cache); + } + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index 5b9742d1594..b1d27fc8ad1 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -2,7 +2,7 @@ namespace Doctrine\Tests\ORM\Cache; -use Doctrine\Tests\OrmFunctionalTestCase; +use Doctrine\Tests\OrmTestCase; use Doctrine\ORM\Cache\DefaultCache; use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Country; @@ -14,19 +14,25 @@ /** * @group DDC-2183 */ -class DefaultCacheTest extends OrmFunctionalTestCase +class DefaultCacheTest extends OrmTestCase { /** * @var \Doctrine\ORM\Cache */ private $cache; + /** + * @var \Doctrine\ORM\EntityManagerInterface + */ + private $em; + protected function setUp() { parent::enableSecondLevelCache(); parent::setUp(); - $this->cache = new DefaultCache($this->_em); + $this->em = $this->_getTestEntityManager(); + $this->cache = new DefaultCache($this->em); } /** @@ -36,10 +42,10 @@ protected function setUp() */ private function putEntityCacheEntry($className, array $identifier, array $data) { - $metadata = $this->_em->getClassMetadata($className); + $metadata = $this->em->getClassMetadata($className); $cacheKey = new EntityCacheKey($metadata->name, $identifier); $cacheEntry = new EntityCacheEntry($metadata->name, $data); - $persister = $this->_em->getUnitOfWork()->getEntityPersister($metadata->rootEntityName); + $persister = $this->em->getUnitOfWork()->getEntityPersister($metadata->rootEntityName); $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); } @@ -52,10 +58,10 @@ private function putEntityCacheEntry($className, array $identifier, array $data) */ private function putCollectionCacheEntry($className, $association, array $ownerIdentifier, array $data) { - $metadata = $this->_em->getClassMetadata($className); + $metadata = $this->em->getClassMetadata($className); $cacheKey = new CollectionCacheKey($metadata->name, $association, $ownerIdentifier); $cacheEntry = new CollectionCacheEntry($data); - $persister = $this->_em->getUnitOfWork()->getCollectionPersister($metadata->getAssociationMapping($association)); + $persister = $this->em->getUnitOfWork()->getCollectionPersister($metadata->getAssociationMapping($association)); $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); } @@ -213,7 +219,7 @@ public function testToIdentifierArrayShoudLookupForEntityIdentifier() { $identifier = 123; $entity = new Country('Foo'); - $metadata = $this->_em->getClassMetadata(Country::CLASSNAME); + $metadata = $this->em->getClassMetadata(Country::CLASSNAME); $method = new \ReflectionMethod($this->cache, 'toIdentifierArray'); $property = new \ReflectionProperty($entity, 'id'); diff --git a/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php index f1527967266..7d01ddd52da 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php @@ -28,6 +28,9 @@ protected function setUp() { } } + /** + * @group non-cacheable + */ public function testIssue() { //setup diff --git a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php index 323d8bf4ab5..dc148707c37 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DefaultValuesTest.php @@ -23,6 +23,9 @@ protected function setUp() { } } + /** + * @group non-cacheable + */ public function testSimpleDetachMerge() { $user = new DefaultValueUser; $user->name = 'romanb'; diff --git a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php index f5c50eb1196..c797de87bcb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php @@ -118,6 +118,9 @@ public function testDetachedEntityThrowsExceptionOnFlush() } catch (\Exception $expected) {} } + /** + * @group non-cacheable + */ public function testUninitializedLazyAssociationsAreIgnoredOnMerge() { $user = new CmsUser; diff --git a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php index eb4dbe4d877..f6c8160372c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/ExtraLazyCollectionTest.php @@ -58,6 +58,7 @@ public function tearDown() /** * @group DDC-546 + * @group non-cacheable */ public function testCountNotInitializesCollection() { @@ -93,6 +94,7 @@ public function testCountWhenNewEntityPresent() /** * @group DDC-546 + * @group non-cacheable */ public function testCountWhenInitialized() { @@ -143,6 +145,7 @@ public function testFullSlice() /** * @group DDC-546 + * @group non-cacheable */ public function testSlice() { @@ -173,6 +176,7 @@ public function testSlice() /** * @group DDC-546 + * @group non-cacheable */ public function testSliceInitializedCollection() { @@ -505,6 +509,7 @@ public function testCountAfterAddThenFlush() /** * @group DDC-1462 + * @group non-cacheable */ public function testSliceOnDirtyCollection() { @@ -526,6 +531,7 @@ public function testSliceOnDirtyCollection() /** * @group DDC-1398 + * @group non-cacheable */ public function testGetIndexByIdentifier() { diff --git a/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php b/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php index 3bc5e25adb8..da0d3bc6462 100644 --- a/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php @@ -30,7 +30,7 @@ public function testInsertWithCompositeKey() } /** - * + *@group non-cacheable */ public function testUpdateWithCompositeKey() { diff --git a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php index f4439b9ad06..eeeed05c6b6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php @@ -8,6 +8,7 @@ * MappedSuperclassTest * * @author robo + * @group non-cacheable */ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php index db62fa1baec..ad00c3993c0 100644 --- a/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/OneToOneEagerLoadingTest.php @@ -117,6 +117,9 @@ public function testEagerLoadManyToOne() $this->assertNotNull($waggon->train); } + /** + * @group non-cacheable + */ public function testEagerLoadWithNullableColumnsGeneratesLeftJoinOnBothSides() { $train = new Train(new TrainOwner("Alexander")); @@ -141,6 +144,9 @@ public function testEagerLoadWithNullableColumnsGeneratesLeftJoinOnBothSides() ); } + /** + * @group non-cacheable + */ public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoinOnOwningSide() { $waggon = new Waggon(); @@ -168,6 +174,9 @@ public function testEagerLoadWithNonNullableColumnsGeneratesInnerJoinOnOwningSid ); } + /** + * @group non-cacheable + */ public function testEagerLoadWithNonNullableColumnsGeneratesLeftJoinOnNonOwningSide() { $owner = new TrainOwner('Alexander'); diff --git a/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php b/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php index 1f94d86a3ba..4e9390cc6f9 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SQLFilterTest.php @@ -31,6 +31,8 @@ * Tests SQLFilter functionality. * * @author Alexander + * + * @group non-cacheable */ class SQLFilterTest extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php index a7154d1d5a8..8782bcf27df 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php @@ -35,7 +35,7 @@ protected function setUp() $this->enableSecondLevelCache(); parent::setUp(); - $this->cacheFactory = new CacheFactorySecondLevelCacheConcurrentTest(self::getSharedSecondLevelCacheDriverImpl()); + $this->cacheFactory = new CacheFactorySecondLevelCacheConcurrentTest($this->getSharedSecondLevelCacheDriverImpl()); $this->_em->getConfiguration()->setSecondLevelCacheFactory($this->cacheFactory); } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheExtraLazyCollectionTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheExtraLazyCollectionTest.php new file mode 100644 index 00000000000..b96c41e1f26 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheExtraLazyCollectionTest.php @@ -0,0 +1,82 @@ +_em->getClassMetadata(Travel::CLASSNAME); + $targetEntity = $this->_em->getClassMetadata(City::CLASSNAME); + + $sourceEntity->associationMappings['visitedCities']['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; + $targetEntity->associationMappings['travels']['fetch'] = ClassMetadata::FETCH_EXTRA_LAZY; + } + + public function tearDown() + { + parent::tearDown(); + + $sourceEntity = $this->_em->getClassMetadata(Travel::CLASSNAME); + $targetEntity = $this->_em->getClassMetadata(City::CLASSNAME); + + $sourceEntity->associationMappings['visitedCities']['fetch'] = ClassMetadata::FETCH_LAZY; + $targetEntity->associationMappings['travels']['fetch'] = ClassMetadata::FETCH_LAZY; + } + + public function testCacheCountAfterAddThenFlush() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesTraveler(); + $this->loadFixturesTravels(); + + $this->_em->clear(); + + $ownerId = $this->travels[0]->getId(); + $owner = $this->_em->find(Travel::CLASSNAME, $ownerId); + $ref = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $ownerId)); + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $ownerId)); + + $newItem = new City("New City", $ref); + $owner->getVisitedCities()->add($newItem); + + $this->_em->persist($newItem); + $this->_em->persist($owner); + + $queryCount = $this->getCurrentQueryCount(); + + $this->assertFalse($owner->getVisitedCities()->isInitialized()); + $this->assertEquals(4, $owner->getVisitedCities()->count()); + $this->assertFalse($owner->getVisitedCities()->isInitialized()); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->_em->flush(); + + $this->assertFalse($owner->getVisitedCities()->isInitialized()); + $this->assertFalse($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $ownerId)); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $owner = $this->_em->find(Travel::CLASSNAME, $ownerId); + + $this->assertEquals(4, $owner->getVisitedCities()->count()); + $this->assertFalse($owner->getVisitedCities()->isInitialized()); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php index c6eb014ebb4..ac18bfe0d03 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -42,12 +42,9 @@ public function testPutAndLoadManyToManyRelation() $this->loadFixturesTravels(); $this->_em->clear(); + $this->evictRegions(); $this->secondLevelCacheLogger->clearStats(); - $this->cache->evictEntityRegion(Travel::CLASSNAME); - $this->cache->evictEntityRegion(City::CLASSNAME); - $this->cache->evictCollectionRegion(Travel::CLASSNAME, 'visitedCities'); - $this->assertFalse($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[0]->getId())); $this->assertFalse($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[1]->getId())); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php index 6fa5da60c84..931ba88ba17 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheOneToManyTest.php @@ -5,6 +5,8 @@ use Doctrine\Tests\Models\Cache\City; use Doctrine\Tests\Models\Cache\State; +use Doctrine\Tests\Models\Cache\Travel; +use Doctrine\Tests\Models\Cache\Traveler; /** * @group DDC-2183 @@ -167,7 +169,7 @@ public function testLoadOnoToManyCollectionFromDatabaseWhenEntityMissing() public function testShoudNotPutOneToManyRelationOnPersist() { $this->loadFixturesCountries(); - $this->cache->evictEntityRegion(State::CLASSNAME); + $this->evictRegions(); $state = new State("State Foo", $this->countries[0]); @@ -305,4 +307,45 @@ public function testOneToManyCount() $this->assertEquals(2, $entity->getCities()->count()); $this->assertEquals($queryCount, $this->getCurrentQueryCount()); } + + public function testCacheInitializeCollectionWithNewObjects() + { + $this->_em->clear(); + $this->evictRegions(); + + $traveler = new Traveler("Doctrine Bot"); + + for ($i=0; $i<3; ++$i) { + $traveler->getTravels()->add(new Travel($traveler)); + } + + $this->_em->persist($traveler); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertCount(3, $traveler->getTravels()); + + $travelerId = $traveler->getId(); + $queryCount = $this->getCurrentQueryCount(); + $entity = $this->_em->find(Traveler::CLASSNAME, $travelerId); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + $this->assertFalse($entity->getTravels()->isInitialized()); + + $newItem = new Travel($entity); + $entity->getTravels()->add($newItem); + + $this->assertFalse($entity->getTravels()->isInitialized()); + $this->assertCount(4, $entity->getTravels()); + $this->assertTrue($entity->getTravels()->isInitialized()); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->_em->flush(); + $this->_em->clear(); + + $query = "SELECT t, tt FROM Doctrine\Tests\Models\Cache\Traveler t JOIN t.travels tt WHERE t.id = $travelerId"; + $result = $this->_em->createQuery($query)->getSingleResult(); + + $this->assertEquals(4, $result->getTravels()->count()); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 9b873feecbf..5d0ee6ec130 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -740,9 +740,12 @@ public function testQueryCacheRegion() */ public function testQueryNotCacheableEntityException() { + $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Generic\BooleanModel'); + $metadata->cache = null; + try { $this->_schemaTool->createSchema(array( - $this->_em->getClassMetadata('Doctrine\Tests\Models\Generic\BooleanModel'), + $metadata, )); } catch (\Doctrine\ORM\Tools\ToolsException $exc) { } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php index 3d4729abab1..37dadd26ce7 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheTest.php @@ -196,7 +196,7 @@ public function testUpdateEntities() public function testPostFlushFailure() { $listener = new ListenerSecondLevelCacheTest(array(Events::postFlush => function(){ - throw new \RuntimeException('pre insert failure'); + throw new \RuntimeException('post flush failure'); })); $this->_em->getEventManager() @@ -207,15 +207,15 @@ public function testPostFlushFailure() $this->cache->evictEntityRegion(Country::CLASSNAME); try { - + $this->_em->persist($country); $this->_em->flush(); $this->fail('Should throw exception'); } catch (\RuntimeException $exc) { $this->assertNotNull($country->getId()); - $this->assertEquals('pre insert failure', $exc->getMessage()); - $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $country->getId())); + $this->assertEquals('post flush failure', $exc->getMessage()); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $country->getId())); } } @@ -301,6 +301,20 @@ public function testPostRemoveFailure() $country = $this->_em->find(Country::CLASSNAME, $countryId); $this->assertInstanceOf(Country::CLASSNAME, $country); } + + public function testCachedNewEntityExists() + { + $this->loadFixturesCountries(); + + $persister = $this->_em->getUnitOfWork()->getEntityPersister(Country::CLASSNAME); + $queryCount = $this->getCurrentQueryCount(); + + $this->assertTrue($persister->exists($this->countries[0])); + + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $this->assertFalse($persister->exists(new Country('Foo'))); + } } @@ -334,4 +348,4 @@ public function postRemove($args) { $this->dispatch(__FUNCTION__, $args); } -} \ No newline at end of file +} diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php index 6673afd1a4f..17ecbafd6e8 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC117Test.php @@ -12,6 +12,9 @@ require_once __DIR__ . '/../../../TestInit.php'; +/** + * @group DDC-117 + */ class DDC117Test extends \Doctrine\Tests\OrmFunctionalTestCase { private $article1; @@ -138,6 +141,7 @@ public function testRemoveCompositeElement() /** * @group DDC-117 + * @group non-cacheable */ public function testDqlRemoveCompositeElement() { @@ -471,6 +475,10 @@ public function testArrayHydrationWithCompositeKey() */ public function testGetEntityState() { + if ($this->isSecondLevelCacheEnabled) { + $this->markTestIncomplete('Second level cache - not supported yet'); + } + $this->article1 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article1->id()); $this->article2 = $this->_em->find("Doctrine\Tests\Models\DDC117\DDC117Article", $this->article2->id()); diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php index 94d02f905a8..36897cc083c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php @@ -8,6 +8,8 @@ /** * @author asm89 + * + * @group non-cacheable */ class DDC1301Test extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php index e6bc589426c..2c631750481 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1595Test.php @@ -5,6 +5,7 @@ /** * @group DDC-1595 * @group DDC-1596 + * @group non-cacheable */ class DDC1595Test extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2090Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2090Test.php index c5da3deaeb4..cd6db885712 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2090Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2090Test.php @@ -6,6 +6,7 @@ /** * @group DDC-2090 + * @group non-cacheable */ class DDC2090Test extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2494Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2494Test.php index 1b2ca881edf..b5a09f9223a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2494Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2494Test.php @@ -7,6 +7,7 @@ /** * @group DDC-2494 + * @group non-cacheable */ class DDC2494Test extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php index 94936a12587..334c2c6235c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC742Test.php @@ -6,6 +6,9 @@ require_once __DIR__ . '/../../../TestInit.php'; +/** + * @group non-cacheable + */ class DDC742Test extends \Doctrine\Tests\OrmFunctionalTestCase { private $userCm; diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 03eacccd8f3..1e9e2538d96 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -4,7 +4,6 @@ use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; use Doctrine\ORM\Cache\DefaultCacheFactory; -use Doctrine\Common\Cache\CacheProvider; /** * Base testcase class for all functional ORM testcases. @@ -13,26 +12,6 @@ */ abstract class OrmFunctionalTestCase extends OrmTestCase { - /** - * @var boolean - */ - private $isSecondLevelCacheEnabled = false; - - /** - * @var boolean - */ - private $isSecondLevelCacheLogEnabled = false; - - /** - * @var \Doctrine\ORM\Cache\CacheFactory - */ - private $secondLevelCacheFactory; - - /** - * @var \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger - */ - protected $secondLevelCacheLogger; - /** * The metadata cache shared between all functional tests. * @@ -348,13 +327,7 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM cache_country'); } - $this->_em->clear(); - - if($this->isSecondLevelCacheEnabled && self::$sharedSecondLevelCacheDriverImpl instanceof CacheProvider) { - self::$sharedSecondLevelCacheDriverImpl->flushAll(); - } - } /** @@ -468,7 +441,9 @@ protected function _getEntityManager($config = null, $eventManager = null) { $config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies'); - if ($this->isSecondLevelCacheEnabled) { + $enableSecondLevelCache = getenv('ENABLE_SECOND_LEVEL_CACHE'); + + if ($this->isSecondLevelCacheEnabled || $enableSecondLevelCache) { $cache = self::getSharedSecondLevelCacheDriverImpl(); $factory = new DefaultCacheFactory($config, $cache); @@ -482,6 +457,8 @@ protected function _getEntityManager($config = null, $eventManager = null) { $config->setSecondLevelCacheEnabled(); $config->setSecondLevelCacheFactory($factory); + + $this->isSecondLevelCacheEnabled = true; } $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array( @@ -499,6 +476,10 @@ protected function _getEntityManager($config = null, $eventManager = null) { } } + if ( ! $this->isSecondLevelCacheEnabled && $enableSecondLevelCache) { + $evm->addEventListener('loadClassMetadata', new EventListener\CacheMetadataListener(get_called_class())); + } + if (isset($GLOBALS['db_event_subscribers'])) { foreach (explode(",", $GLOBALS['db_event_subscribers']) AS $subscriberClass) { $subscriberInstance = new $subscriberClass(); @@ -513,12 +494,6 @@ protected function _getEntityManager($config = null, $eventManager = null) { return \Doctrine\ORM\EntityManager::create($conn, $config); } - protected function enableSecondLevelCache($log = true) - { - $this->isSecondLevelCacheEnabled = true; - $this->isSecondLevelCacheLogEnabled = $log; - } - /** * @param \Exception $e * diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 315264693bd..143bc6d54f7 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -3,16 +3,13 @@ namespace Doctrine\Tests; use Doctrine\Common\Cache\ArrayCache; +use Doctrine\ORM\Cache\DefaultCacheFactory; /** * Base testcase class for all ORM testcases. */ abstract class OrmTestCase extends DoctrineTestCase { - /** - * @var \Doctrine\Common\Cache\Cache|null - */ - protected static $sharedSecondLevelCacheDriverImpl = null; /** * The metadata cache that is shared between all ORM tests (except functional tests). @@ -28,6 +25,31 @@ abstract class OrmTestCase extends DoctrineTestCase */ private static $_queryCacheImpl = null; + /** + * @var boolean + */ + protected $isSecondLevelCacheEnabled = false; + + /** + * @var boolean + */ + protected $isSecondLevelCacheLogEnabled = false; + + /** + * @var \Doctrine\ORM\Cache\CacheFactory + */ + protected $secondLevelCacheFactory; + + /** + * @var \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger + */ + protected $secondLevelCacheLogger; + + /** + * @var \Doctrine\Common\Cache\Cache|null + */ + protected $secondLevelCacheDriverImpl = null; + /** * @param array $paths * @param mixed $alias @@ -100,6 +122,19 @@ protected function _getTestEntityManager($conn = null, $conf = null, $eventManag $config->setQueryCacheImpl(self::getSharedQueryCacheImpl()); $config->setProxyDir(__DIR__ . '/Proxies'); $config->setProxyNamespace('Doctrine\Tests\Proxies'); + $config->setMetadataDriverImpl($config->newDefaultAnnotationDriver(array( + realpath(__DIR__ . '/Models/Cache') + ), true)); + + if ($this->isSecondLevelCacheEnabled) { + $cache = self::getSharedSecondLevelCacheDriverImpl(); + $factory = new DefaultCacheFactory($config, $cache); + + $this->secondLevelCacheFactory = $factory; + + $config->setSecondLevelCacheEnabled(); + $config->setSecondLevelCacheFactory($factory); + } if ($conn === null) { $conn = array( @@ -117,6 +152,12 @@ protected function _getTestEntityManager($conn = null, $conf = null, $eventManag return \Doctrine\Tests\Mocks\EntityManagerMock::create($conn, $config, $eventManager); } + protected function enableSecondLevelCache($log = true) + { + $this->isSecondLevelCacheEnabled = true; + $this->isSecondLevelCacheLogEnabled = $log; + } + /** * @return \Doctrine\Common\Cache\Cache */ @@ -144,12 +185,12 @@ private static function getSharedQueryCacheImpl() /** * @return \Doctrine\Common\Cache\Cache */ - protected static function getSharedSecondLevelCacheDriverImpl() + protected function getSharedSecondLevelCacheDriverImpl() { - if (self::$sharedSecondLevelCacheDriverImpl === null) { - self::$sharedSecondLevelCacheDriverImpl = new \Doctrine\Common\Cache\ArrayCache; + if ($this->secondLevelCacheDriverImpl === null) { + $this->secondLevelCacheDriverImpl = new \Doctrine\Common\Cache\ArrayCache(); } - return self::$sharedSecondLevelCacheDriverImpl; + return $this->secondLevelCacheDriverImpl; } } From 59ff67600eab8404ec34cabcec1a657312cad71f Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 1 Aug 2013 16:24:25 -0400 Subject: [PATCH 58/71] Improve code coverage --- .../ORM/Cache/DefaultEntityEntryStructure.php | 4 - lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 12 +- .../Doctrine/Tests/Mocks/CacheRegionMock.php | 77 +++ tests/Doctrine/Tests/Models/Cache/City.php | 2 +- tests/Doctrine/Tests/Models/Cache/Flight.php | 65 +++ tests/Doctrine/Tests/Models/Cache/State.php | 2 +- .../Tests/ORM/Cache/DefaultCacheTest.php | 34 +- .../Tests/ORM/Cache/DefaultQueryCacheTest.php | 478 ++++++++++++++++++ ...SecondLevelCacheCompositPrimaryKeyTest.php | 176 +++++++ .../SecondLevelCacheManyToOneTest.php | 25 + .../SecondLevelCacheQueryCacheTest.php | 150 +++--- .../Doctrine/Tests/OrmFunctionalTestCase.php | 2 + 12 files changed, 953 insertions(+), 74 deletions(-) create mode 100644 tests/Doctrine/Tests/Mocks/CacheRegionMock.php create mode 100644 tests/Doctrine/Tests/Models/Cache/Flight.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheCompositPrimaryKeyTest.php diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php index 734fe743e8f..14549ecbf38 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php @@ -128,10 +128,6 @@ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, Ent continue; } - if ( ! $assoc['type'] & ClassMetadata::TO_ONE) { - continue; - } - $assocId = $data[$name]; $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 7ceae3cd8ce..5cbc0f762f7 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -57,11 +57,6 @@ class DefaultQueryCache implements QueryCache */ private $region; - /** - * @var \Doctrine\ORM\Cache\Logging\CacheLogger - */ - private $logger; - /** * @var \Doctrine\ORM\Cache\QueryCacheValidator */ @@ -81,7 +76,6 @@ public function __construct(EntityManagerInterface $em, Region $region) $this->em = $em; $this->region = $region; $this->uow = $em->getUnitOfWork(); - $this->logger = $em->getConfiguration()->getSecondLevelCacheLogger(); $this->validator = $em->getConfiguration()->getSecondLevelCacheQueryValidator(); } @@ -158,7 +152,7 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) return null; } - $element = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints);; + $element = $this->uow->createEntity($assocEntry->class, $assocEntry->data, self::$hints); $collection->hydrateSet($assocIndex, $element); } @@ -240,7 +234,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { - // Cancel put result if entity put fail + // Cancel put result if association entity put fail if ( ! $assocPersister->putEntityCache($assocValue, $entityKey)) { return false; } @@ -256,7 +250,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) } // Handle *-to-many associations - if (is_array($assocValue) && ! $assocValue instanceof Collection) { + if ( ! is_array($assocValue) && ! $assocValue instanceof Collection) { continue; } diff --git a/tests/Doctrine/Tests/Mocks/CacheRegionMock.php b/tests/Doctrine/Tests/Mocks/CacheRegionMock.php new file mode 100644 index 00000000000..06a40c2eba9 --- /dev/null +++ b/tests/Doctrine/Tests/Mocks/CacheRegionMock.php @@ -0,0 +1,77 @@ +returns[$method][] = $value; + } + + public function getReturn($method, $datault) + { + if (isset($this->returns[$method]) && ! empty($this->returns[$method])) { + return array_shift($this->returns[$method]); + } + + return $datault; + } + + public function getName() + { + $this->calls[__FUNCTION__][] = array(); + + return $this->name; + } + + public function contains(CacheKey $key) + { + $this->calls[__FUNCTION__][] = array('key' => $key); + + return $this->getReturn(__FUNCTION__, false); + } + + public function evict(CacheKey $key) + { + $this->calls[__FUNCTION__][] = array('key' => $key); + + return $this->getReturn(__FUNCTION__, true); + } + + public function evictAll() + { + $this->calls[__FUNCTION__][] = array(); + + return $this->getReturn(__FUNCTION__, true); + } + + public function get(CacheKey $key) + { + $this->calls[__FUNCTION__][] = array('key' => $key); + + return $this->getReturn(__FUNCTION__, null); + } + + public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) + { + $this->calls[__FUNCTION__][] = array('key' => $key, 'entry' => $entry); + + return $this->getReturn(__FUNCTION__, true); + } + + public function clear() + { + $this->calls = array(); + $this->returns = array(); + } +} diff --git a/tests/Doctrine/Tests/Models/Cache/City.php b/tests/Doctrine/Tests/Models/Cache/City.php index 8d8ee9a54d0..f755e07aec9 100644 --- a/tests/Doctrine/Tests/Models/Cache/City.php +++ b/tests/Doctrine/Tests/Models/Cache/City.php @@ -44,7 +44,7 @@ class City */ public $attractions; - public function __construct($name, State $state) + public function __construct($name, State $state = null) { $this->name = $name; $this->state = $state; diff --git a/tests/Doctrine/Tests/Models/Cache/Flight.php b/tests/Doctrine/Tests/Models/Cache/Flight.php new file mode 100644 index 00000000000..a95caab241c --- /dev/null +++ b/tests/Doctrine/Tests/Models/Cache/Flight.php @@ -0,0 +1,65 @@ +goingTo = $goingTo; + $this->leavingFrom = $leavingFrom; + $this->departure = new \DateTime(); + } + + public function getLeavingFrom() + { + return $this->leavingFrom; + } + + public function getGoingTo() + { + return $this->goingTo; + } + + public function getDeparture() + { + return $this->departure; + } + + public function setDeparture($departure) + { + $this->departure = $departure; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/Models/Cache/State.php b/tests/Doctrine/Tests/Models/Cache/State.php index 9891da93bcb..64812869dd6 100644 --- a/tests/Doctrine/Tests/Models/Cache/State.php +++ b/tests/Doctrine/Tests/Models/Cache/State.php @@ -38,7 +38,7 @@ class State */ protected $cities; - public function __construct($name, Country $country) + public function __construct($name, Country $country = null) { $this->name = $name; $this->country = $country; diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index b1d27fc8ad1..d1f406ddfeb 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -26,6 +26,8 @@ class DefaultCacheTest extends OrmTestCase */ private $em; + const NON_CACHEABLE_ENTITY = 'Doctrine\Tests\Models\CMS\CmsUser'; + protected function setUp() { parent::enableSecondLevelCache(); @@ -74,13 +76,13 @@ public function testImplementsCache() public function testGetEntityCacheRegionAccess() { $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccess', $this->cache->getEntityCacheRegionAccess(State::CLASSNAME)); - $this->assertNull($this->cache->getEntityCacheRegionAccess('Doctrine\Tests\Models\CMS\CmsUser')); + $this->assertNull($this->cache->getEntityCacheRegionAccess(self::NON_CACHEABLE_ENTITY)); } public function testGetCollectionCacheRegionAccess() { $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccess', $this->cache->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')); - $this->assertNull($this->cache->getCollectionCacheRegionAccess('Doctrine\Tests\Models\CMS\CmsUser', 'phonenumbers')); + $this->assertNull($this->cache->getCollectionCacheRegionAccess(self::NON_CACHEABLE_ENTITY, 'phonenumbers')); } public function testContainsEntity() @@ -94,6 +96,7 @@ public function testContainsEntity() $this->putEntityCacheEntry($className, $identifier, $cacheEntry); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); + $this->assertFalse($this->cache->containsEntity(self::NON_CACHEABLE_ENTITY, 1)); } public function testEvictEntity() @@ -107,6 +110,7 @@ public function testEvictEntity() $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); $this->cache->evictEntity(Country::CLASSNAME, 1); + $this->cache->evictEntity(self::NON_CACHEABLE_ENTITY, 1); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1)); } @@ -122,6 +126,7 @@ public function testEvictEntityRegion() $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, 1)); $this->cache->evictEntityRegion(Country::CLASSNAME); + $this->cache->evictEntityRegion(self::NON_CACHEABLE_ENTITY); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, 1)); } @@ -156,6 +161,7 @@ public function testContainsCollection() $this->putCollectionCacheEntry($className, $association, $ownerId, $cacheEntry); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); + $this->assertFalse($this->cache->containsCollection(self::NON_CACHEABLE_ENTITY, 'phonenumbers', 1)); } public function testEvictCollection() @@ -173,6 +179,7 @@ public function testEvictCollection() $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); $this->cache->evictCollection($className, $association, $ownerId); + $this->cache->evictCollection(self::NON_CACHEABLE_ENTITY, 'phonenumbers', 1); $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); } @@ -192,6 +199,7 @@ public function testEvictCollectionRegion() $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, $association, 1)); $this->cache->evictCollectionRegion($className, $association); + $this->cache->evictCollectionRegion(self::NON_CACHEABLE_ENTITY, 'phonenumbers'); $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); } @@ -215,6 +223,28 @@ public function testEvictCollectionRegions() $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, $association, 1)); } + public function testQueryCache() + { + $this->assertFalse($this->cache->containsQuery('foo')); + + $defaultQueryCache = $this->cache->getQueryCache(); + $fooQueryCache = $this->cache->getQueryCache('foo'); + + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCache', $defaultQueryCache); + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCache', $fooQueryCache); + $this->assertSame($defaultQueryCache, $this->cache->getQueryCache()); + $this->assertSame($fooQueryCache, $this->cache->getQueryCache('foo')); + + $this->cache->evictQueryRegion(); + $this->cache->evictQueryRegion('foo'); + $this->cache->evictQueryRegions(); + + $this->assertTrue($this->cache->containsQuery('foo')); + + $this->assertSame($defaultQueryCache, $this->cache->getQueryCache()); + $this->assertSame($fooQueryCache, $this->cache->getQueryCache('foo')); + } + public function testToIdentifierArrayShoudLookupForEntityIdentifier() { $identifier = 123; diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php new file mode 100644 index 00000000000..0a9594723f9 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php @@ -0,0 +1,478 @@ +enableSecondLevelCache(); + + $this->em = $this->_getTestEntityManager(); + $this->region = new CacheRegionMock(); + $this->queryCache = new DefaultQueryCache($this->em, $this->region); + $this->cacheFactory = new CacheFactoryDefaultQueryCacheTest($this->queryCache, $this->region); + + $this->em->getConfiguration()->setSecondLevelCacheFactory($this->cacheFactory); + } + + public function testImplementQueryCache() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCache', $this->queryCache); + } + + public function testGetRegion() + { + $this->assertSame($this->region, $this->queryCache->getRegion()); + } + + public function testClearShouldEvictRegion() + { + $this->queryCache->clear(); + + $this->assertArrayHasKey('evictAll', $this->region->calls); + $this->assertCount(1, $this->region->calls['evictAll']); + } + + public function testPutBasicQueryResult() + { + $result = array(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $metadata = $this->em->getClassMetadata(Country::CLASSNAME); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + for ($i = 0; $i < 4; $i++) { + $name = "Country $i"; + $entity = new Country($name); + $result[] = $entity; + + $metadata->setFieldValue($entity, 'id', $i); + $this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('name' => $name)); + } + + $this->assertTrue($this->queryCache->put($key, $rsm, $result)); + $this->assertArrayHasKey('put', $this->region->calls); + $this->assertCount(5, $this->region->calls['put']); + + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][0]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][1]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][2]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][3]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheKey', $this->region->calls['put'][4]['key']); + + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][0]['entry']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][1]['entry']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][2]['entry']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheEntry', $this->region->calls['put'][3]['entry']); + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheEntry', $this->region->calls['put'][4]['entry']); + } + + public function testPutToOneAssociationQueryResult() + { + $result = array(); + $uow = $this->em->getUnitOfWork(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $cityClass = $this->em->getClassMetadata(City::CLASSNAME); + $stateClass = $this->em->getClassMetadata(State::CLASSNAME); + + $rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c'); + $rsm->addJoinedEntityFromClassMetadata(State::CLASSNAME, 's', 'c', 'state', array('id'=>'state_id', 'name'=>'state_name')); + + for ($i = 0; $i < 4; $i++) { + $state = new State("State $i"); + $city = new City("City $i", $state); + $result[] = $city; + + $cityClass->setFieldValue($city, 'id', $i); + $stateClass->setFieldValue($state, 'id', $i*2); + + $uow->registerManaged($state, array('id' => $state->getId()), array('name' => $city->getName())); + $uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => $state)); + } + + $this->assertTrue($this->queryCache->put($key, $rsm, $result)); + $this->assertArrayHasKey('put', $this->region->calls); + $this->assertCount(9, $this->region->calls['put']); + + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][0]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][1]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][2]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][3]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][4]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][5]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][6]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][7]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheKey', $this->region->calls['put'][8]['key']); + } + + public function testPutToOneAssociationNullQueryResult() + { + $result = array(); + $uow = $this->em->getUnitOfWork(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $cityClass = $this->em->getClassMetadata(City::CLASSNAME); + + $rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c'); + $rsm->addJoinedEntityFromClassMetadata(State::CLASSNAME, 's', 'c', 'state', array('id'=>'state_id', 'name'=>'state_name')); + + for ($i = 0; $i < 4; $i++) { + $city = new City("City $i", null); + $result[] = $city; + + $cityClass->setFieldValue($city, 'id', $i); + + $uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => null)); + } + + $this->assertTrue($this->queryCache->put($key, $rsm, $result)); + $this->assertArrayHasKey('put', $this->region->calls); + $this->assertCount(5, $this->region->calls['put']); + + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][0]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][1]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][2]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\EntityCacheKey', $this->region->calls['put'][3]['key']); + $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheKey', $this->region->calls['put'][4]['key']); + } + + public function testgGetBasicQueryResult() + { + $rsm = new ResultSetMappingBuilder($this->em); + $key = new QueryCacheKey('query.key1', 0); + $entry = new QueryCacheEntry(array( + array('identifier' => array('id' => 1)), + array('identifier' => array('id' => 2)) + )); + + $data = array( + array('id'=>1, 'name' => 'Foo'), + array('id'=>2, 'name' => 'Bar') + ); + + $this->region->addReturn('get', $entry); + $this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[0])); + $this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[1])); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + $result = $this->queryCache->get($key, $rsm, $entry); + + $this->assertCount(2, $result); + $this->assertInstanceOf(Country::CLASSNAME, $result[0]); + $this->assertInstanceOf(Country::CLASSNAME, $result[1]); + $this->assertEquals(1, $result[0]->getId()); + $this->assertEquals(2, $result[1]->getId()); + $this->assertEquals('Foo', $result[0]->getName()); + $this->assertEquals('Bar', $result[1]->getName()); + } + + public function testCancelPutResultIfEntityPutFails() + { + $result = array(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $metadata = $this->em->getClassMetadata(Country::CLASSNAME); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + for ($i = 0; $i < 4; $i++) { + $name = "Country $i"; + $entity = new Country($name); + $result[] = $entity; + + $metadata->setFieldValue($entity, 'id', $i); + $this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('name' => $name)); + } + + $this->region->addReturn('put', false); + + $this->assertFalse($this->queryCache->put($key, $rsm, $result)); + $this->assertArrayHasKey('put', $this->region->calls); + $this->assertCount(1, $this->region->calls['put']); + } + + public function testCancelPutResultIfAssociationEntityPutFails() + { + $result = array(); + $uow = $this->em->getUnitOfWork(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $cityClass = $this->em->getClassMetadata(City::CLASSNAME); + $stateClass = $this->em->getClassMetadata(State::CLASSNAME); + + $rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c'); + $rsm->addJoinedEntityFromClassMetadata(State::CLASSNAME, 's', 'c', 'state', array('id'=>'state_id', 'name'=>'state_name')); + + $state = new State("State 1"); + $city = new City("City 2", $state); + $result[] = $city; + + $cityClass->setFieldValue($city, 'id', 1); + $stateClass->setFieldValue($state, 'id', 11); + + $uow->registerManaged($state, array('id' => $state->getId()), array('name' => $city->getName())); + $uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => $state)); + + $this->region->addReturn('put', true); // put root entity + $this->region->addReturn('put', false); // association fails + + $this->assertFalse($this->queryCache->put($key, $rsm, $result)); + } + + public function testIgnoreCacheNonGetMode() + { + $rsm = new ResultSetMappingBuilder($this->em); + $key = new QueryCacheKey('query.key1', 0, Cache::MODE_PUT); + $entry = new QueryCacheEntry(array( + array('identifier' => array('id' => 1)), + array('identifier' => array('id' => 2)) + )); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + $this->region->addReturn('get', $entry); + + $this->assertNull($this->queryCache->get($key, $rsm, $entry)); + } + + public function testIgnoreCacheNonPutMode() + { + $result = array(); + $rsm = new ResultSetMappingBuilder($this->em); + $metadata = $this->em->getClassMetadata(Country::CLASSNAME); + $key = new QueryCacheKey('query.key1', 0, Cache::MODE_GET); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + for ($i = 0; $i < 4; $i++) { + $name = "Country $i"; + $entity = new Country($name); + $result[] = $entity; + + $metadata->setFieldValue($entity, 'id', $i); + $this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('name' => $name)); + } + + $this->assertFalse($this->queryCache->put($key, $rsm, $result)); + } + + public function testGetShouldIgnoreOldQueryCacheEntryResult() + { + $rsm = new ResultSetMappingBuilder($this->em); + $key = new QueryCacheKey('query.key1', 50); + $entry = new QueryCacheEntry(array( + array('identifier' => array('id' => 1)), + array('identifier' => array('id' => 2)) + )); + $entities = array( + array('id'=>1, 'name' => 'Foo'), + array('id'=>2, 'name' => 'Bar') + ); + + $entry->time = time() - 100; + + $this->region->addReturn('get', $entry); + $this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $entities[0])); + $this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $entities[1])); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + $this->assertNull($this->queryCache->get($key, $rsm, $entry)); + } + + public function testGetShouldIgnoreNonQueryCacheEntryResult() + { + $rsm = new ResultSetMappingBuilder($this->em); + $key = new QueryCacheKey('query.key1', 0); + $entry = new \ArrayObject(array( + array('identifier' => array('id' => 1)), + array('identifier' => array('id' => 2)) + )); + + $data = array( + array('id'=>1, 'name' => 'Foo'), + array('id'=>2, 'name' => 'Bar') + ); + + $this->region->addReturn('get', $entry); + $this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[0])); + $this->region->addReturn('get', new EntityCacheEntry(Country::CLASSNAME, $data[1])); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + $this->assertNull($this->queryCache->get($key, $rsm, $entry)); + } + + public function testGetShouldIgnoreMissingEntityQueryCacheEntry() + { + $rsm = new ResultSetMappingBuilder($this->em); + $key = new QueryCacheKey('query.key1', 0); + $entry = new QueryCacheEntry(array( + array('identifier' => array('id' => 1)), + array('identifier' => array('id' => 2)) + )); + + $this->region->addReturn('get', $entry); + $this->region->addReturn('get', null); + + $rsm->addRootEntityFromClassMetadata(Country::CLASSNAME, 'c'); + + $this->assertNull($this->queryCache->get($key, $rsm, $entry)); + } + + /** + * @expectedException Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Entity association field "Doctrine\Tests\Models\Cache\City#travels" not configured as part of the second-level cache. + */ + public function testQueryNotCacheableAssociationException() + { + $uow = $this->em->getUnitOfWork(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $cityClass = $this->em->getClassMetadata(City::CLASSNAME); + $city = new City("City 1", null); + $result = array( + $city + ); + + $cityClass->setFieldValue($city, 'id', 1); + + $rsm->addRootEntityFromClassMetadata(City::CLASSNAME, 'c'); + $rsm->addJoinedEntityFromClassMetadata(Travel::CLASSNAME, 't', 'c', 'travels', array('id' => 't_id')); + $uow->registerManaged($city, array('id' => $city->getId()), array('name' => $city->getName(), 'state' => null)); + + $this->queryCache->put($key, $rsm, $result); + } + + /** + * @expectedException Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Second level cache does not suport scalar results. + */ + public function testScalarResultException() + { + $result = array(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + + $rsm->addScalarResult('id', 'u'); + + $this->queryCache->put($key, $rsm, $result); + } + + /** + * @expectedException Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Entity "Doctrine\Tests\Models\Generic\BooleanModel" not configured as part of the second-level cache. + */ + public function testNotCacheableEntityException() + { + $result = array(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $className = 'Doctrine\Tests\Models\Generic\BooleanModel'; + + $rsm->addRootEntityFromClassMetadata($className, 'c'); + + for ($i = 0; $i < 4; $i++) { + $entity = new BooleanModel(); + $boolean = ($i % 2 === 0); + + $entity->id = $i; + $entity->booleanField = $boolean; + $result[] = $entity; + + $this->em->getUnitOfWork()->registerManaged($entity, array('id' => $i), array('booleanField' => $boolean)); + } + + $this->assertFalse($this->queryCache->put($key, $rsm, $result)); + } + +} + +class CacheFactoryDefaultQueryCacheTest implements CacheFactory +{ + private $queryCache; + private $region; + + public function __construct(DefaultQueryCache $queryCache, CacheRegionMock $region) + { + $this->queryCache = $queryCache; + $this->region = $region; + } + + public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) + { + return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy($this->region); + } + + public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) + { + return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy($this->region); + } + + public function buildQueryCache(EntityManagerInterface $em, $regionName = null) + { + return $this->queryCache; + } + + public function buildCollectionEntryStructure(EntityManagerInterface $em) + { + return new \Doctrine\ORM\Cache\DefaultCollectionEntryStructure($em); + } + + public function buildEntityEntryStructure(EntityManagerInterface $em) + { + return new \Doctrine\ORM\Cache\DefaultEntityEntryStructure($em); + } + + private function createRegion($regionName) + { + + return $this->region; + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheCompositPrimaryKeyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheCompositPrimaryKeyTest.php new file mode 100644 index 00000000000..5c58b0aaa00 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheCompositPrimaryKeyTest.php @@ -0,0 +1,176 @@ +loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->_em->clear(); + $this->evictRegions(); + + $leavingFromId = $this->cities[0]->getId(); + $goingToId = $this->cities[1]->getId(); + $leavingFrom = $this->_em->find(City::CLASSNAME, $leavingFromId); + $goingTo = $this->_em->find(City::CLASSNAME, $goingToId); + $flight = new Flight($leavingFrom, $goingTo); + $id = array( + 'leavingFrom' => $leavingFromId, + 'goingTo' => $goingToId, + ); + + $flight->setDeparture(new \DateTime('tomorrow')); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $this->_em->persist($flight); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Flight::CLASSNAME, $id)); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $queryCount = $this->getCurrentQueryCount(); + $flight = $this->_em->find(Flight::CLASSNAME, $id); + $leavingFrom = $flight->getLeavingFrom(); + $goingTo = $flight->getGoingTo(); + + $this->assertInstanceOf(Flight::CLASSNAME, $flight); + $this->assertInstanceOf(City::CLASSNAME, $goingTo); + $this->assertInstanceOf(City::CLASSNAME, $leavingFrom); + + $this->assertEquals($goingTo->getId(), $goingToId); + $this->assertEquals($leavingFrom->getId(), $leavingFromId); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + } + + public function testRemoveCompositPrimaryKeyEntities() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->_em->clear(); + $this->evictRegions(); + + $leavingFromId = $this->cities[0]->getId(); + $goingToId = $this->cities[1]->getId(); + $leavingFrom = $this->_em->find(City::CLASSNAME, $leavingFromId); + $goingTo = $this->_em->find(City::CLASSNAME, $goingToId); + $flight = new Flight($leavingFrom, $goingTo); + $id = array( + 'leavingFrom' => $leavingFromId, + 'goingTo' => $goingToId, + ); + + $flight->setDeparture(new \DateTime('tomorrow')); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $this->_em->persist($flight); + $this->_em->flush(); + + $this->assertTrue($this->cache->containsEntity(Flight::CLASSNAME, $id)); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $this->_em->remove($flight); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertFalse($this->cache->containsEntity(Flight::CLASSNAME, $id)); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $this->assertNull($this->_em->find(Flight::CLASSNAME, $id)); + } + + public function testUpdateCompositPrimaryKeyEntities() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->_em->clear(); + $this->evictRegions(); + + $now = new \DateTime('now'); + $tomorrow = new \DateTime('tomorrow'); + $leavingFromId = $this->cities[0]->getId(); + $goingToId = $this->cities[1]->getId(); + $leavingFrom = $this->_em->find(City::CLASSNAME, $leavingFromId); + $goingTo = $this->_em->find(City::CLASSNAME, $goingToId); + $flight = new Flight($leavingFrom, $goingTo); + $id = array( + 'leavingFrom' => $leavingFromId, + 'goingTo' => $goingToId, + ); + + $flight->setDeparture($now); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $this->_em->persist($flight); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Flight::CLASSNAME, $id)); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $queryCount = $this->getCurrentQueryCount(); + $flight = $this->_em->find(Flight::CLASSNAME, $id); + $leavingFrom = $flight->getLeavingFrom(); + $goingTo = $flight->getGoingTo(); + + $this->assertInstanceOf(Flight::CLASSNAME, $flight); + $this->assertInstanceOf(City::CLASSNAME, $goingTo); + $this->assertInstanceOf(City::CLASSNAME, $leavingFrom); + + $this->assertEquals($goingTo->getId(), $goingToId); + $this->assertEquals($flight->getDeparture(), $now); + $this->assertEquals($leavingFrom->getId(), $leavingFromId); + $this->assertEquals($leavingFrom->getId(), $leavingFromId); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + + $flight->setDeparture($tomorrow); + + $this->_em->persist($flight); + $this->_em->flush(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Flight::CLASSNAME, $id)); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); + + $queryCount = $this->getCurrentQueryCount(); + $flight = $this->_em->find(Flight::CLASSNAME, $id); + $leavingFrom = $flight->getLeavingFrom(); + $goingTo = $flight->getGoingTo(); + + $this->assertInstanceOf(Flight::CLASSNAME, $flight); + $this->assertInstanceOf(City::CLASSNAME, $goingTo); + $this->assertInstanceOf(City::CLASSNAME, $leavingFrom); + + $this->assertEquals($goingTo->getId(), $goingToId); + $this->assertEquals($flight->getDeparture(), $tomorrow); + $this->assertEquals($leavingFrom->getId(), $leavingFromId); + $this->assertEquals($leavingFrom->getId(), $leavingFromId); + $this->assertEquals($queryCount, $this->getCurrentQueryCount()); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php index dfbe21f3cd2..c271434be33 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToOneTest.php @@ -93,4 +93,29 @@ public function testPutAndLoadManyToOneRelation() $this->assertEquals($this->states[1]->getCountry()->getId(), $c4->getCountry()->getId()); $this->assertEquals($this->states[1]->getCountry()->getName(), $c4->getCountry()->getName()); } + + public function testLoadFromDatabaseWhenAssociationIsMissing() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->_em->clear(); + + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->states[0]->getCountry()->getId())); + $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $this->states[1]->getCountry()->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[0]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $this->states[1]->getId())); + + $this->cache->evictEntityRegion(Country::CLASSNAME); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->states[0]->getCountry()->getId())); + $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->states[1]->getCountry()->getId())); + + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + + $state1 = $this->_em->find(State::CLASSNAME, $this->states[0]->getId()); + $state2 = $this->_em->find(State::CLASSNAME, $this->states[1]->getId()); + + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index 5d0ee6ec130..bab865a5d0c 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -281,7 +281,7 @@ public function testBasicQueryCachePutEntityCache() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount($this->getDefaultQueryRegionName())); } - public function testBasicQueryParamsParams() + public function testBasicQueryParams() { $this->evictRegions(); @@ -505,6 +505,98 @@ public function testBasicQueryFetchJoinsManyToOne() $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); } + public function testReloadQueryIfToOneIsNotFound() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->_em->clear(); + + $this->evictRegions(); + $this->secondLevelCacheLogger->clearStats(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT c, s FROM Doctrine\Tests\Models\Cache\City c JOIN c.state s'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(4, $result1); + $this->assertInstanceOf(City::CLASSNAME, $result1[0]); + $this->assertInstanceOf(City::CLASSNAME, $result1[1]); + $this->assertInstanceOf(State::CLASSNAME, $result1[0]->getState()); + $this->assertInstanceOf(State::CLASSNAME, $result1[1]->getState()); + + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $result1[0]->getId())); + $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $result1[1]->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $result1[0]->getState()->getId())); + $this->assertTrue($this->cache->containsEntity(State::CLASSNAME, $result1[1]->getState()->getId())); + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + + $this->_em->clear(); + + $this->cache->evictEntityRegion(State::CLASSNAME); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertCount(4, $result1); + $this->assertInstanceOf(City::CLASSNAME, $result2[0]); + $this->assertInstanceOf(City::CLASSNAME, $result2[1]); + $this->assertInstanceOf(State::CLASSNAME, $result2[0]->getState()); + $this->assertInstanceOf(State::CLASSNAME, $result2[1]->getState()); + + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + } + + public function testReloadQueryIfToManyAssociationItemIsNotFound() + { + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + + $this->evictRegions(); + $this->_em->clear(); + + $queryCount = $this->getCurrentQueryCount(); + $dql = 'SELECT s, c FROM Doctrine\Tests\Models\Cache\State s JOIN s.cities c'; + $result1 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); + $this->assertInstanceOf(State::CLASSNAME, $result1[0]); + $this->assertInstanceOf(State::CLASSNAME, $result1[1]); + $this->assertCount(2, $result1[0]->getCities()); + $this->assertCount(2, $result1[1]->getCities()); + + $this->assertInstanceOf(City::CLASSNAME, $result1[0]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result1[0]->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $result1[1]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result1[1]->getCities()->get(1)); + + $this->_em->clear(); + + $this->cache->evictEntityRegion(City::CLASSNAME); + + $result2 = $this->_em->createQuery($dql) + ->setCacheable(true) + ->getResult(); + + $this->assertInstanceOf(State::CLASSNAME, $result2[0]); + $this->assertInstanceOf(State::CLASSNAME, $result2[1]); + $this->assertCount(2, $result2[0]->getCities()); + $this->assertCount(2, $result2[1]->getCities()); + + $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result2[0]->getCities()->get(1)); + $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(0)); + $this->assertInstanceOf(City::CLASSNAME, $result2[1]->getCities()->get(1)); + + $this->assertEquals($queryCount + 2, $this->getCurrentQueryCount()); + } + public function testBasicNativeQueryCache() { $this->evictRegions(); @@ -733,60 +825,4 @@ public function testQueryCacheRegion() $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionPutCount('bar_region')); $this->assertEquals(1, $this->secondLevelCacheLogger->getRegionMissCount('bar_region')); } - - /** - * @expectedException Doctrine\ORM\Cache\CacheException - * @expectedExceptionMessage Entity "Doctrine\Tests\Models\Generic\BooleanModel" not configured as part of the second-level cache. - */ - public function testQueryNotCacheableEntityException() - { - $metadata = $this->_em->getClassMetadata('Doctrine\Tests\Models\Generic\BooleanModel'); - $metadata->cache = null; - - try { - $this->_schemaTool->createSchema(array( - $metadata, - )); - } catch (\Doctrine\ORM\Tools\ToolsException $exc) { - } - - $dql = 'SELECT b FROM Doctrine\Tests\Models\Generic\BooleanModel b'; - $query = $this->_em->createQuery($dql); - - $query->setCacheable(true) - ->getResult(); - } - - /** - * @expectedException Doctrine\ORM\Cache\CacheException - * @expectedExceptionMessage Entity association field "Doctrine\Tests\Models\Cache\City#travels" not configured as part of the second-level cache. - */ - public function testQueryNotCacheableAssociationException() - { - $this->loadFixturesCountries(); - $this->loadFixturesStates(); - $this->loadFixturesCities(); - $this->loadFixturesTraveler(); - $this->loadFixturesTravels(); - - $dql = 'SELECT c, t FROM Doctrine\Tests\Models\Cache\City c LEFT JOIN c.travels t'; - $query = $this->_em->createQuery($dql); - - $query->setCacheable(true) - ->getResult(); - } - - /** - * @expectedException Doctrine\ORM\Cache\CacheException - * @expectedExceptionMessage Second level cache does not suport scalar results. - */ - public function testQueryScalarResultException() - { - $dql = 'SELECT c, c.id, c.name FROM Doctrine\Tests\Models\Cache\Country c'; - $query = $this->_em->createQuery($dql); - - $query->setCacheable(true) - ->getResult(); - } - } \ No newline at end of file diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 1e9e2538d96..3183f81bf68 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -175,6 +175,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase 'Doctrine\Tests\Models\Cache\Restaurant', 'Doctrine\Tests\Models\Cache\Beach', 'Doctrine\Tests\Models\Cache\Bar', + 'Doctrine\Tests\Models\Cache\Flight', 'Doctrine\Tests\Models\Cache\AttractionInfo', 'Doctrine\Tests\Models\Cache\AttractionContactInfo', 'Doctrine\Tests\Models\Cache\AttractionLocationInfo' @@ -319,6 +320,7 @@ protected function tearDown() $conn->executeUpdate('DELETE FROM cache_attraction_contact_info'); $conn->executeUpdate('DELETE FROM cache_attraction_info'); $conn->executeUpdate('DELETE FROM cache_visited_cities'); + $conn->executeUpdate('DELETE FROM cache_flight'); $conn->executeUpdate('DELETE FROM cache_attraction'); $conn->executeUpdate('DELETE FROM cache_travel'); $conn->executeUpdate('DELETE FROM cache_traveler'); From 73fb0f7ec9433d41aaf804481ef950fa1e1b34ee Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 3 Aug 2013 14:04:28 -0400 Subject: [PATCH 59/71] association type based collections persisters --- .../AbstractCollectionPersister.php | 37 +------ .../ORM/Persisters/BasicEntityPersister.php | 13 +-- .../Persisters/CachedCollectionPersister.php | 4 +- .../ORM/Persisters/CollectionPersister.php | 14 --- .../ORM/Persisters/ManyToManyPersister.php | 55 +++++---- .../ORM/Persisters/OneToManyPersister.php | 37 +++---- lib/Doctrine/ORM/UnitOfWork.php | 104 +++++++++--------- 7 files changed, 102 insertions(+), 162 deletions(-) diff --git a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php index dbbb737167b..61222318f76 100644 --- a/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/AbstractCollectionPersister.php @@ -59,53 +59,18 @@ abstract class AbstractCollectionPersister implements CollectionPersister */ protected $quoteStrategy; - /** - * @var \Doctrine\ORM\Mapping\ClassMetadata - */ - protected $sourceEntity; - - /** - * @var \Doctrine\ORM\Mapping\ClassMetadata - */ - protected $targetEntity; - - /** - * @var array - */ - protected $association; - /** * Initializes a new instance of a class derived from AbstractCollectionPersister. * * @param \Doctrine\ORM\EntityManager $em - * @param array $association */ - public function __construct(EntityManager $em, array $association) + public function __construct(EntityManager $em) { $this->em = $em; - $this->association = $association; $this->uow = $em->getUnitOfWork(); $this->conn = $em->getConnection(); $this->platform = $this->conn->getDatabasePlatform(); $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); - $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); - $this->targetEntity = $em->getClassMetadata($association['targetEntity']); - } - - /** - * {@inheritdoc} - */ - public function getSourceEntityMetadata() - { - return $this->sourceEntity; - } - - /** - * {@inheritdoc} - */ - public function getTargetEntityMetadata() - { - return $this->targetEntity; } /** diff --git a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php index c148eb19e85..9a85db6a753 100644 --- a/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php @@ -218,12 +218,11 @@ public function __construct(EntityManager $em, ClassMetadata $class) $this->em = $em; $this->class = $class; $this->conn = $em->getConnection(); - $configuration = $em->getConfiguration(); $this->platform = $this->conn->getDatabasePlatform(); - $this->quoteStrategy = $configuration->getQuoteStrategy(); + $this->quoteStrategy = $em->getConfiguration()->getQuoteStrategy(); } - /** + /** * {@inheritdoc} */ public function getClassMetadata() @@ -941,9 +940,8 @@ private function loadCollectionFromStatement($assoc, $stmt, $coll) public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getManyToManyStatement($assoc, $sourceEntity); - $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); - return $list; + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } /** @@ -1630,9 +1628,8 @@ public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = nu public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $coll) { $stmt = $this->getOneToManyStatement($assoc, $sourceEntity); - $list = $this->loadCollectionFromStatement($assoc, $stmt, $coll); - - return $list; + + return $this->loadCollectionFromStatement($assoc, $stmt, $coll); } /** diff --git a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php index 5b7a51372bc..3b843fc59ac 100644 --- a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php @@ -98,10 +98,10 @@ public function __construct(CollectionPersister $persister, EntityManagerInterfa $this->association = $association; $this->uow = $em->getUnitOfWork(); $this->metadataFactory = $em->getMetadataFactory(); - $this->sourceEntity = $persister->getSourceEntityMetadata(); - $this->targetEntity = $persister->getTargetEntityMetadata(); $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); $this->cacheEntryStructure = $cacheFactory->buildCollectionEntryStructure($em); + $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); + $this->targetEntity = $em->getClassMetadata($association['targetEntity']); $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($this->sourceEntity, $association['fieldName']); } diff --git a/lib/Doctrine/ORM/Persisters/CollectionPersister.php b/lib/Doctrine/ORM/Persisters/CollectionPersister.php index 174e0fb127f..f8d1944420b 100644 --- a/lib/Doctrine/ORM/Persisters/CollectionPersister.php +++ b/lib/Doctrine/ORM/Persisters/CollectionPersister.php @@ -30,20 +30,6 @@ */ interface CollectionPersister { - /** - * The class name of the source entity. - * - * @return \Doctrine\ORM\Mapping\ClassMetadata - */ - public function getSourceEntityMetadata(); - - /** - * The class name of the target entity. - * - * @return \Doctrine\ORM\Mapping\ClassMetadata - */ - public function getTargetEntityMetadata(); - /** * Deletes the persistent state represented by the given collection. * diff --git a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php index 363f1de46ca..14664d3ce32 100644 --- a/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/ManyToManyPersister.php @@ -42,7 +42,7 @@ protected function getDeleteRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); - $class = $this->sourceEntity; + $class = $this->em->getClassMetadata(get_class($coll->getOwner())); $tableName = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { @@ -90,7 +90,7 @@ protected function getInsertRowSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); - $class = $this->sourceEntity; + $class = $this->em->getClassMetadata(get_class($coll->getOwner())); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { @@ -132,25 +132,30 @@ private function collectJoinTableColumnParameters(PersistentCollection $coll, $e $mapping = $coll->getMapping(); $isComposite = count($mapping['joinTableColumns']) > 2; - $ownerId = $this->uow->getEntityIdentifier($coll->getOwner()); - $targetId = $this->uow->getEntityIdentifier($element); + $identifier1 = $this->uow->getEntityIdentifier($coll->getOwner()); + $identifier2 = $this->uow->getEntityIdentifier($element); + + if ($isComposite) { + $class1 = $this->em->getClassMetadata(get_class($coll->getOwner())); + $class2 = $coll->getTypeClass(); + } foreach ($mapping['joinTableColumns'] as $joinTableColumn) { $isRelationToSource = isset($mapping['relationToSourceKeyColumns'][$joinTableColumn]); if ( ! $isComposite) { - $params[] = $isRelationToSource ? array_pop($ownerId) : array_pop($targetId); + $params[] = $isRelationToSource ? array_pop($identifier1) : array_pop($identifier2); continue; } if ($isRelationToSource) { - $params[] = $ownerId[$this->sourceEntity->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; + $params[] = $identifier1[$class1->getFieldForColumn($mapping['relationToSourceKeyColumns'][$joinTableColumn])]; continue; } - $params[] = $targetId[$this->targetEntity->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; + $params[] = $identifier2[$class2->getFieldForColumn($mapping['relationToTargetKeyColumns'][$joinTableColumn])]; } return $params; @@ -165,7 +170,7 @@ protected function getDeleteSQL(PersistentCollection $coll) { $columns = array(); $mapping = $coll->getMapping(); - $class = $this->sourceEntity; + $class = $this->em->getClassMetadata(get_class($coll->getOwner())); $joinTable = $this->quoteStrategy->getJoinTableName($mapping, $class, $this->platform); foreach ($mapping['joinTable']['joinColumns'] as $joinColumn) { @@ -194,7 +199,7 @@ protected function getDeleteSQLParameters(PersistentCollection $coll) } // Composite identifier - $sourceClass = $this->sourceEntity; + $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $params = array(); foreach ($mapping['relationToSourceKeyColumns'] as $columnName => $refColumnName) { @@ -215,11 +220,11 @@ public function count(PersistentCollection $coll) $params = array(); $mapping = $coll->getMapping(); $association = $mapping; - $class = $this->sourceEntity; + $class = $this->em->getClassMetadata($mapping['sourceEntity']); $id = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); if ( ! $mapping['isOwningSide']) { - $targetEntity = $this->targetEntity; + $targetEntity = $this->em->getClassMetadata($mapping['targetEntity']); $association = $targetEntity->associationMappings[$mapping['mappedBy']]; } @@ -252,11 +257,7 @@ public function count(PersistentCollection $coll) } /** - * @param \Doctrine\ORM\PersistentCollection $coll - * @param int $offset - * @param int|null $length - * - * @return array + * {@inheritdoc} */ public function slice(PersistentCollection $coll, $offset, $length = null) { @@ -266,10 +267,7 @@ public function slice(PersistentCollection $coll, $offset, $length = null) } /** - * @param \Doctrine\ORM\PersistentCollection $coll - * @param object $element - * - * @return boolean + * {@inheritdoc} */ public function contains(PersistentCollection $coll, $element) { @@ -295,10 +293,7 @@ public function contains(PersistentCollection $coll, $element) } /** - * @param \Doctrine\ORM\PersistentCollection $coll - * @param object $element - * - * @return boolean + * {@inheritdoc} */ public function removeElement(PersistentCollection $coll, $element) { @@ -338,15 +333,15 @@ private function getJoinTableRestrictions(PersistentCollection $coll, $element, $mapping = $filterMapping; if ( ! $mapping['isOwningSide']) { - $sourceClass = $this->targetEntity; - $targetClass = $this->sourceEntity; + $sourceClass = $this->em->getClassMetadata($mapping['targetEntity']); + $targetClass = $this->em->getClassMetadata($mapping['sourceEntity']); $sourceId = $uow->getEntityIdentifier($element); $targetId = $uow->getEntityIdentifier($coll->getOwner()); $mapping = $sourceClass->associationMappings[$mapping['mappedBy']]; } else { - $sourceClass = $this->sourceEntity; - $targetClass = $this->targetEntity; + $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); + $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $sourceId = $uow->getEntityIdentifier($coll->getOwner()); $targetId = $uow->getEntityIdentifier($element); } @@ -401,7 +396,7 @@ private function getJoinTableRestrictions(PersistentCollection $coll, $element, */ public function getFilterSql($mapping) { - $targetClass = $this->targetEntity; + $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); $rootClass = $this->em->getClassMetadata($targetClass->rootEntityName); $filterSql = $this->generateFilterConditionSQL($rootClass, 'te'); @@ -413,7 +408,7 @@ public function getFilterSql($mapping) $association = $mapping; if ( ! $mapping['isOwningSide']) { - $class = $this->targetEntity; + $class = $this->em->getClassMetadata($mapping['targetEntity']); $association = $class->associationMappings[$mapping['mappedBy']]; } diff --git a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php index db86f582b9f..6c0c1e78e3a 100644 --- a/lib/Doctrine/ORM/Persisters/OneToManyPersister.php +++ b/lib/Doctrine/ORM/Persisters/OneToManyPersister.php @@ -34,8 +34,6 @@ class OneToManyPersister extends AbstractCollectionPersister { /** * {@inheritdoc} - * - * @override */ public function get(PersistentCollection $coll, $index) { @@ -62,8 +60,10 @@ public function get(PersistentCollection $coll, $index) */ protected function getDeleteRowSQL(PersistentCollection $coll) { - $tableName = $this->quoteStrategy->getTableName($this->targetEntity, $this->platform); - $idColumns = $this->quoteStrategy->getIdentifierColumnNames($this->targetEntity, $this->platform); + $mapping = $coll->getMapping(); + $class = $this->em->getClassMetadata($mapping['targetEntity']); + $tableName = $this->quoteStrategy->getTableName($class, $this->platform); + $idColumns = $class->getIdentifierColumnNames(); return 'DELETE FROM ' . $tableName . ' WHERE ' . implode('= ? AND ', $idColumns) . ' = ?'; @@ -133,8 +133,8 @@ protected function getDeleteSQLParameters(PersistentCollection $coll) public function count(PersistentCollection $coll) { $mapping = $coll->getMapping(); - $targetClass = $this->targetEntity; - $sourceClass = $this->sourceEntity; + $targetClass = $this->em->getClassMetadata($mapping['targetEntity']); + $sourceClass = $this->em->getClassMetadata($mapping['sourceEntity']); $id = $this->em->getUnitOfWork()->getEntityIdentifier($coll->getOwner()); $whereClauses = array(); @@ -164,11 +164,7 @@ public function count(PersistentCollection $coll) } /** - * @param \Doctrine\ORM\PersistentCollection $coll - * @param int $offset - * @param int|null $length - * - * @return \Doctrine\Common\Collections\ArrayCollection + * {@inheritdoc} */ public function slice(PersistentCollection $coll, $offset, $length = null) { @@ -179,11 +175,8 @@ public function slice(PersistentCollection $coll, $offset, $length = null) return $persister->getOneToManyCollection($mapping, $coll->getOwner(), $offset, $length); } - /** - * @param \Doctrine\ORM\PersistentCollection $coll - * @param object $element - * - * @return boolean + /** + * {@inheritdoc} */ public function contains(PersistentCollection $coll, $element) { @@ -213,10 +206,7 @@ public function contains(PersistentCollection $coll, $element) } /** - * @param \Doctrine\ORM\PersistentCollection $coll - * @param object $element - * - * @return boolean + * {@inheritdoc} */ public function removeElement(PersistentCollection $coll, $element) { @@ -235,9 +225,10 @@ public function removeElement(PersistentCollection $coll, $element) return false; } - $tableName = $this->quoteStrategy->getTableName($this->targetEntity, $this->platform); - $idColumns = $this->quoteStrategy->getIdentifierColumnNames($this->targetEntity, $this->platform); - $sql = 'DELETE FROM ' . $tableName . ' WHERE ' . implode('= ? AND ', $idColumns) . ' = ?'; + $mapping = $coll->getMapping(); + $class = $this->em->getClassMetadata($mapping['targetEntity']); + $sql = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform) + . ' WHERE ' . implode('= ? AND ', $class->getIdentifierColumnNames()) . ' = ?'; return (bool) $this->conn->executeUpdate($sql, $this->getDeleteRowSQLParameters($coll, $element)); } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index bb66888ad80..7f72a66dbb7 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -264,11 +264,6 @@ class UnitOfWork implements PropertyChangedListener */ private $eagerLoadingEntities = array(); - /** - * @var array<\Doctrine\ORM\Persisters\CachedPersister> - */ - private $cachedPersisters = array(); - /** * @var boolean */ @@ -370,24 +365,12 @@ public function commit($entity = null) // Collection deletions (deletions of complete collections) foreach ($this->collectionDeletions as $collectionToDelete) { - $collectionPersister = $this->getCollectionPersister($collectionToDelete->getMapping()); - - $collectionPersister->delete($collectionToDelete); - - if ($this->hasCache && ($collectionPersister instanceof CachedPersister)) { - $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; - } + $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete); } // Collection updates (deleteRows, updateRows, insertRows) foreach ($this->collectionUpdates as $collectionToUpdate) { - $collectionPersister = $this->getCollectionPersister($collectionToUpdate->getMapping()); - - $collectionPersister->update($collectionToUpdate); - - if ($this->hasCache && ($collectionPersister instanceof CachedPersister)) { - $this->cachedPersisters[spl_object_hash($collectionPersister)] = $collectionPersister; - } + $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate); } // Entity deletions come last and need to be in reverse commit order @@ -402,16 +385,12 @@ public function commit($entity = null) $this->em->close(); $conn->rollback(); - foreach ($this->cachedPersisters as $persister) { - $persister->afterTransactionRolledBack(); - } + $this->afterTransactionRolledBack(); throw $e; } - foreach ($this->cachedPersisters as $persister) { - $persister->afterTransactionComplete(); - } + $this->afterTransactionComplete(); // Take new snapshots from visited collections foreach ($this->visitedCollections as $coll) { @@ -430,7 +409,6 @@ public function commit($entity = null) $this->collectionDeletions = $this->visitedCollections = $this->scheduledForDirtyCheck = - $this->cachedPersisters = $this->orphanRemovals = array(); } @@ -970,21 +948,19 @@ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) */ private function executeInserts($class) { - $inserted = false; $entities = array(); $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist); foreach ($this->entityInsertions as $oid => $entity) { + if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } $persister->addInsert($entity); - $inserted = true; - unset($this->entityInsertions[$oid]); if ($invoke !== ListenersInvoker::INVOKE_NONE) { @@ -1013,10 +989,6 @@ private function executeInserts($class) foreach ($entities as $entity) { $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } - - if ($this->hasCache && $inserted && ($persister instanceof CachedPersister)) { - $this->cachedPersisters[spl_object_hash($persister)] = $persister; - } } /** @@ -1028,13 +1000,13 @@ private function executeInserts($class) */ private function executeUpdates($class) { - $updated = false; $className = $class->name; $persister = $this->getEntityPersister($className); $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate); $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate); foreach ($this->entityUpdates as $oid => $entity) { + if ($this->em->getClassMetadata(get_class($entity))->name !== $className) { continue; } @@ -1056,10 +1028,6 @@ private function executeUpdates($class) $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke); } } - - if ($this->hasCache && $updated && ($persister instanceof CachedPersister)) { - $this->cachedPersisters[spl_object_hash($persister)] = $persister; - } } /** @@ -1071,7 +1039,6 @@ private function executeUpdates($class) */ private function executeDeletions($class) { - $deleted = false; $className = $class->name; $persister = $this->getEntityPersister($className); $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove); @@ -1100,12 +1067,6 @@ private function executeDeletions($class) if ($invoke !== ListenersInvoker::INVOKE_NONE) { $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke); } - - $deleted = true; - } - - if ($this->hasCache && $deleted && ($persister instanceof CachedPersister)) { - $this->cachedPersisters[spl_object_hash($persister)] = $persister; } } @@ -2416,7 +2377,6 @@ public function clear($entityName = null) $this->collectionUpdates = $this->extraUpdates = $this->readOnlyObjects = - $this->cachedPersisters = $this->visitedCollections = $this->orphanRemovals = array(); @@ -3071,15 +3031,17 @@ public function getEntityPersister($entityName) */ public function getCollectionPersister(array $association) { - $role = $association['sourceEntity'] . '::' . $association['fieldName']; + $role = isset($association['cache']) + ? $association['sourceEntity'] . '::' . $association['fieldName'] + : $association['type']; if (isset($this->collectionPersisters[$role])) { return $this->collectionPersisters[$role]; } $persister = ClassMetadata::ONE_TO_MANY === $association['type'] - ? new OneToManyPersister($this->em, $association) - : new ManyToManyPersister($this->em, $association); + ? new OneToManyPersister($this->em) + : new ManyToManyPersister($this->em); if ($this->hasCache && isset($association['cache'])) { $persister = new CachedCollectionPersister($persister, $this->em, $association); @@ -3280,6 +3242,50 @@ public function isReadOnly($object) return isset($this->readOnlyObjects[spl_object_hash($object)]); } + /** + * Perform whatever processing is encapsulated here after completion of the transaction. + */ + private function afterTransactionComplete() + { + if ( ! $this->hasCache) { + return; + } + + foreach ($this->persisters as $persister) { + if($persister instanceof CachedPersister) { + $persister->afterTransactionComplete(); + } + } + + foreach ($this->collectionPersisters as $persister) { + if($persister instanceof CachedPersister) { + $persister->afterTransactionComplete(); + } + } + } + + /** + * Perform whatever processing is encapsulated here after completion of the rolled-back. + */ + private function afterTransactionRolledBack() + { + if ( ! $this->hasCache) { + return; + } + + foreach ($this->persisters as $persister) { + if($persister instanceof CachedPersister) { + $persister->afterTransactionRolledBack(); + } + } + + foreach ($this->collectionPersisters as $persister) { + if($persister instanceof CachedPersister) { + $persister->afterTransactionRolledBack(); + } + } + } + private function dispatchOnFlushEvent() { if ($this->evm->hasListeners(Events::onFlush)) { From 6caaf2ce4b130870e4d7939da93b54026473349d Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sat, 3 Aug 2013 14:22:30 -0400 Subject: [PATCH 60/71] Move cached persisters --- lib/Doctrine/ORM/Cache/DefaultCache.php | 2 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 2 +- .../Persisters/CachedCollectionPersister.php | 6 +++--- .../{ => Cache}/Persisters/CachedEntityPersister.php | 12 ++++++------ .../ORM/{ => Cache}/Persisters/CachedPersister.php | 3 ++- lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php | 4 ++++ lib/Doctrine/ORM/UnitOfWork.php | 10 ++++------ 7 files changed, 21 insertions(+), 18 deletions(-) rename lib/Doctrine/ORM/{ => Cache}/Persisters/CachedCollectionPersister.php (99%) rename lib/Doctrine/ORM/{ => Cache}/Persisters/CachedEntityPersister.php (99%) rename lib/Doctrine/ORM/{ => Cache}/Persisters/CachedPersister.php (97%) diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index 8efd17ce0af..e1f6a4f8d56 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -26,7 +26,7 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\Persisters\CachedPersister; +use Doctrine\ORM\Cache\Persisters\CachedPersister; use Doctrine\ORM\ORMInvalidArgumentException; /** diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 5cbc0f762f7..667bb63926c 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -21,7 +21,7 @@ namespace Doctrine\ORM\Cache; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\Persisters\CachedPersister; +use Doctrine\ORM\Cache\Persisters\CachedPersister; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\ResultSetMapping; diff --git a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persisters/CachedCollectionPersister.php similarity index 99% rename from lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php rename to lib/Doctrine/ORM/Cache/Persisters/CachedCollectionPersister.php index 3b843fc59ac..3d5afa6aa92 100644 --- a/lib/Doctrine/ORM/Persisters/CachedCollectionPersister.php +++ b/lib/Doctrine/ORM/Cache/Persisters/CachedCollectionPersister.php @@ -18,15 +18,15 @@ * . */ -namespace Doctrine\ORM\Persisters; +namespace Doctrine\ORM\Cache\Persisters; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\ConcurrentRegionAccess; - -use Doctrine\Common\Util\ClassUtils; +use Doctrine\ORM\Persisters\CollectionPersister; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Common\Util\ClassUtils; /** * @author Fabio B. Silva diff --git a/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persisters/CachedEntityPersister.php similarity index 99% rename from lib/Doctrine/ORM/Persisters/CachedEntityPersister.php rename to lib/Doctrine/ORM/Cache/Persisters/CachedEntityPersister.php index 3213c675c2e..150f00d1dd8 100644 --- a/lib/Doctrine/ORM/Persisters/CachedEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persisters/CachedEntityPersister.php @@ -18,20 +18,20 @@ * . */ -namespace Doctrine\ORM\Persisters; +namespace Doctrine\ORM\Cache\Persisters; +use Doctrine\ORM\Cache; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\ConcurrentRegionAccess; use Doctrine\ORM\Cache\QueryCacheKey; -use Doctrine\ORM\Cache; - -use Doctrine\Common\Util\ClassUtils; -use Doctrine\Common\Collections\Criteria; - use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Persisters\EntityPersister; + +use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Collections\Criteria; /** * @author Fabio B. Silva diff --git a/lib/Doctrine/ORM/Persisters/CachedPersister.php b/lib/Doctrine/ORM/Cache/Persisters/CachedPersister.php similarity index 97% rename from lib/Doctrine/ORM/Persisters/CachedPersister.php rename to lib/Doctrine/ORM/Cache/Persisters/CachedPersister.php index 863ad66240c..ade7f8be791 100644 --- a/lib/Doctrine/ORM/Persisters/CachedPersister.php +++ b/lib/Doctrine/ORM/Cache/Persisters/CachedPersister.php @@ -16,7 +16,8 @@ * and is licensed under the MIT license. For more information, see * . */ -namespace Doctrine\ORM\Persisters; + +namespace Doctrine\ORM\Cache\Persisters; /** * Interface for persister that support second level cache. diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 9097a9bbe5d..125088610a1 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -874,6 +874,10 @@ public function __sleep() $serialized[] = "customGeneratorDefinition"; } + if ($this->cache) { + $serialized[] = "cache"; + } + return $serialized; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 7f72a66dbb7..de3867b4bb7 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -38,9 +38,9 @@ use Doctrine\ORM\Event\PostFlushEventArgs; use Doctrine\ORM\Event\ListenersInvoker; -use Doctrine\ORM\Persisters\CachedPersister; -use Doctrine\ORM\Persisters\CachedCollectionPersister; -use Doctrine\ORM\Persisters\CachedEntityPersister; +use Doctrine\ORM\Cache\Persisters\CachedCollectionPersister; +use Doctrine\ORM\Cache\Persisters\CachedEntityPersister; +use Doctrine\ORM\Cache\Persisters\CachedPersister; use Doctrine\ORM\Persisters\BasicEntityPersister; use Doctrine\ORM\Persisters\SingleTablePersister; use Doctrine\ORM\Persisters\JoinedSubclassPersister; @@ -1018,8 +1018,6 @@ private function executeUpdates($class) if ( ! empty($this->entityChangeSets[$oid])) { $persister->update($entity); - - $updated = true; } unset($this->entityUpdates[$oid]); @@ -3004,7 +3002,7 @@ public function getEntityPersister($entityName) case ($class->isInheritanceTypeSingleTable()): $persister = new SingleTablePersister($this->em, $class); break; - + case ($class->isInheritanceTypeJoined()): $persister = new JoinedSubclassPersister($this->em, $class); break; From 376a50a2eac810ce10f570309393659b56b75ad0 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 4 Aug 2013 00:16:54 -0400 Subject: [PATCH 61/71] Improve code coverage --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 4 -- .../Tests/ORM/Cache/DefaultQueryCacheTest.php | 71 +++++++++++++++++++ 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 667bb63926c..8bba92b2c18 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -250,10 +250,6 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) } // Handle *-to-many associations - if ( ! is_array($assocValue) && ! $assocValue instanceof Collection) { - continue; - } - $list = array(); foreach ($assocValue as $assocItemIndex => $assocItem) { diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php index 0a9594723f9..87e74cbefbd 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php @@ -181,6 +181,41 @@ public function testPutToOneAssociationNullQueryResult() $this->assertInstanceOf('Doctrine\ORM\Cache\QueryCacheKey', $this->region->calls['put'][4]['key']); } + public function testPutToManyAssociationQueryResult() + { + $result = array(); + $uow = $this->em->getUnitOfWork(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $cityClass = $this->em->getClassMetadata(City::CLASSNAME); + $stateClass = $this->em->getClassMetadata(State::CLASSNAME); + + $rsm->addRootEntityFromClassMetadata(State::CLASSNAME, 's'); + $rsm->addJoinedEntityFromClassMetadata(City::CLASSNAME, 'c', 's', 'cities', array('id'=>'c_id', 'name'=>'c_name')); + + for ($i = 0; $i < 4; $i++) { + $state = new State("State $i"); + $city1 = new City("City 1", $state); + $city2 = new City("City 2", $state); + $result[] = $state; + + $cityClass->setFieldValue($city1, 'id', $i + 11); + $cityClass->setFieldValue($city2, 'id', $i + 22); + $stateClass->setFieldValue($state, 'id', $i); + + $state->addCity($city1); + $state->addCity($city2); + + $uow->registerManaged($city1, array('id' => $city1->getId()), array('name' => $city1->getName(), 'state' => $state)); + $uow->registerManaged($city2, array('id' => $city2->getId()), array('name' => $city2->getName(), 'state' => $state)); + $uow->registerManaged($state, array('id' => $state->getId()), array('name' => $state->getName(), 'cities' => $state->getCities())); + } + + $this->assertTrue($this->queryCache->put($key, $rsm, $result)); + $this->assertArrayHasKey('put', $this->region->calls); + $this->assertCount(13, $this->region->calls['put']); + } + public function testgGetBasicQueryResult() { $rsm = new ResultSetMappingBuilder($this->em); @@ -265,6 +300,42 @@ public function testCancelPutResultIfAssociationEntityPutFails() $this->assertFalse($this->queryCache->put($key, $rsm, $result)); } + public function testCancelPutToManyAssociationQueryResult() + { + $result = array(); + $uow = $this->em->getUnitOfWork(); + $key = new QueryCacheKey('query.key1', 0); + $rsm = new ResultSetMappingBuilder($this->em); + $cityClass = $this->em->getClassMetadata(City::CLASSNAME); + $stateClass = $this->em->getClassMetadata(State::CLASSNAME); + + $rsm->addRootEntityFromClassMetadata(State::CLASSNAME, 's'); + $rsm->addJoinedEntityFromClassMetadata(City::CLASSNAME, 'c', 's', 'cities', array('id'=>'c_id', 'name'=>'c_name')); + + $state = new State("State"); + $city1 = new City("City 1", $state); + $city2 = new City("City 2", $state); + $result[] = $state; + + $stateClass->setFieldValue($state, 'id', 1); + $cityClass->setFieldValue($city1, 'id', 11); + $cityClass->setFieldValue($city2, 'id', 22); + + $state->addCity($city1); + $state->addCity($city2); + + $uow->registerManaged($city1, array('id' => $city1->getId()), array('name' => $city1->getName(), 'state' => $state)); + $uow->registerManaged($city2, array('id' => $city2->getId()), array('name' => $city2->getName(), 'state' => $state)); + $uow->registerManaged($state, array('id' => $state->getId()), array('name' => $state->getName(), 'cities' => $state->getCities())); + + $this->region->addReturn('put', true); // put root entity + $this->region->addReturn('put', false); // collection association fails + + $this->assertFalse($this->queryCache->put($key, $rsm, $result)); + $this->assertArrayHasKey('put', $this->region->calls); + $this->assertCount(2, $this->region->calls['put']); + } + public function testIgnoreCacheNonGetMode() { $rsm = new ResultSetMappingBuilder($this->em); From a447775ca8cfc993fdce0e4d3b0b64a9aa7c881f Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 4 Aug 2013 11:56:25 -0400 Subject: [PATCH 62/71] Fix second-level-cache build --- .../Tests/ORM/Functional/AdvancedAssociationTest.php | 3 --- tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php | 3 --- .../Tests/ORM/Functional/JoinedTableCompositeKeyTest.php | 2 +- tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php | 1 - .../Tests/ORM/Functional/SingleTableCompositeKeyTest.php | 2 +- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php | 3 ++- tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2012Test.php | 1 + tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2350Test.php | 1 + tests/Doctrine/Tests/OrmFunctionalTestCase.php | 5 +++-- tests/travis/mysql.travis.xml | 1 + tests/travis/pgsql.travis.xml | 1 + tests/travis/sqlite.travis.xml | 1 + 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php b/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php index 7d01ddd52da..f1527967266 100644 --- a/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/AdvancedAssociationTest.php @@ -28,9 +28,6 @@ protected function setUp() { } } - /** - * @group non-cacheable - */ public function testIssue() { //setup diff --git a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php index c797de87bcb..f5c50eb1196 100644 --- a/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/DetachedEntityTest.php @@ -118,9 +118,6 @@ public function testDetachedEntityThrowsExceptionOnFlush() } catch (\Exception $expected) {} } - /** - * @group non-cacheable - */ public function testUninitializedLazyAssociationsAreIgnoredOnMerge() { $user = new CmsUser; diff --git a/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php b/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php index da0d3bc6462..8f6e0cc59fd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/JoinedTableCompositeKeyTest.php @@ -30,7 +30,7 @@ public function testInsertWithCompositeKey() } /** - *@group non-cacheable + * @group non-cacheable */ public function testUpdateWithCompositeKey() { diff --git a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php index eeeed05c6b6..f4439b9ad06 100644 --- a/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/MappedSuperclassTest.php @@ -8,7 +8,6 @@ * MappedSuperclassTest * * @author robo - * @group non-cacheable */ class MappedSuperclassTest extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/SingleTableCompositeKeyTest.php b/tests/Doctrine/Tests/ORM/Functional/SingleTableCompositeKeyTest.php index 3d1fe7714cb..53829a1c05e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SingleTableCompositeKeyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SingleTableCompositeKeyTest.php @@ -30,7 +30,7 @@ public function testInsertWithCompositeKey() } /** - * + * @group non-cacheable */ public function testUpdateWithCompositeKey() { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php index 36897cc083c..502101bf91e 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1301Test.php @@ -8,8 +8,9 @@ /** * @author asm89 - * + * * @group non-cacheable + * @group DDC-1301 */ class DDC1301Test extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2012Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2012Test.php index eca15cea3fb..ac93906dab6 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2012Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2012Test.php @@ -9,6 +9,7 @@ /** * @group DDC-2012 + * @group non-cacheable */ class DDC2012Test extends \Doctrine\Tests\OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2350Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2350Test.php index 229bbff52ee..75f34a25c19 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2350Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2350Test.php @@ -6,6 +6,7 @@ /** * @group DDC-2350 + * @group non-cacheable */ class DDC2350Test extends OrmFunctionalTestCase { diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 3183f81bf68..2e7ccc4a844 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -2,6 +2,7 @@ namespace Doctrine\Tests; +use Doctrine\Tests\EventListener\CacheMetadataListener; use Doctrine\ORM\Cache\Logging\StatisticsCacheLogger; use Doctrine\ORM\Cache\DefaultCacheFactory; @@ -478,8 +479,8 @@ protected function _getEntityManager($config = null, $eventManager = null) { } } - if ( ! $this->isSecondLevelCacheEnabled && $enableSecondLevelCache) { - $evm->addEventListener('loadClassMetadata', new EventListener\CacheMetadataListener(get_called_class())); + if ($enableSecondLevelCache) { + $evm->addEventListener('loadClassMetadata', new CacheMetadataListener()); } if (isset($GLOBALS['db_event_subscribers'])) { diff --git a/tests/travis/mysql.travis.xml b/tests/travis/mysql.travis.xml index 82559afdf90..8e56cb5c82c 100644 --- a/tests/travis/mysql.travis.xml +++ b/tests/travis/mysql.travis.xml @@ -28,6 +28,7 @@ performance + non-cacheable locking_functional diff --git a/tests/travis/pgsql.travis.xml b/tests/travis/pgsql.travis.xml index b92f775aa54..897acdec28c 100644 --- a/tests/travis/pgsql.travis.xml +++ b/tests/travis/pgsql.travis.xml @@ -32,6 +32,7 @@ performance + non-cacheable locking_functional diff --git a/tests/travis/sqlite.travis.xml b/tests/travis/sqlite.travis.xml index a4c400caaf1..b657285bd3c 100644 --- a/tests/travis/sqlite.travis.xml +++ b/tests/travis/sqlite.travis.xml @@ -14,6 +14,7 @@ performance + non-cacheable locking_functional From 4dbbdbf033fafa778e9dc0cbfba538d28a4d2299 Mon Sep 17 00:00:00 2001 From: "Fabio B. Silva" Date: Sun, 4 Aug 2013 11:59:22 -0400 Subject: [PATCH 63/71] add travis build filter whitelist --- tests/travis/mysql.travis.xml | 5 +++++ tests/travis/pgsql.travis.xml | 6 +++++- tests/travis/sqlite.travis.xml | 6 +++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/travis/mysql.travis.xml b/tests/travis/mysql.travis.xml index 8e56cb5c82c..9177e2f51fa 100644 --- a/tests/travis/mysql.travis.xml +++ b/tests/travis/mysql.travis.xml @@ -25,6 +25,11 @@ ./../Doctrine/Tests/ORM + + + ./../../lib/Doctrine + + performance diff --git a/tests/travis/pgsql.travis.xml b/tests/travis/pgsql.travis.xml index 897acdec28c..1280d95c852 100644 --- a/tests/travis/pgsql.travis.xml +++ b/tests/travis/pgsql.travis.xml @@ -28,7 +28,11 @@ ./../Doctrine/Tests/ORM - + + + ./../../lib/Doctrine + + performance diff --git a/tests/travis/sqlite.travis.xml b/tests/travis/sqlite.travis.xml index b657285bd3c..7dbe99fe1bf 100644 --- a/tests/travis/sqlite.travis.xml +++ b/tests/travis/sqlite.travis.xml @@ -10,7 +10,11 @@ ./../Doctrine/Tests/ORM - + + + ./../../lib/Doctrine + + performance From 2e2355579341cc50c1e2320819fb9b055623b87f Mon Sep 17 00:00:00 2001 From: fabios Date: Fri, 20 Sep 2013 17:33:37 -0400 Subject: [PATCH 64/71] Refactoring region access strategies --- docs/en/reference/second-level-cache.rst | 48 +++------ ...y.php => AbstractRegionAccessStrategy.php} | 25 +---- ...eadWriteCollectionRegionAccessStrategy.php | 34 ++++++ ...ictReadWriteEntityRegionAccessStrategy.php | 51 +++++++++ ...ReadOnlyCollectionRegionAccessStrategy.php | 34 ++++++ ...=> ReadOnlyEntityRegionAccessStrategy.php} | 2 +- ...eadWriteCollectionRegionAccessStrategy.php | 102 ++++++++++++++++++ ...> ReadWriteEntityRegionAccessStrategy.php} | 50 +-------- lib/Doctrine/ORM/Cache/CacheFactory.php | 4 +- .../Cache/CollectionRegionAccessStrategy.php | 31 ++++++ ...php => ConcurrentRegionAccessStrategy.php} | 4 +- .../ORM/Cache/DefaultCacheFactory.php | 14 +-- .../ORM/Cache/EntityRegionAccessStrategy.php | 54 ++++++++++ ...ionAccess.php => RegionAccessStrategy.php} | 28 +---- .../Cache/AbstractEntityRegionAccessTest.php | 46 ++++++++ .../ORM/Cache/AbstractRegionAccessTest.php | 38 +------ .../Tests/ORM/Cache/DefaultCacheTest.php | 4 +- .../Tests/ORM/Cache/DefaultQueryCacheTest.php | 6 +- ...riteCollectionRegionAccessStrategyTest.php | 17 +++ ...eadWriteEntityRegionAccessStrategyTest.php | 17 +++ .../NonStrictReadWriteRegionAccessTest.php | 17 --- ...OnlyCollectionRegionAccessStrategyTest.php | 17 +++ ...eadOnlyEntityRegionAccessStrategyTest.php} | 6 +- ...riteCollectionRegionAccessStrategyTest.php | 29 +++++ ...adWriteEntityRegionAccessStrategyTest.php} | 7 +- .../SecondLevelCacheConcurrentTest.php | 10 +- tests/travis/mysql.travis.xml | 2 +- tests/travis/pgsql.travis.xml | 2 +- tests/travis/sqlite.travis.xml | 2 +- 29 files changed, 487 insertions(+), 214 deletions(-) rename lib/Doctrine/ORM/Cache/Access/{NonStrictReadWriteRegionAccessStrategy.php => AbstractRegionAccessStrategy.php} (78%) create mode 100644 lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteCollectionRegionAccessStrategy.php create mode 100644 lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php create mode 100644 lib/Doctrine/ORM/Cache/Access/ReadOnlyCollectionRegionAccessStrategy.php rename lib/Doctrine/ORM/Cache/Access/{ReadOnlyRegionAccess.php => ReadOnlyEntityRegionAccessStrategy.php} (94%) create mode 100644 lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php rename lib/Doctrine/ORM/Cache/Access/{ConcurrentRegionAccessStrategy.php => ReadWriteEntityRegionAccessStrategy.php} (81%) create mode 100644 lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php rename lib/Doctrine/ORM/Cache/{ConcurrentRegionAccess.php => ConcurrentRegionAccessStrategy.php} (96%) create mode 100644 lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php rename lib/Doctrine/ORM/Cache/{RegionAccess.php => RegionAccessStrategy.php} (70%) create mode 100644 tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteEntityRegionAccessStrategyTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteRegionAccessTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/ReadOnlyCollectionRegionAccessStrategyTest.php rename tests/Doctrine/Tests/ORM/Cache/{ReadOnlyRegionAccessTest.php => ReadOnlyEntityRegionAccessStrategyTest.php} (72%) create mode 100644 tests/Doctrine/Tests/ORM/Cache/ReadWriteCollectionRegionAccessStrategyTest.php rename tests/Doctrine/Tests/ORM/Cache/{ConcurrentRegionAccessTest.php => ReadWriteEntityRegionAccessStrategyTest.php} (94%) diff --git a/docs/en/reference/second-level-cache.rst b/docs/en/reference/second-level-cache.rst index 0159eb3246b..63ed0de42ce 100644 --- a/docs/en/reference/second-level-cache.rst +++ b/docs/en/reference/second-level-cache.rst @@ -208,17 +208,20 @@ Built-in Strategies Caching Strategies are responsible for access cache regions. -* ``Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess`` implements ``READ_ONLY`` -* ``Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy`` implements ``NONSTRICT_READ_WRITE`` -* ``Doctrine\ORM\Cache\Access\ConcurrentRegionAccessStrategy`` implements ``READ_WRITE`` requires a ``ConcurrentRegion`` - -``Doctrine\ORM\Cache\RegionAccess`` and ``Doctrine\ORM\Cache\ConcurrentRegionAccess`` +* ``Doctrine\ORM\Cache\Access\ReadOnlyEntityRegionAccessStrategy`` implements ``READ_ONLY`` for entities +* ``Doctrine\ORM\Cache\Access\ReadOnlyCollectionRegionAccessStrategy`` implements ``READ_ONLY`` for collections +* ``Doctrine\ORM\Cache\Access\NonStrictReadWriteEntityRegionAccessStrategy`` implements ``NONSTRICT_READ_WRITE`` for entities +* ``Doctrine\ORM\Cache\Access\NonStrictReadWriteCollectionRegionAccessStrategy`` implements ``NONSTRICT_READ_WRITE`` for collections +* ``Doctrine\ORM\Cache\Access\ReadWriteEntityRegionAccessStrategy`` implements ``READ_WRITE`` requires a ``ConcurrentRegion`` for entities +* ``Doctrine\ORM\Cache\Access\ReadWriteCollectionRegionAccessStrategy`` implements ``READ_WRITE`` requires a ``ConcurrentRegion`` for collections + +``Doctrine\ORM\Cache\RegionAccessStrategy``, ``Doctrine\ORM\Cache\CollectionRegionAccessStrategy`` and ``Doctrine\ORM\Cache\ConcurrentRegionAccessStrategy`` Defines contracts that should be implemented by caching strategies. -If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegionAccess``; ``RegionAccess`` otherwise. +If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegionAccessStrategy``; ``RegionAccessStrategy`` otherwise. -``Doctrine\ORM\Cache\RegionAccess`` +``Doctrine\ORM\Cache\RegionAccessStrategy`` Interface for all region access strategies. @@ -226,7 +229,7 @@ Interface for all region access strategies. */ -class NonStrictReadWriteRegionAccessStrategy implements RegionAccess +abstract class AbstractRegionAccessStrategy implements RegionAccessStrategy { /** * @var \Doctrine\ORM\Cache\Region */ - private $region; + protected $region; /** * @param \Doctrine\ORM\Cache\Region $region @@ -55,22 +54,6 @@ public function getRegion() return $this->region; } - /** - * {@inheritdoc} - */ - public function afterInsert(CacheKey $key, CacheEntry $entry) - { - return $this->region->put($key, $entry); - } - - /** - * {@inheritdoc} - */ - public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null) - { - return $this->region->put($key, $entry); - } - /** * {@inheritdoc} */ diff --git a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteCollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteCollectionRegionAccessStrategy.php new file mode 100644 index 00000000000..b4257c2244c --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteCollectionRegionAccessStrategy.php @@ -0,0 +1,34 @@ +. + */ + +namespace Doctrine\ORM\Cache\Access; + +use Doctrine\ORM\Cache\CollectionRegionAccessStrategy; + +/** + * Specific non-strict read/write region access strategy + * + * @since 2.5 + * @author Fabio B. Silva + */ +class NonStrictReadWriteCollectionRegionAccessStrategy extends AbstractRegionAccessStrategy implements CollectionRegionAccessStrategy +{ + +} diff --git a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php new file mode 100644 index 00000000000..24203755247 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\ORM\Cache\Access; + +use Doctrine\ORM\Cache\EntityRegionAccessStrategy; +use Doctrine\ORM\Cache\CacheEntry; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\ORM\Cache\Lock; + +/** + * Specific non-strict read/write region access strategy + * + * @since 2.5 + * @author Fabio B. Silva + */ +class NonStrictReadWriteEntityRegionAccessStrategy extends AbstractRegionAccessStrategy implements EntityRegionAccessStrategy +{ + /** + * {@inheritdoc} + */ + public function afterInsert(CacheKey $key, CacheEntry $entry) + { + return $this->region->put($key, $entry); + } + + /** + * {@inheritdoc} + */ + public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null) + { + return $this->region->put($key, $entry); + } +} diff --git a/lib/Doctrine/ORM/Cache/Access/ReadOnlyCollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/ReadOnlyCollectionRegionAccessStrategy.php new file mode 100644 index 00000000000..c384ee0ad6b --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Access/ReadOnlyCollectionRegionAccessStrategy.php @@ -0,0 +1,34 @@ +. + */ + +namespace Doctrine\ORM\Cache\Access; + +use Doctrine\ORM\Cache\CollectionRegionAccessStrategy; + +/** + * Specific read-only region access strategy + * + * @since 2.5 + * @author Fabio B. Silva + */ +class ReadOnlyCollectionRegionAccessStrategy extends AbstractRegionAccessStrategy implements CollectionRegionAccessStrategy +{ + +} diff --git a/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php b/lib/Doctrine/ORM/Cache/Access/ReadOnlyEntityRegionAccessStrategy.php similarity index 94% rename from lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php rename to lib/Doctrine/ORM/Cache/Access/ReadOnlyEntityRegionAccessStrategy.php index 14698a96a67..4f4372393b5 100644 --- a/lib/Doctrine/ORM/Cache/Access/ReadOnlyRegionAccess.php +++ b/lib/Doctrine/ORM/Cache/Access/ReadOnlyEntityRegionAccessStrategy.php @@ -31,7 +31,7 @@ * @since 2.5 * @author Fabio B. Silva */ -class ReadOnlyRegionAccess extends NonStrictReadWriteRegionAccessStrategy +class ReadOnlyEntityRegionAccessStrategy extends NonStrictReadWriteEntityRegionAccessStrategy { /** * {@inheritdoc} diff --git a/lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php new file mode 100644 index 00000000000..e2da56d26ef --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php @@ -0,0 +1,102 @@ +. + */ + +namespace Doctrine\ORM\Cache\Access; + +use Doctrine\ORM\Cache\ConcurrentRegionAccessStrategy; +use Doctrine\ORM\Cache\CollectionRegionAccessStrategy; +use Doctrine\ORM\Cache\ConcurrentRegion; +use Doctrine\ORM\Cache\CacheException; +use Doctrine\ORM\Cache\LockException; +use Doctrine\ORM\Cache\CacheEntry; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\ORM\Cache\Lock; + +/** + * Region access strategies for concurrently managed data. + * + * @since 2.5 + * @author Fabio B. Silva + */ +class ReadWriteCollectionRegionAccessStrategy extends AbstractRegionAccessStrategy implements ConcurrentRegionAccessStrategy, CollectionRegionAccessStrategy +{ + /** + * @param \Doctrine\ORM\Cache\ConcurrentRegion $region + */ + public function __construct(ConcurrentRegion $region) + { + $this->region = $region; + } + + /** + * {@inheritdoc} + */ + public function put(CacheKey $key, CacheEntry $entry) + { + $writeLock = null; + + try { + if ( ! ($writeLock = $this->region->writeLock($key))) { + return false; + } + + if ( ! $this->region->put($key, $entry, $writeLock)) { + return false; + } + + $this->region->writeUnlock($key, $writeLock); + + return true; + + } catch (LockException $exc) { + + if ($writeLock) { + $this->region->writeUnlock($key, $writeLock); + } + + throw new $exc; + } catch (\Exception $exc) { + + if ($writeLock) { + $this->region->writeUnlock($key, $writeLock); + } + + throw new CacheException($exc->getMessage(), $exc->getCode(), $exc); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function lockItem(CacheKey $key) + { + return $this->region->readLock($key); + } + + /** + * {@inheritdoc} + */ + public function unlockItem(CacheKey $key, Lock $lock) + { + return $this->region->readUnlock($key, $lock); + } +} diff --git a/lib/Doctrine/ORM/Cache/Access/ConcurrentRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/ReadWriteEntityRegionAccessStrategy.php similarity index 81% rename from lib/Doctrine/ORM/Cache/Access/ConcurrentRegionAccessStrategy.php rename to lib/Doctrine/ORM/Cache/Access/ReadWriteEntityRegionAccessStrategy.php index c2b90bab0ec..77b5818924a 100644 --- a/lib/Doctrine/ORM/Cache/Access/ConcurrentRegionAccessStrategy.php +++ b/lib/Doctrine/ORM/Cache/Access/ReadWriteEntityRegionAccessStrategy.php @@ -20,7 +20,8 @@ namespace Doctrine\ORM\Cache\Access; -use Doctrine\ORM\Cache\ConcurrentRegionAccess; +use Doctrine\ORM\Cache\ConcurrentRegionAccessStrategy; +use Doctrine\ORM\Cache\EntityRegionAccessStrategy; use Doctrine\ORM\Cache\ConcurrentRegion; use Doctrine\ORM\Cache\CacheException; use Doctrine\ORM\Cache\LockException; @@ -34,13 +35,8 @@ * @since 2.5 * @author Fabio B. Silva */ -class ConcurrentRegionAccessStrategy implements ConcurrentRegionAccess +class ReadWriteEntityRegionAccessStrategy extends AbstractRegionAccessStrategy implements ConcurrentRegionAccessStrategy, EntityRegionAccessStrategy { - /** - * @var \Doctrine\ORM\Cache\ConcurrentRegion - */ - private $region; - /** * @param \Doctrine\ORM\Cache\ConcurrentRegion $region */ @@ -49,14 +45,6 @@ public function __construct(ConcurrentRegion $region) $this->region = $region; } - /** - * {@inheritdoc} - */ - public function getRegion() - { - return $this->region; - } - /** * {@inheritdoc} */ @@ -158,36 +146,4 @@ public function unlockItem(CacheKey $key, Lock $lock) { return $this->region->readUnlock($key, $lock); } - - /** - * {@inheritdoc} - */ - public function get(CacheKey $key) - { - return $this->region->get($key); - } - - /** - * {@inheritdoc} - */ - public function put(CacheKey $key, CacheEntry $entry) - { - return $this->region->put($key, $entry); - } - - /** - * {@inheritdoc} - */ - public function evict(CacheKey $key) - { - return $this->region->evict($key); - } - - /** - * {@inheritdoc} - */ - public function evictAll() - { - return $this->region->evictAll(); - } } diff --git a/lib/Doctrine/ORM/Cache/CacheFactory.php b/lib/Doctrine/ORM/Cache/CacheFactory.php index 6ea5ba25a37..b79d8b8fabb 100644 --- a/lib/Doctrine/ORM/Cache/CacheFactory.php +++ b/lib/Doctrine/ORM/Cache/CacheFactory.php @@ -34,7 +34,7 @@ interface CacheFactory * * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * - * @return \Doctrine\ORM\Cache\RegionAccess The built region access. + * @return \Doctrine\ORM\Cache\EntityRegionAccessStrategy The built region access. * * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. */ @@ -46,7 +46,7 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata); * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * @param string $fieldName The field name that represents the association. * - * @return \Doctrine\ORM\Cache\RegionAccess The built region access. + * @return \Doctrine\ORM\Cache\CollectionRegionAccessStrategy The built region access. * * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. */ diff --git a/lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php new file mode 100644 index 00000000000..4c93d6ee746 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Interface for entity region access. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface CollectionRegionAccessStrategy extends RegionAccessStrategy +{ + +} diff --git a/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php b/lib/Doctrine/ORM/Cache/ConcurrentRegionAccessStrategy.php similarity index 96% rename from lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php rename to lib/Doctrine/ORM/Cache/ConcurrentRegionAccessStrategy.php index d87ab62da0b..22fbe940fb9 100644 --- a/lib/Doctrine/ORM/Cache/ConcurrentRegionAccess.php +++ b/lib/Doctrine/ORM/Cache/ConcurrentRegionAccessStrategy.php @@ -26,7 +26,7 @@ * @since 2.5 * @author Fabio B. Silva */ -interface ConcurrentRegionAccess extends RegionAccess +interface ConcurrentRegionAccessStrategy extends RegionAccessStrategy { /** * We are going to attempt to update/delete the keyed object. @@ -48,4 +48,4 @@ public function lockItem(CacheKey $key); * @throws \Doctrine\ORM\Cache\CacheException */ public function unlockItem(CacheKey $key, Lock $lock); -} +} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index f3686fa0cdc..71ea9d4a430 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -26,8 +26,10 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\Common\Cache\Cache as CacheDriver; -use Doctrine\ORM\Cache\Access\ReadOnlyRegionAccess; -use Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy; +use Doctrine\ORM\Cache\Access\ReadOnlyEntityRegionAccessStrategy; +use Doctrine\ORM\Cache\Access\ReadOnlyCollectionRegionAccessStrategy; +use Doctrine\ORM\Cache\Access\NonStrictReadWriteEntityRegionAccessStrategy; +use Doctrine\ORM\Cache\Access\NonStrictReadWriteCollectionRegionAccessStrategy; /** * @since 2.5 @@ -60,11 +62,11 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) $usage = $metadata->cache['usage']; if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { - return new ReadOnlyRegionAccess($this->createRegion($regionName)); + return new ReadOnlyEntityRegionAccessStrategy($this->createRegion($regionName)); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { - return new NonStrictReadWriteRegionAccessStrategy($this->createRegion($regionName)); + return new NonStrictReadWriteEntityRegionAccessStrategy($this->createRegion($regionName)); } throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); @@ -80,11 +82,11 @@ public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fi $usage = $mapping['cache']['usage']; if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { - return new ReadOnlyRegionAccess($this->createRegion($regionName)); + return new ReadOnlyCollectionRegionAccessStrategy($this->createRegion($regionName)); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { - return new NonStrictReadWriteRegionAccessStrategy($this->createRegion($regionName)); + return new NonStrictReadWriteCollectionRegionAccessStrategy($this->createRegion($regionName)); } throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); diff --git a/lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php new file mode 100644 index 00000000000..2e272eca688 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\ORM\Cache; + +/** + * Interface for entity region access. + * + * @since 2.5 + * @author Fabio B. Silva + */ +interface EntityRegionAccessStrategy extends RegionAccessStrategy +{ + /** + * Called after an item has been inserted (after the transaction completes). + * + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. + * + * @return boolean TRUE If the contents of the cache actual were changed. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function afterInsert(CacheKey $key, CacheEntry $entry); + + /** + * Called after an item has been updated (after the transaction completes). + * + * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. + * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} + * + * @return boolean TRUE If the contents of the cache actual were changed. + * + * @throws \Doctrine\ORM\Cache\CacheException + */ + public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null); +} diff --git a/lib/Doctrine/ORM/Cache/RegionAccess.php b/lib/Doctrine/ORM/Cache/RegionAccessStrategy.php similarity index 70% rename from lib/Doctrine/ORM/Cache/RegionAccess.php rename to lib/Doctrine/ORM/Cache/RegionAccessStrategy.php index 086716d5a74..bc37ad1cb3b 100644 --- a/lib/Doctrine/ORM/Cache/RegionAccess.php +++ b/lib/Doctrine/ORM/Cache/RegionAccessStrategy.php @@ -1,5 +1,4 @@ */ -interface RegionAccess +interface RegionAccessStrategy { /** * Get the wrapped data cache region @@ -58,31 +57,6 @@ public function get(CacheKey $key); */ public function put(CacheKey $key, CacheEntry $entry); - /** - * Called after an item has been inserted (after the transaction completes). - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. - * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. - * - * @return boolean TRUE If the contents of the cache actual were changed. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function afterInsert(CacheKey $key, CacheEntry $entry); - - /** - * Called after an item has been updated (after the transaction completes). - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. - * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} - * - * @return boolean TRUE If the contents of the cache actual were changed. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null); - /** * Forcibly evict an item from the cache immediately without regard for locks. * diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php new file mode 100644 index 00000000000..86c7748bdd2 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php @@ -0,0 +1,46 @@ +assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->afterInsert($key1, new CacheEntryMock(array('value' => 'foo'))); + $this->regionAccess->afterInsert($key2, new CacheEntryMock(array('value' => 'bar'))); + + $this->assertNotNull($this->regionAccess->get($key1)); + $this->assertNotNull($this->regionAccess->get($key2)); + + $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); + } + + public function testAfterUpdate() + { + $key1 = new CacheKeyMock('key.1'); + $key2 = new CacheKeyMock('key.2'); + + $this->assertNull($this->regionAccess->get($key1)); + $this->assertNull($this->regionAccess->get($key2)); + + $this->regionAccess->afterUpdate($key1, new CacheEntryMock(array('value' => 'foo'))); + $this->regionAccess->afterUpdate($key2, new CacheEntryMock(array('value' => 'bar'))); + + $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); + $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); + } +} diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php index 0be3bccef76..3e8888ffef0 100644 --- a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php @@ -3,18 +3,17 @@ namespace Doctrine\Tests\ORM\Cache; use Doctrine\ORM\Cache\Region; +use Doctrine\Tests\OrmTestCase; use Doctrine\Common\Cache\Cache; use Doctrine\Common\Cache\ArrayCache; use Doctrine\Tests\Mocks\CacheKeyMock; use Doctrine\Tests\Mocks\CacheEntryMock; use Doctrine\ORM\Cache\Region\DefaultRegion; -require_once __DIR__ . '/../../TestInit.php'; - /** * @group DDC-2183 */ -abstract class AbstractRegionAccessTest extends \Doctrine\Tests\OrmTestCase +abstract class AbstractRegionAccessTest extends OrmTestCase { /** * @var \Doctrine\Common\Cache\Cache @@ -116,37 +115,4 @@ public function testEvictAll() $this->assertNull($this->regionAccess->get($key1)); $this->assertNull($this->regionAccess->get($key2)); } - - public function testAfterInsert() - { - $key1 = new CacheKeyMock('key.1'); - $key2 = new CacheKeyMock('key.2'); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->afterInsert($key1, new CacheEntryMock(array('value' => 'foo'))); - $this->regionAccess->afterInsert($key2, new CacheEntryMock(array('value' => 'bar'))); - - $this->assertNotNull($this->regionAccess->get($key1)); - $this->assertNotNull($this->regionAccess->get($key2)); - - $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); - } - - public function testAfterUpdate() - { - $key1 = new CacheKeyMock('key.1'); - $key2 = new CacheKeyMock('key.2'); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->afterUpdate($key1, new CacheEntryMock(array('value' => 'foo'))); - $this->regionAccess->afterUpdate($key2, new CacheEntryMock(array('value' => 'bar'))); - - $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); - } } diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index d1f406ddfeb..be75cc47ae8 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -75,13 +75,13 @@ public function testImplementsCache() public function testGetEntityCacheRegionAccess() { - $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccess', $this->cache->getEntityCacheRegionAccess(State::CLASSNAME)); + $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccessStrategy', $this->cache->getEntityCacheRegionAccess(State::CLASSNAME)); $this->assertNull($this->cache->getEntityCacheRegionAccess(self::NON_CACHEABLE_ENTITY)); } public function testGetCollectionCacheRegionAccess() { - $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccess', $this->cache->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')); + $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccessStrategy', $this->cache->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')); $this->assertNull($this->cache->getCollectionCacheRegionAccess(self::NON_CACHEABLE_ENTITY, 'phonenumbers')); } diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php index 87e74cbefbd..33d1b199b81 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php @@ -40,7 +40,7 @@ class DefaultQueryCacheTest extends OrmTestCase private $region; /** - * @var \Doctrine\ORM\Cache\Access\ConcurrentRegionAccessStrategy\CacheFactorySecondLevelCacheConcurrentTest + * @var \Doctrine\Tests\ORM\Cache\CacheFactoryDefaultQueryCacheTest */ private $cacheFactory; @@ -518,12 +518,12 @@ public function __construct(DefaultQueryCache $queryCache, CacheRegionMock $regi public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) { - return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy($this->region); + return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteEntityRegionAccessStrategy($this->region); } public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) { - return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteRegionAccessStrategy($this->region); + return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteCollectionRegionAccessStrategy($this->region); } public function buildQueryCache(EntityManagerInterface $em, $regionName = null) diff --git a/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php b/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php new file mode 100644 index 00000000000..47e7b91934f --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php @@ -0,0 +1,17 @@ +cache['region']; $region = $this->createRegion($regionName); - $access = new ConcurrentRegionAccessStrategy($region); + $access = new ReadWriteEntityRegionAccessStrategy($region); return $access; } @@ -260,7 +260,7 @@ public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fi $mapping = $metadata->getAssociationMapping($fieldName); $regionName = $mapping['cache']['region']; $region = $this->createRegion($regionName); - $access = new ConcurrentRegionAccessStrategy($region); + $access = new ReadWriteCollectionRegionAccessStrategy($region); return $access; } diff --git a/tests/travis/mysql.travis.xml b/tests/travis/mysql.travis.xml index 9177e2f51fa..e278e88596d 100644 --- a/tests/travis/mysql.travis.xml +++ b/tests/travis/mysql.travis.xml @@ -1,5 +1,5 @@ - + diff --git a/tests/travis/pgsql.travis.xml b/tests/travis/pgsql.travis.xml index 1280d95c852..a2993fcb173 100644 --- a/tests/travis/pgsql.travis.xml +++ b/tests/travis/pgsql.travis.xml @@ -1,5 +1,5 @@ - + diff --git a/tests/travis/sqlite.travis.xml b/tests/travis/sqlite.travis.xml index 7dbe99fe1bf..242d4dea8c4 100644 --- a/tests/travis/sqlite.travis.xml +++ b/tests/travis/sqlite.travis.xml @@ -1,5 +1,5 @@ - + From f757894f0f99c1d0884b6256682dd489a9f5203a Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 23 Sep 2013 14:32:18 -0400 Subject: [PATCH 65/71] Drop RegionAccessStrategy in favor of CachedPersister --- .travis.yml | 2 +- docs/en/reference/second-level-cache.rst | 212 +++------ lib/Doctrine/ORM/Cache.php | 8 +- .../Access/AbstractRegionAccessStrategy.php | 88 ---- ...ictReadWriteEntityRegionAccessStrategy.php | 51 --- ...eadWriteCollectionRegionAccessStrategy.php | 102 ----- .../ReadWriteEntityRegionAccessStrategy.php | 149 ------- lib/Doctrine/ORM/Cache/CacheEntry.php | 2 +- lib/Doctrine/ORM/Cache/CacheException.php | 19 +- lib/Doctrine/ORM/Cache/CacheFactory.php | 55 +-- lib/Doctrine/ORM/Cache/CacheKey.php | 6 +- .../ORM/Cache/CollectionCacheEntry.php | 2 +- lib/Doctrine/ORM/Cache/CollectionCacheKey.php | 25 +- ...ryStructure.php => CollectionHydrator.php} | 12 +- .../Cache/CollectionRegionAccessStrategy.php | 31 -- lib/Doctrine/ORM/Cache/ConcurrentRegion.php | 28 +- .../Cache/ConcurrentRegionAccessStrategy.php | 51 --- lib/Doctrine/ORM/Cache/DefaultCache.php | 27 +- .../ORM/Cache/DefaultCacheFactory.php | 56 ++- ...ture.php => DefaultCollectionHydrator.php} | 8 +- ...tructure.php => DefaultEntityHydrator.php} | 6 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 23 +- lib/Doctrine/ORM/Cache/EntityCacheEntry.php | 2 +- lib/Doctrine/ORM/Cache/EntityCacheKey.php | 28 +- ...yEntryStructure.php => EntityHydrator.php} | 12 +- .../ORM/Cache/EntityRegionAccessStrategy.php | 54 --- lib/Doctrine/ORM/Cache/Lock.php | 19 +- .../ORM/Cache/Logging/CacheLogger.php | 28 +- .../Cache/Logging/StatisticsCacheLogger.php | 4 +- .../Persister/AbstractCollectionPersister.php | 275 ++++++++++++ .../AbstractEntityPersister.php} | 278 +++--------- .../Persister/CachedCollectionPersister.php | 64 +++ .../CachedEntityPersister.php} | 23 +- .../CachedPersister.php | 7 +- ...rictReadWriteCachedCollectionPersister.php | 105 +++++ ...onStrictReadWriteCachedEntityPersister.php | 116 +++++ .../ReadOnlyCachedCollectionPersister.php} | 24 +- .../ReadOnlyCachedEntityPersister.php} | 18 +- .../ReadWriteCachedCollectionPersister.php | 134 ++++++ .../ReadWriteCachedEntityPersister.php | 131 ++++++ .../Persisters/CachedCollectionPersister.php | 390 ---------------- lib/Doctrine/ORM/Cache/QueryCache.php | 10 +- lib/Doctrine/ORM/Cache/QueryCacheEntry.php | 2 +- lib/Doctrine/ORM/Cache/QueryCacheKey.php | 17 +- .../ORM/Cache/QueryCacheValidator.php | 2 +- lib/Doctrine/ORM/Cache/Region.php | 2 +- .../ORM/Cache/Region/DefaultRegion.php | 4 +- .../ORM/Cache/RegionAccessStrategy.php | 75 ---- .../Cache/TimestampQueryCacheValidator.php | 2 +- lib/Doctrine/ORM/Configuration.php | 29 ++ lib/Doctrine/ORM/Query/SqlWalker.php | 3 +- lib/Doctrine/ORM/UnitOfWork.php | 13 +- tests/Doctrine/Tests/Mocks/CacheKeyMock.php | 7 +- .../Tests/Mocks/ConcurrentRegionMock.php | 54 +-- tests/Doctrine/Tests/Models/Cache/Travel.php | 2 +- .../Cache/AbstractEntityRegionAccessTest.php | 46 -- .../ORM/Cache/AbstractRegionAccessTest.php | 118 ----- .../Doctrine/Tests/ORM/Cache/CacheKeyTest.php | 14 +- .../Tests/ORM/Cache/DefaultCacheTest.php | 12 +- ....php => DefaultCollectionHydratorTest.php} | 12 +- ...Test.php => DefaultEntityHydratorTest.php} | 11 +- .../Tests/ORM/Cache/DefaultQueryCacheTest.php | 27 +- ...riteCollectionRegionAccessStrategyTest.php | 17 - ...eadWriteEntityRegionAccessStrategyTest.php | 17 - .../AbstractCollectionPersisterTest.php | 300 +++++++++++++ .../Persister/AbstractEntityPersisterTest.php | 415 ++++++++++++++++++ ...ReadWriteCachedCollectionPersisterTest.php | 23 + ...rictReadWriteCachedEntityPersisterTest.php | 24 + .../ReadOnlyCachedCollectionPersisterTest.php | 22 + .../ReadOnlyCachedEntityPersisterTest.php | 36 ++ ...ReadWriteCachedCollectionPersisterTest.php | 41 ++ .../ReadWriteCachedEntityPersisterTest.php | 42 ++ ...OnlyCollectionRegionAccessStrategyTest.php | 17 - ...ReadOnlyEntityRegionAccessStrategyTest.php | 28 -- ...riteCollectionRegionAccessStrategyTest.php | 29 -- ...eadWriteEntityRegionAccessStrategyTest.php | 111 ----- .../SecondLevelCacheAbstractTest.php | 4 +- .../SecondLevelCacheConcurrentTest.php | 191 +------- ...condLevelCacheJoinTableInheritanceTest.php | 6 +- .../SecondLevelCacheManyToManyTest.php | 43 +- .../SecondLevelCacheQueryCacheTest.php | 2 +- .../SecondLevelCacheRepositoryTest.php | 2 +- ...ndLevelCacheSingleTableInheritanceTest.php | 8 +- 83 files changed, 2230 insertions(+), 2285 deletions(-) delete mode 100644 lib/Doctrine/ORM/Cache/Access/AbstractRegionAccessStrategy.php delete mode 100644 lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php delete mode 100644 lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php delete mode 100644 lib/Doctrine/ORM/Cache/Access/ReadWriteEntityRegionAccessStrategy.php rename lib/Doctrine/ORM/Cache/{CollectionEntryStructure.php => CollectionHydrator.php} (79%) delete mode 100644 lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php delete mode 100644 lib/Doctrine/ORM/Cache/ConcurrentRegionAccessStrategy.php rename lib/Doctrine/ORM/Cache/{DefaultCollectionEntryStructure.php => DefaultCollectionHydrator.php} (93%) rename lib/Doctrine/ORM/Cache/{DefaultEntityEntryStructure.php => DefaultEntityHydrator.php} (96%) rename lib/Doctrine/ORM/Cache/{EntityEntryStructure.php => EntityHydrator.php} (79%) delete mode 100644 lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php create mode 100644 lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php rename lib/Doctrine/ORM/Cache/{Persisters/CachedEntityPersister.php => Persister/AbstractEntityPersister.php} (57%) create mode 100644 lib/Doctrine/ORM/Cache/Persister/CachedCollectionPersister.php rename lib/Doctrine/ORM/Cache/{Access/NonStrictReadWriteCollectionRegionAccessStrategy.php => Persister/CachedEntityPersister.php} (60%) rename lib/Doctrine/ORM/Cache/{Persisters => Persister}/CachedPersister.php (92%) create mode 100644 lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersister.php create mode 100644 lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersister.php rename lib/Doctrine/ORM/Cache/{Access/ReadOnlyCollectionRegionAccessStrategy.php => Persister/ReadOnlyCachedCollectionPersister.php} (59%) rename lib/Doctrine/ORM/Cache/{Access/ReadOnlyEntityRegionAccessStrategy.php => Persister/ReadOnlyCachedEntityPersister.php} (69%) create mode 100644 lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php create mode 100644 lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php delete mode 100644 lib/Doctrine/ORM/Cache/Persisters/CachedCollectionPersister.php delete mode 100644 lib/Doctrine/ORM/Cache/RegionAccessStrategy.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php rename tests/Doctrine/Tests/ORM/Cache/{DefaultCollectionEntryStructureTest.php => DefaultCollectionHydratorTest.php} (87%) rename tests/Doctrine/Tests/ORM/Cache/{DefaultEntityEntryStructureTest.php => DefaultEntityHydratorTest.php} (91%) delete mode 100644 tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteEntityRegionAccessStrategyTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/AbstractCollectionPersisterTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersisterTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersisterTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/ReadOnlyCachedCollectionPersisterTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/ReadOnlyCachedEntityPersisterTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/ReadOnlyCollectionRegionAccessStrategyTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/ReadOnlyEntityRegionAccessStrategyTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/ReadWriteCollectionRegionAccessStrategyTest.php delete mode 100644 tests/Doctrine/Tests/ORM/Cache/ReadWriteEntityRegionAccessStrategyTest.php diff --git a/.travis.yml b/.travis.yml index 9f3d6e6ab6c..2c35f8999fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ before_script: - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'create database IF NOT EXISTS doctrine_tests_tmp;create database IF NOT EXISTS doctrine_tests;'; fi" - composer install --prefer-dist --dev -script: phpunit --configuration tests/travis/$DB.travis.xml +script: phpunit -v --configuration tests/travis/$DB.travis.xml after_script: - php vendor/bin/coveralls -v diff --git a/docs/en/reference/second-level-cache.rst b/docs/en/reference/second-level-cache.rst index 63ed0de42ce..a0763dbc808 100644 --- a/docs/en/reference/second-level-cache.rst +++ b/docs/en/reference/second-level-cache.rst @@ -75,7 +75,6 @@ It allows you to provide your own cache implementation that might take advantage If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise. - ``Doctrine\ORM\Cache\Region`` Defines a contract for accessing a particular cache region. @@ -179,8 +178,8 @@ Defines contract for concurrently managed data region. public function readUnlock(CacheKey $key, Lock $lock); } -Caching Strategies ------------------- +Caching mode +------------ * ``READ_ONLY`` (DEFAULT) @@ -203,115 +202,26 @@ Caching Strategies * To use it a the cache region implementation must support locking. -Built-in Strategies +Built-in cached persisters ~~~~~~~~~~~~~~~~~~~ -Caching Strategies are responsible for access cache regions. - -* ``Doctrine\ORM\Cache\Access\ReadOnlyEntityRegionAccessStrategy`` implements ``READ_ONLY`` for entities -* ``Doctrine\ORM\Cache\Access\ReadOnlyCollectionRegionAccessStrategy`` implements ``READ_ONLY`` for collections -* ``Doctrine\ORM\Cache\Access\NonStrictReadWriteEntityRegionAccessStrategy`` implements ``NONSTRICT_READ_WRITE`` for entities -* ``Doctrine\ORM\Cache\Access\NonStrictReadWriteCollectionRegionAccessStrategy`` implements ``NONSTRICT_READ_WRITE`` for collections -* ``Doctrine\ORM\Cache\Access\ReadWriteEntityRegionAccessStrategy`` implements ``READ_WRITE`` requires a ``ConcurrentRegion`` for entities -* ``Doctrine\ORM\Cache\Access\ReadWriteCollectionRegionAccessStrategy`` implements ``READ_WRITE`` requires a ``ConcurrentRegion`` for collections - -``Doctrine\ORM\Cache\RegionAccessStrategy``, ``Doctrine\ORM\Cache\CollectionRegionAccessStrategy`` and ``Doctrine\ORM\Cache\ConcurrentRegionAccessStrategy`` -Defines contracts that should be implemented by caching strategies. - -If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegionAccessStrategy``; ``RegionAccessStrategy`` otherwise. - - -``Doctrine\ORM\Cache\RegionAccessStrategy`` - -Interface for all region access strategies. - -.. code-block:: php - - null - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function get(CacheKey $key); - - /** - * Attempt to cache an object, after loading from the database. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. - * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. - * - * @return TRUE if the object was successfully cached. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function put(CacheKey $key, CacheEntry $entry); - - /** - * Forcibly evict an item from the cache immediately without regard for locks. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to remove. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function evict(CacheKey $key); - - /** - * Forcibly evict all items from the cache immediately without regard for locks. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function evictAll(); - } - - -``Doctrine\ORM\Cache\RegionAccess`` - -Defines contract for regions which hold concurrently managed data. - -.. code-block:: php - - . - */ - -namespace Doctrine\ORM\Cache\Access; - -use Doctrine\ORM\Cache\RegionAccessStrategy; -use Doctrine\ORM\Cache\CacheEntry; -use Doctrine\ORM\Cache\CacheKey; -use Doctrine\ORM\Cache\Region; - -/** - * Abstract region access strategy - * - * @since 2.5 - * @author Fabio B. Silva - */ -abstract class AbstractRegionAccessStrategy implements RegionAccessStrategy -{ - /** - * @var \Doctrine\ORM\Cache\Region - */ - protected $region; - - /** - * @param \Doctrine\ORM\Cache\Region $region - */ - public function __construct(Region $region) - { - $this->region = $region; - } - - /** - * {@inheritdoc} - */ - public function getRegion() - { - return $this->region; - } - - /** - * {@inheritdoc} - */ - public function get(CacheKey $key) - { - return $this->region->get($key); - } - - /** - * {@inheritdoc} - */ - public function put(CacheKey $key, CacheEntry $entry) - { - return $this->region->put($key, $entry); - } - - /** - * {@inheritdoc} - */ - public function evict(CacheKey $key) - { - return $this->region->evict($key); - } - - /** - * {@inheritdoc} - */ - public function evictAll() - { - return $this->region->evictAll(); - } -} diff --git a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php deleted file mode 100644 index 24203755247..00000000000 --- a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteEntityRegionAccessStrategy.php +++ /dev/null @@ -1,51 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache\Access; - -use Doctrine\ORM\Cache\EntityRegionAccessStrategy; -use Doctrine\ORM\Cache\CacheEntry; -use Doctrine\ORM\Cache\CacheKey; -use Doctrine\ORM\Cache\Lock; - -/** - * Specific non-strict read/write region access strategy - * - * @since 2.5 - * @author Fabio B. Silva - */ -class NonStrictReadWriteEntityRegionAccessStrategy extends AbstractRegionAccessStrategy implements EntityRegionAccessStrategy -{ - /** - * {@inheritdoc} - */ - public function afterInsert(CacheKey $key, CacheEntry $entry) - { - return $this->region->put($key, $entry); - } - - /** - * {@inheritdoc} - */ - public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null) - { - return $this->region->put($key, $entry); - } -} diff --git a/lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php deleted file mode 100644 index e2da56d26ef..00000000000 --- a/lib/Doctrine/ORM/Cache/Access/ReadWriteCollectionRegionAccessStrategy.php +++ /dev/null @@ -1,102 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache\Access; - -use Doctrine\ORM\Cache\ConcurrentRegionAccessStrategy; -use Doctrine\ORM\Cache\CollectionRegionAccessStrategy; -use Doctrine\ORM\Cache\ConcurrentRegion; -use Doctrine\ORM\Cache\CacheException; -use Doctrine\ORM\Cache\LockException; -use Doctrine\ORM\Cache\CacheEntry; -use Doctrine\ORM\Cache\CacheKey; -use Doctrine\ORM\Cache\Lock; - -/** - * Region access strategies for concurrently managed data. - * - * @since 2.5 - * @author Fabio B. Silva - */ -class ReadWriteCollectionRegionAccessStrategy extends AbstractRegionAccessStrategy implements ConcurrentRegionAccessStrategy, CollectionRegionAccessStrategy -{ - /** - * @param \Doctrine\ORM\Cache\ConcurrentRegion $region - */ - public function __construct(ConcurrentRegion $region) - { - $this->region = $region; - } - - /** - * {@inheritdoc} - */ - public function put(CacheKey $key, CacheEntry $entry) - { - $writeLock = null; - - try { - if ( ! ($writeLock = $this->region->writeLock($key))) { - return false; - } - - if ( ! $this->region->put($key, $entry, $writeLock)) { - return false; - } - - $this->region->writeUnlock($key, $writeLock); - - return true; - - } catch (LockException $exc) { - - if ($writeLock) { - $this->region->writeUnlock($key, $writeLock); - } - - throw new $exc; - } catch (\Exception $exc) { - - if ($writeLock) { - $this->region->writeUnlock($key, $writeLock); - } - - throw new CacheException($exc->getMessage(), $exc->getCode(), $exc); - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function lockItem(CacheKey $key) - { - return $this->region->readLock($key); - } - - /** - * {@inheritdoc} - */ - public function unlockItem(CacheKey $key, Lock $lock) - { - return $this->region->readUnlock($key, $lock); - } -} diff --git a/lib/Doctrine/ORM/Cache/Access/ReadWriteEntityRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Access/ReadWriteEntityRegionAccessStrategy.php deleted file mode 100644 index 77b5818924a..00000000000 --- a/lib/Doctrine/ORM/Cache/Access/ReadWriteEntityRegionAccessStrategy.php +++ /dev/null @@ -1,149 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache\Access; - -use Doctrine\ORM\Cache\ConcurrentRegionAccessStrategy; -use Doctrine\ORM\Cache\EntityRegionAccessStrategy; -use Doctrine\ORM\Cache\ConcurrentRegion; -use Doctrine\ORM\Cache\CacheException; -use Doctrine\ORM\Cache\LockException; -use Doctrine\ORM\Cache\CacheEntry; -use Doctrine\ORM\Cache\CacheKey; -use Doctrine\ORM\Cache\Lock; - -/** - * Region access strategies for concurrently managed data. - * - * @since 2.5 - * @author Fabio B. Silva - */ -class ReadWriteEntityRegionAccessStrategy extends AbstractRegionAccessStrategy implements ConcurrentRegionAccessStrategy, EntityRegionAccessStrategy -{ - /** - * @param \Doctrine\ORM\Cache\ConcurrentRegion $region - */ - public function __construct(ConcurrentRegion $region) - { - $this->region = $region; - } - - /** - * {@inheritdoc} - */ - public function afterInsert(CacheKey $key, CacheEntry $entry) - { - $writeLock = null; - - try { - if ( ! ($writeLock = $this->region->writeLock($key))) { - return false; - } - - if ( ! $this->region->put($key, $entry, $writeLock)) { - return false; - } - - $this->region->writeUnlock($key, $writeLock); - - return true; - - } catch (LockException $exc) { - - if ($writeLock) { - $this->region->writeUnlock($key, $writeLock); - } - - throw new $exc; - } catch (\Exception $exc) { - - if ($writeLock) { - $this->region->writeUnlock($key, $writeLock); - } - - throw new CacheException($exc->getMessage(), $exc->getCode(), $exc); - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $readLock = null) - { - $writeLock = null; - - try { - if ( ! ($writeLock = $this->region->writeLock($key, $readLock))) { - return false; - } - - if ( ! $this->region->put($key, $entry, $writeLock)) { - return false; - } - - $this->region->writeUnlock($key, $writeLock); - - return true; - - } catch (LockException $exc) { - - if ($readLock) { - $this->region->readUnlock($key, $writeLock); - } - - if ($writeLock) { - $this->region->writeUnlock($key, $writeLock); - } - - throw new $exc; - } catch (\Exception $exc) { - - if ($readLock) { - $this->region->readUnlock($key, $writeLock); - } - - if ($writeLock) { - $this->region->writeUnlock($key, $writeLock); - } - - throw new CacheException($exc->getMessage(), $exc->getCode(), $exc); - } - - return false; - } - - /** - * {@inheritdoc} - */ - public function lockItem(CacheKey $key) - { - return $this->region->readLock($key); - } - - /** - * {@inheritdoc} - */ - public function unlockItem(CacheKey $key, Lock $lock) - { - return $this->region->readUnlock($key, $lock); - } -} diff --git a/lib/Doctrine/ORM/Cache/CacheEntry.php b/lib/Doctrine/ORM/Cache/CacheEntry.php index 5b775615708..cdcb6c0fb70 100644 --- a/lib/Doctrine/ORM/Cache/CacheEntry.php +++ b/lib/Doctrine/ORM/Cache/CacheEntry.php @@ -29,4 +29,4 @@ interface CacheEntry { -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/CacheException.php b/lib/Doctrine/ORM/Cache/CacheException.php index 068396c8d42..5b548fe5ec7 100644 --- a/lib/Doctrine/ORM/Cache/CacheException.php +++ b/lib/Doctrine/ORM/Cache/CacheException.php @@ -30,15 +30,29 @@ class CacheException extends ORMException { /** + * @param string $sourceEntity + * @param string $fieldName + * * @return \Doctrine\ORM\Cache\CacheException */ - public static function updateReadOnlyobject() + public static function updateReadOnlyCollection($sourceEntity, $fieldName) { - return new self("Can't update a readonly object"); + return new self(sprintf('Cannot update a readonly collection "%s#%s"', $sourceEntity, $fieldName)); } /** * @param string $entityName + * + * @return \Doctrine\ORM\Cache\CacheException + */ + public static function updateReadOnlyEntity($entityName) + { + return new self(sprintf('Cannot update a readonly entity "%s"', $entityName)); + } + + /** + * @param string $entityName + * * @return \Doctrine\ORM\Cache\CacheException */ public static function nonCacheableEntity($entityName) @@ -48,6 +62,7 @@ public static function nonCacheableEntity($entityName) /** * @param string $entityName + * * @return \Doctrine\ORM\Cache\CacheException */ public static function nonCacheableEntityAssociation($entityName, $field) diff --git a/lib/Doctrine/ORM/Cache/CacheFactory.php b/lib/Doctrine/ORM/Cache/CacheFactory.php index b79d8b8fabb..68c3d8284fd 100644 --- a/lib/Doctrine/ORM/Cache/CacheFactory.php +++ b/lib/Doctrine/ORM/Cache/CacheFactory.php @@ -23,6 +23,9 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Persisters\CollectionPersister; +use Doctrine\ORM\Persisters\EntityPersister; + /** * @since 2.5 * @author Fabio B. Silva @@ -30,52 +33,52 @@ interface CacheFactory { /** - * Build an entity RegionAccess for the input entity. - * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * Build an entity persister for the given entity metadata. * - * @return \Doctrine\ORM\Cache\EntityRegionAccessStrategy The built region access. + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param \Doctrine\ORM\Persisters\EntityPersister $persister The entity persister that will be cached. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * - * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + * @return \Doctrine\ORM\Cache\Persister\CachedEntityPersister */ - public function buildEntityRegionAccessStrategy(ClassMetadata $metadata); + public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata); /** - * Build an collection RegionAccess for the input entity accociation. + * Build a collection persister for the given relation mapping. * - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param string $fieldName The field name that represents the association. + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param \Doctrine\ORM\Persisters\CollectionPersister $persister The collection persister that will be cached. + * @param array $mapping The association mapping. * - * @return \Doctrine\ORM\Cache\CollectionRegionAccessStrategy The built region access. - * - * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + * @return \Doctrine\ORM\Cache\Persister\CachedCollectionPersister */ - public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName); + public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, $mapping); /** - * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. - * @param string $regionName The region name. + * Build a query cache based on the given region name + * + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param string $regionName The region name. * * @return \Doctrine\ORM\Cache\QueryCache The built query cache, or default query cache if the region name is NULL. - * - * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. */ public function buildQueryCache(EntityManagerInterface $em, $regionName = null); /** - * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. - * @return \Doctrine\ORM\Cache\EntityEntryStructure The built entity entry structure. + * Build an entity hidrator * - * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * + * @return \Doctrine\ORM\Cache\EntityHydrator The built entity hidrator. */ - public function buildEntityEntryStructure(EntityManagerInterface $em); + public function buildEntityHydrator(EntityManagerInterface $em); /** + * Build a collection hidrator + * * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. - * @return \Doctrine\ORM\Cache\CollectionEntryStructure The built collection entry structure. * - * @throws \Doctrine\ORM\Cache\CacheException Indicates problems building the region access. + * @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hidrator. */ - public function buildCollectionEntryStructure(EntityManagerInterface $em); - -} \ No newline at end of file + public function buildCollectionHydrator(EntityManagerInterface $em); +} diff --git a/lib/Doctrine/ORM/Cache/CacheKey.php b/lib/Doctrine/ORM/Cache/CacheKey.php index e613efe60d8..ede2030913c 100644 --- a/lib/Doctrine/ORM/Cache/CacheKey.php +++ b/lib/Doctrine/ORM/Cache/CacheKey.php @@ -27,10 +27,10 @@ * @since 2.5 * @author Fabio B. Silva */ -interface CacheKey +abstract class CacheKey { /** - * @return string Unique identifier + * @var string Unique identifier */ - public function hash(); + public $hash; } diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php index ab1c993e49d..5f77ac30236 100644 --- a/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php @@ -40,4 +40,4 @@ public function __construct(array $identifiers) { $this->identifiers = $identifiers; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheKey.php b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php index 377d881a10e..e2c74355520 100644 --- a/lib/Doctrine/ORM/Cache/CollectionCacheKey.php +++ b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php @@ -26,7 +26,7 @@ * @since 2.5 * @author Fabio B. Silva */ -class CollectionCacheKey implements CacheKey +class CollectionCacheKey extends CacheKey { /** * @var array @@ -43,11 +43,6 @@ class CollectionCacheKey implements CacheKey */ public $association; - /** - * @var string - */ - private $hash; - /** * @param string $entityClass The entity class. * @param string $association The field name that represents the association. @@ -55,23 +50,11 @@ class CollectionCacheKey implements CacheKey */ public function __construct($entityClass, $association, array $ownerIdentifier) { + ksort($ownerIdentifier); + $this->entityClass = $entityClass; $this->association = $association; $this->ownerIdentifier = $ownerIdentifier; - } - - /** - * {@inheritdoc} - */ - public function hash() - { - if ($this->hash === null) { - - ksort($this->ownerIdentifier); - - $this->hash = sprintf('%s[%s].%s', str_replace('\\', '.', strtolower($this->entityClass)), implode(' ', $this->ownerIdentifier), $this->association); - } - - return $this->hash; + $this->hash = sprintf('%s[%s].%s', str_replace('\\', '.', strtolower($entityClass)), implode(' ', $ownerIdentifier), $association); } } diff --git a/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/CollectionHydrator.php similarity index 79% rename from lib/Doctrine/ORM/Cache/CollectionEntryStructure.php rename to lib/Doctrine/ORM/Cache/CollectionHydrator.php index 9809cd3b3fb..9b3141dd518 100644 --- a/lib/Doctrine/ORM/Cache/CollectionEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/CollectionHydrator.php @@ -26,12 +26,12 @@ use Doctrine\ORM\Cache\CollectionCacheEntry; /** - * Structure cache entry for collections + * Hidrator cache entry for collections * * @since 2.5 * @author Fabio B. Silva */ -interface CollectionEntryStructure +interface CollectionHydrator { /** * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. @@ -43,10 +43,10 @@ interface CollectionEntryStructure public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection); /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata. - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key. - * @param \Doctrine\ORM\Cache\CollectionCacheEntry $entry The cached collection entry. - * @param Doctrine\ORM\PersistentCollection $collection The collection to load the cache into. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The owning entity metadata. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cached collection key. + * @param \Doctrine\ORM\Cache\CollectionCacheEntry $entry The cached collection entry. + * @param Doctrine\ORM\PersistentCollection $collection The collection to load the cache into. * * @return array */ diff --git a/lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php deleted file mode 100644 index 4c93d6ee746..00000000000 --- a/lib/Doctrine/ORM/Cache/CollectionRegionAccessStrategy.php +++ /dev/null @@ -1,31 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache; - -/** - * Interface for entity region access. - * - * @since 2.5 - * @author Fabio B. Silva - */ -interface CollectionRegionAccessStrategy extends RegionAccessStrategy -{ - -} diff --git a/lib/Doctrine/ORM/Cache/ConcurrentRegion.php b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php index eec5af0052a..a3c558686ed 100644 --- a/lib/Doctrine/ORM/Cache/ConcurrentRegion.php +++ b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php @@ -30,30 +30,6 @@ */ interface ConcurrentRegion extends Region { - /** - * Attempts to write lock the mapping for the given key. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link writeLock} - * - * @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists. - * - * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. - */ - public function writeLock(CacheKey $key, Lock $lock = null); - - /** - * Attempts to write unlock the mapping for the given key. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link writeLock} - * - * @return void - * - * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. - */ - public function writeUnlock(CacheKey $key, Lock $lock); - /** * Attempts to read lock the mapping for the given key. * @@ -68,8 +44,8 @@ public function readLock(CacheKey $key); /** * Attempts to read unlock the mapping for the given key. * - * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link readLock} + * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. + * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link readLock} * * @return void * diff --git a/lib/Doctrine/ORM/Cache/ConcurrentRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/ConcurrentRegionAccessStrategy.php deleted file mode 100644 index 22fbe940fb9..00000000000 --- a/lib/Doctrine/ORM/Cache/ConcurrentRegionAccessStrategy.php +++ /dev/null @@ -1,51 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache; - -/** - * Defines contract for regions which hold concurrently managed data. - * - * @since 2.5 - * @author Fabio B. Silva - */ -interface ConcurrentRegionAccessStrategy extends RegionAccessStrategy -{ - /** - * We are going to attempt to update/delete the keyed object. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. - * - * @return \Doctrine\ORM\Cache\Lock A representation of our lock on the item - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function lockItem(CacheKey $key); - - /** - * Called when we have finished the attempted update/delete (which may or may not have been successful), after transaction completion. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to unlock. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function unlockItem(CacheKey $key, Lock $lock); -} \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/DefaultCache.php b/lib/Doctrine/ORM/Cache/DefaultCache.php index e1f6a4f8d56..92f0c639619 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultCache.php @@ -26,7 +26,7 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\Cache\Persisters\CachedPersister; +use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\ORMInvalidArgumentException; /** @@ -75,7 +75,7 @@ public function __construct(EntityManagerInterface $em) /** * {@inheritdoc} */ - public function getEntityCacheRegionAccess($className) + public function getEntityCacheRegion($className) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getEntityPersister($metadata->rootEntityName); @@ -84,13 +84,13 @@ public function getEntityCacheRegionAccess($className) return null; } - return $persister->getCacheRegionAcess(); + return $persister->getCacheRegion(); } /** * {@inheritdoc} */ - public function getCollectionCacheRegionAccess($className, $association) + public function getCollectionCacheRegion($className, $association) { $metadata = $this->em->getClassMetadata($className); $persister = $this->uow->getCollectionPersister($metadata->getAssociationMapping($association)); @@ -99,7 +99,7 @@ public function getCollectionCacheRegionAccess($className, $association) return null; } - return $persister->getCacheRegionAcess(); + return $persister->getCacheRegion(); } /** @@ -115,7 +115,7 @@ public function containsEntity($className, $identifier) return false; } - return $persister->getCacheRegionAcess()->getRegion()->contains($key); + return $persister->getCacheRegion()->contains($key); } /** @@ -131,7 +131,7 @@ public function evictEntity($className, $identifier) return; } - $persister->getCacheRegionAcess()->evict($key); + $persister->getCacheRegion()->evict($key); } /** @@ -146,7 +146,7 @@ public function evictEntityRegion($className) return; } - $persister->getCacheRegionAcess()->evictAll(); + $persister->getCacheRegion()->evictAll(); } /** @@ -163,7 +163,7 @@ public function evictEntityRegions() continue; } - $persister->getCacheRegionAcess()->evictAll(); + $persister->getCacheRegion()->evictAll(); } } @@ -180,7 +180,7 @@ public function containsCollection($className, $association, $ownerIdentifier) return false; } - return $persister->getCacheRegionAcess()->getRegion()->contains($key); + return $persister->getCacheRegion()->contains($key); } /** @@ -196,7 +196,7 @@ public function evictCollection($className, $association, $ownerIdentifier) return; } - $persister->getCacheRegionAcess()->evict($key); + $persister->getCacheRegion()->evict($key); } /** @@ -211,7 +211,7 @@ public function evictCollectionRegion($className, $association) return; } - $persister->getCacheRegionAcess()->evictAll(); + $persister->getCacheRegion()->evictAll(); } /** @@ -224,6 +224,7 @@ public function evictCollectionRegions() foreach ($metadatas as $metadata) { foreach ($metadata->associationMappings as $association) { + if ( ! $association['type'] & ClassMetadata::TO_MANY) { continue; } @@ -234,7 +235,7 @@ public function evictCollectionRegions() continue; } - $persister->getCacheRegionAcess()->evictAll(); + $persister->getCacheRegion()->evictAll(); } } } diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index 71ea9d4a430..f203989beea 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -26,10 +26,15 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\Common\Cache\Cache as CacheDriver; -use Doctrine\ORM\Cache\Access\ReadOnlyEntityRegionAccessStrategy; -use Doctrine\ORM\Cache\Access\ReadOnlyCollectionRegionAccessStrategy; -use Doctrine\ORM\Cache\Access\NonStrictReadWriteEntityRegionAccessStrategy; -use Doctrine\ORM\Cache\Access\NonStrictReadWriteCollectionRegionAccessStrategy; + +use Doctrine\ORM\Persisters\EntityPersister; +use Doctrine\ORM\Persisters\CollectionPersister; +use Doctrine\ORM\Cache\Persister\ReadOnlyCachedEntityPersister; +use Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister; +use Doctrine\ORM\Cache\Persister\ReadOnlyCachedCollectionPersister; +use Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister; +use Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedEntityPersister; +use Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedCollectionPersister; /** * @since 2.5 @@ -47,6 +52,10 @@ class DefaultCacheFactory implements CacheFactory */ private $configuration; + /** + * @param \Doctrine\ORM\Configuration $configuration + * @param \Doctrine\Common\Cache\Cache $cache + */ public function __construct(Configuration $configuration, CacheDriver $cache) { $this->cache = $cache; @@ -56,17 +65,22 @@ public function __construct(Configuration $configuration, CacheDriver $cache) /** * {@inheritdoc} */ - public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) + public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata) { $regionName = $metadata->cache['region']; $usage = $metadata->cache['usage']; + $region = $this->createRegion($regionName); if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { - return new ReadOnlyEntityRegionAccessStrategy($this->createRegion($regionName)); + return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { - return new NonStrictReadWriteEntityRegionAccessStrategy($this->createRegion($regionName)); + return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata); + } + + if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { + return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata); } throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); @@ -75,18 +89,22 @@ public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) /** * {@inheritdoc} */ - public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) + public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, $mapping) { - $mapping = $metadata->getAssociationMapping($fieldName); $regionName = $mapping['cache']['region']; $usage = $mapping['cache']['usage']; + $region = $this->createRegion($regionName); if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { - return new ReadOnlyCollectionRegionAccessStrategy($this->createRegion($regionName)); + return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping); } if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { - return new NonStrictReadWriteCollectionRegionAccessStrategy($this->createRegion($regionName)); + return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); + } + + if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); } throw new \InvalidArgumentException(sprintf("Unrecognized access strategy type [%s]", $usage)); @@ -103,28 +121,28 @@ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) /** * {@inheritdoc} */ - public function buildCollectionEntryStructure(EntityManagerInterface $em) + public function buildCollectionHydrator(EntityManagerInterface $em) { - return new DefaultCollectionEntryStructure($em); + return new DefaultCollectionHydrator($em); } /** * {@inheritdoc} */ - public function buildEntityEntryStructure(EntityManagerInterface $em) + public function buildEntityHydrator(EntityManagerInterface $em) { - return new DefaultEntityEntryStructure($em); + return new DefaultEntityHydrator($em); } /** * @param string $regionName - * @return \Doctrine\ORM\Cache\Region\DefaultRegion + * + * @return \Doctrine\ORM\Cache\Region */ - private function createRegion($regionName) + protected function createRegion($regionName) { return new DefaultRegion($regionName, $this->cache, array( 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($regionName) )); } - -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php similarity index 93% rename from lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php rename to lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php index b7e30007db2..fb4a45d2f66 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCollectionEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultCollectionHydrator.php @@ -28,12 +28,12 @@ use Doctrine\ORM\Cache\CollectionCacheEntry; /** - * Default structure cache entry for collections + * Default hidrator cache for collections * * @since 2.5 * @author Fabio B. Silva */ -class DefaultCollectionEntryStructure implements CollectionEntryStructure +class DefaultCollectionHydrator implements CollectionHydrator { /** * @var \Doctrine\ORM\EntityManagerInterface @@ -80,7 +80,7 @@ public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, { $assoc = $metadata->associationMappings[$key->association]; $targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']); - $targetRegion = $targetPersister->getCacheRegionAcess()->getRegion(); + $targetRegion = $targetPersister->getCacheRegion(); $list = array(); foreach ($entry->identifiers as $index => $identifier) { @@ -94,7 +94,7 @@ public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $list[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->data, self::$hints); } - array_walk($list, function($entity, $index) use ($collection){ + array_walk($list, function($entity, $index) use ($collection) { $collection->hydrateSet($index, $entity); }); diff --git a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php similarity index 96% rename from lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php rename to lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php index 14549ecbf38..fede42d517f 100644 --- a/lib/Doctrine/ORM/Cache/DefaultEntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/DefaultEntityHydrator.php @@ -28,12 +28,12 @@ use Doctrine\ORM\Cache\EntityCacheEntry; /** - * Default cache entry structure for entities + * Default hidrator cache for entities * * @since 2.5 * @author Fabio B. Silva */ -class DefaultEntityEntryStructure implements EntityEntryStructure +class DefaultEntityHydrator implements EntityHydrator { /** * @var \Doctrine\ORM\EntityManager @@ -130,7 +130,7 @@ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, Ent $assocId = $data[$name]; $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); - $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + $assocRegion = $assocPersister->getCacheRegion(); $assocEntry = $assocRegion->get(new EntityCacheKey($assoc['targetEntity'], $assocId)); if ($assocEntry === null) { diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 8bba92b2c18..5f51830d1b4 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -21,8 +21,7 @@ namespace Doctrine\ORM\Cache; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\Cache\Persisters\CachedPersister; -use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Mapping\ClassMetadata; @@ -68,8 +67,8 @@ class DefaultQueryCache implements QueryCache private static $hints = array(Query::HINT_CACHE_ENABLED => true); /** - * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. - * @param \Doctrine\ORM\Cache\Region $region The query region. + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param \Doctrine\ORM\Cache\Region $region The query region. */ public function __construct(EntityManagerInterface $em, Region $region) { @@ -106,7 +105,7 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) $entityName = reset($rsm->aliasMap); //@TODO find root entity $hasRelation = ( ! empty($rsm->relationMap)); $persister = $this->uow->getEntityPersister($entityName); - $region = $persister->getCacheRegionAcess()->getRegion(); + $region = $persister->getCacheRegion(); // @TODO - move to cache hydration componente foreach ($entry->result as $index => $entry) { @@ -126,7 +125,7 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) foreach ($entry['associations'] as $name => $assoc) { $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); - $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + $assocRegion = $assocPersister->getCacheRegion(); if ($assoc['type'] & ClassMetadata::TO_ONE) { @@ -193,7 +192,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) throw CacheException::nonCacheableEntity($entityName); } - $region = $persister->getCacheRegionAcess()->getRegion(); + $region = $persister->getCacheRegion(); foreach ($result as $index => $entity) { $identifier = $this->uow->getEntityIdentifier($entity); @@ -202,7 +201,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) { // Cancel put result if entity put fail - if ( ! $persister->putEntityCache($entity, $entityKey)) { + if ( ! $persister->storeEntityCache($entity, $entityKey)) { return false; } } @@ -224,7 +223,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) } $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); - $assocRegion = $assocPersister->getCacheRegionAcess()->getRegion(); + $assocRegion = $assocPersister->getCacheRegion(); $assocMetadata = $assocPersister->getClassMetadata(); // Handle *-to-one associations @@ -235,7 +234,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { // Cancel put result if association entity put fail - if ( ! $assocPersister->putEntityCache($assocValue, $entityKey)) { + if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) { return false; } } @@ -258,7 +257,7 @@ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result) if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) { // Cancel put result if entity put fail - if ( ! $assocPersister->putEntityCache($assocItem, $entityKey)) { + if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) { return false; } } @@ -292,4 +291,4 @@ public function getRegion() { return $this->region; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/EntityCacheEntry.php b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php index d71aca65b5c..9693ebe23b9 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php @@ -47,4 +47,4 @@ public function __construct($class, array $data) $this->class = $class; $this->data = $data; } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/EntityCacheKey.php b/lib/Doctrine/ORM/Cache/EntityCacheKey.php index 969cb8c9508..86b8855ee9f 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheKey.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheKey.php @@ -26,7 +26,7 @@ * @since 2.5 * @author Fabio B. Silva */ -class EntityCacheKey implements CacheKey +class EntityCacheKey extends CacheKey { /** * @var array @@ -39,31 +39,15 @@ class EntityCacheKey implements CacheKey public $entityClass; /** - * @var string - */ - private $hash; - - /** - * @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class. - * @param array $identifier The entity identifier + * @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class. + * @param array $identifier The entity identifier */ public function __construct($entityClass, array $identifier) { + ksort($identifier); + $this->identifier = $identifier; $this->entityClass = $entityClass; - } - - /** - * {@inheritdoc} - */ - public function hash() - { - if ($this->hash === null) { - ksort($this->identifier); - - $this->hash = sprintf('%s[%s]', str_replace('\\', '.', strtolower($this->entityClass)) , implode(' ', $this->identifier)); - } - - return $this->hash; + $this->hash = sprintf('%s[%s]', str_replace('\\', '.', strtolower($entityClass)) , implode(' ', $identifier)); } } diff --git a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php b/lib/Doctrine/ORM/Cache/EntityHydrator.php similarity index 79% rename from lib/Doctrine/ORM/Cache/EntityEntryStructure.php rename to lib/Doctrine/ORM/Cache/EntityHydrator.php index a0025edb3b5..3e382cd75ca 100644 --- a/lib/Doctrine/ORM/Cache/EntityEntryStructure.php +++ b/lib/Doctrine/ORM/Cache/EntityHydrator.php @@ -25,12 +25,12 @@ use Doctrine\ORM\Cache\EntityCacheEntry; /** - * Structure cache entry for entities + * Hidrator cache entry for entities * * @since 2.5 * @author Fabio B. Silva */ -interface EntityEntryStructure +interface EntityHydrator { /** * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. @@ -42,10 +42,10 @@ interface EntityEntryStructure public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity); /** - * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. - * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. - * @param \Doctrine\ORM\Cache\EntityCacheEntry $entry The entity cache entry. - * @param object $entity The entity to load the cache into. If not specified, a new entity is created. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The entity cache key. + * @param \Doctrine\ORM\Cache\EntityCacheEntry $entry The entity cache entry. + * @param object $entity The entity to load the cache into. If not specified, a new entity is created. */ public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null); } diff --git a/lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php deleted file mode 100644 index 2e272eca688..00000000000 --- a/lib/Doctrine/ORM/Cache/EntityRegionAccessStrategy.php +++ /dev/null @@ -1,54 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache; - -/** - * Interface for entity region access. - * - * @since 2.5 - * @author Fabio B. Silva - */ -interface EntityRegionAccessStrategy extends RegionAccessStrategy -{ - /** - * Called after an item has been inserted (after the transaction completes). - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. - * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. - * - * @return boolean TRUE If the contents of the cache actual were changed. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function afterInsert(CacheKey $key, CacheEntry $entry); - - /** - * Called after an item has been updated (after the transaction completes). - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. - * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from {@link lockItem} - * - * @return boolean TRUE If the contents of the cache actual were changed. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null); -} diff --git a/lib/Doctrine/ORM/Cache/Lock.php b/lib/Doctrine/ORM/Cache/Lock.php index 7f8cb144a65..490b04d4a09 100644 --- a/lib/Doctrine/ORM/Cache/Lock.php +++ b/lib/Doctrine/ORM/Cache/Lock.php @@ -28,9 +28,6 @@ */ class Lock { - const LOCK_READ = 1; - const LOCK_WRITE = 2; - /** * @var string */ @@ -47,30 +44,20 @@ class Lock public $type; /** - * @param string $value - * @param integer $type + * @param string $value * @param integer $time */ - public function __construct($value, $type, $time = null) + public function __construct($value, $time = null) { $this->value = $value; - $this->type = $type; $this->time = $time ? : time(); } - /** - * @return \Doctrine\ORM\Cache\Lock - */ - public static function createLockWrite() - { - return new self(uniqid(time() . self::LOCK_WRITE), self::LOCK_WRITE); - } - /** * @return \Doctrine\ORM\Cache\Lock */ public static function createLockRead() { - return new self(uniqid(time() . self::LOCK_READ), self::LOCK_READ); + return new self(uniqid(time())); } } diff --git a/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php b/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php index df7e052ea8b..bdae4eea8c5 100644 --- a/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php +++ b/lib/Doctrine/ORM/Cache/Logging/CacheLogger.php @@ -35,54 +35,54 @@ interface CacheLogger /** * Log an entity put into second level cache. * - * @param string $regionName The name of the cache region. - * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. */ public function entityCachePut($regionName, EntityCacheKey $key); /** * Log an entity get from second level cache resulted in a hit. * - * @param string $regionName The name of the cache region. - * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. */ public function entityCacheHit($regionName, EntityCacheKey $key); /** * Log an entity get from second level cache resulted in a miss. * - * @param string $regionName The name of the cache region. - * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\EntityCacheKey $key The cache key of the entity. */ public function entityCacheMiss($regionName, EntityCacheKey $key); /** * Log an entity put into second level cache. * - * @param string $regionName The name of the cache region. - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. */ public function collectionCachePut($regionName, CollectionCacheKey $key); /** * Log an entity get from second level cache resulted in a hit. * - * @param string $regionName The name of the cache region. - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. */ public function collectionCacheHit($regionName, CollectionCacheKey $key); /** * Log an entity get from second level cache resulted in a miss. * - * @param string $regionName The name of the cache region. - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. + * @param string $regionName The name of the cache region. + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key The cache key of the collection. */ public function collectionCacheMiss($regionName, CollectionCacheKey $key); /** * Log a query put into the query cache. - * + * * @param string $regionName The name of the cache region. * @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query. */ @@ -103,4 +103,4 @@ public function queryCacheHit($regionName, QueryCacheKey $key); * @param \Doctrine\ORM\Cache\QueryCacheKey $key The cache key of the query. */ public function queryCacheMiss($regionName, QueryCacheKey $key); -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php index 48a1a9e878a..283bd451247 100644 --- a/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php +++ b/lib/Doctrine/ORM/Cache/Logging/StatisticsCacheLogger.php @@ -139,7 +139,7 @@ public function queryCachePut($regionName, QueryCacheKey $key) /** * Get the number of entries successfully retrieved from cache. - * + * * @param string $regionName The name of the cache region. * * @return integer @@ -224,4 +224,4 @@ public function getMissCount() { return array_sum($this->cacheMissCountMap); } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php new file mode 100644 index 00000000000..cd85779a8dd --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php @@ -0,0 +1,275 @@ +. + */ + +namespace Doctrine\ORM\Cache\Persister; + +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Persisters\CollectionPersister; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Cache\Region; +use Doctrine\Common\Util\ClassUtils; + +/** + * @author Fabio B. Silva + * @since 2.5 + */ +abstract class AbstractCollectionPersister implements CachedCollectionPersister +{ + /** + * @var \Doctrine\ORM\UnitOfWork + */ + protected $uow; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadataFactory + */ + protected $metadataFactory; + + /** + * @var \Doctrine\ORM\Persisters\CollectionPersister + */ + protected $persister; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + protected $sourceEntity; + + /** + * @var \Doctrine\ORM\Mapping\ClassMetadata + */ + protected $targetEntity; + + /** + * @var array + */ + protected $association; + + /** + * @var array + */ + protected $queuedCache = array(); + + /** + * @var \Doctrine\ORM\Cache\Region + */ + protected $region; + + /** + * @var string + */ + protected $regionName; + + /** + * @var \Doctrine\ORM\Cache\CollectionHydrator + */ + protected $hidrator; + + /** + * @var \Doctrine\ORM\Cache\Logging\CacheLogger + */ + protected $cacheLogger; + + /** + * @param \Doctrine\ORM\Persisters\CollectionPersister $persister The collection persister that will be cached. + * @param \Doctrine\ORM\Cache\Region $region The collection region. + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param array $mapping The association mapping. + */ + public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association) + { + $configuration = $em->getConfiguration(); + $cacheFactory = $configuration->getSecondLevelCacheFactory(); + + $this->region = $region; + $this->persister = $persister; + $this->association = $association; + $this->regionName = $region->getName(); + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); + $this->hidrator = $cacheFactory->buildCollectionHydrator($em); + $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); + $this->targetEntity = $em->getClassMetadata($association['targetEntity']); + } + + /** + * {@inheritdoc} + */ + public function getCacheRegion() + { + return $this->region; + } + + /** + * {@inheritdoc} + */ + public function getSourceEntityMetadata() + { + return $this->sourceEntity; + } + + /** + * {@inheritdoc} + */ + public function getTargetEntityMetadata() + { + return $this->targetEntity; + } + + /** + * @param \Doctrine\ORM\PersistentCollection $collection + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * + * @return \Doctrine\ORM\PersistentCollection|null + */ + public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key) + { + + if (($cache = $this->region->get($key)) === null) { + return null; + } + + if (($cache = $this->hidrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) { + return null; + } + + return $cache; + } + + /** + * {@inheritdoc} + */ + public function storeCollectionCache(CollectionCacheKey $key, $elements) + { + $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); + $targetRegion = $targetPersister->getCacheRegion(); + $targetHidrator = $targetPersister->getEntityHydrator(); + $entry = $this->hidrator->buildCacheEntry($this->targetEntity, $key, $elements); + + foreach ($entry->identifiers as $index => $identifier) { + $entityKey = new EntityCacheKey($this->targetEntity->rootEntityName, $identifier); + + if ($targetRegion->contains($entityKey)) { + continue; + } + + $class = $this->targetEntity; + $className = ClassUtils::getClass($elements[$index]); + + if ($className !== $this->targetEntity->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $entity = $elements[$index]; + $entityEntry = $targetHidrator->buildCacheEntry($class, $entityKey, $entity); + + $targetRegion->put($entityKey, $entityEntry); + } + + $cached = $this->region->put($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->collectionCachePut($this->regionName, $key); + } + } + + /** + * {@inheritdoc} + */ + public function contains(PersistentCollection $collection, $element) + { + return $this->persister->contains($collection, $element); + } + + /** + * {@inheritdoc} + */ + public function containsKey(PersistentCollection $collection, $key) + { + return $this->persister->containsKey($collection, $key); + } + + /** + * {@inheritdoc} + */ + public function count(PersistentCollection $collection) + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + $entry = $this->region->get($key); + + if ($entry !== null) { + return count($entry->identifiers); + } + + return $this->persister->count($collection); + } + + /** + * {@inheritdoc} + */ + public function deleteRows(PersistentCollection $collection) + { + $this->persister->deleteRows($collection); + } + + /** + * {@inheritdoc} + */ + public function insertRows(PersistentCollection $collection) + { + $this->persister->insertRows($collection); + } + + /** + * {@inheritdoc} + */ + public function get(PersistentCollection $collection, $index) + { + return $this->persister->get($collection, $index); + } + + /** + * {@inheritdoc} + */ + public function removeElement(PersistentCollection $collection, $element) + { + return $this->persister->removeElement($collection, $element); + } + + /** + * {@inheritdoc} + */ + public function removeKey(PersistentCollection $collection, $key) + { + return $this->persister->removeKey($collection, $key); + } + + /** + * {@inheritdoc} + */ + public function slice(PersistentCollection $collection, $offset, $length = null) + { + return $this->persister->slice($collection, $offset, $length); + } +} diff --git a/lib/Doctrine/ORM/Cache/Persisters/CachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php similarity index 57% rename from lib/Doctrine/ORM/Cache/Persisters/CachedEntityPersister.php rename to lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php index 150f00d1dd8..7360e88b8cb 100644 --- a/lib/Doctrine/ORM/Cache/Persisters/CachedEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php @@ -18,12 +18,12 @@ * . */ -namespace Doctrine\ORM\Cache\Persisters; +namespace Doctrine\ORM\Cache\Persister; use Doctrine\ORM\Cache; +use Doctrine\ORM\Cache\Region; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\Cache\ConcurrentRegionAccess; use Doctrine\ORM\Cache\QueryCacheKey; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\PersistentCollection; @@ -37,22 +37,22 @@ * @author Fabio B. Silva * @since 2.5 */ -class CachedEntityPersister implements CachedPersister, EntityPersister +abstract class AbstractEntityPersister implements CachedEntityPersister { /** * @var \Doctrine\ORM\UnitOfWork */ - private $uow; + protected $uow; /** * @var \Doctrine\ORM\Mapping\ClassMetadataFactory */ - private $metadataFactory; + protected $metadataFactory; /** * @var \Doctrine\ORM\Persisters\EntityPersister */ - private $persister; + protected $persister; /** * @var \Doctrine\ORM\Mapping\ClassMetadata @@ -65,19 +65,14 @@ class CachedEntityPersister implements CachedPersister, EntityPersister protected $queuedCache = array(); /** - * @var boolean + * @var \Doctrine\ORM\Cache\Region */ - private $isConcurrentRegion = false; + protected $region; /** - * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess + * @var \Doctrine\ORM\Cache\EntityHydrator */ - protected $cacheRegionAccess; - - /** - * @var \Doctrine\ORM\Cache\EntityEntryStructure - */ - protected $cacheEntryStructure; + protected $hidrator; /** * @var \Doctrine\ORM\Cache @@ -92,23 +87,28 @@ class CachedEntityPersister implements CachedPersister, EntityPersister /** * @var string */ - protected $cacheRegionName; + protected $regionName; - public function __construct(EntityPersister $persister, EntityManagerInterface $em, ClassMetadata $class) + /** + * @param \Doctrine\ORM\Persisters\EntityPersister $persister The entity persister to cache. + * @param \Doctrine\ORM\Cache\Region $region The entity cache region. + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata. + */ + public function __construct(EntityPersister $persister, Region $region, EntityManagerInterface $em, ClassMetadata $class) { $config = $em->getConfiguration(); $factory = $config->getSecondLevelCacheFactory(); - $this->class = $class; - $this->persister = $persister; - $this->cache = $em->getCache(); - $this->uow = $em->getUnitOfWork(); - $this->metadataFactory = $em->getMetadataFactory(); - $this->cacheLogger = $config->getSecondLevelCacheLogger(); - $this->cacheEntryStructure = $factory->buildEntityEntryStructure($em); - $this->cacheRegionAccess = $factory->buildEntityRegionAccessStrategy($this->class); - $this->cacheRegionName = $this->cacheRegionAccess->getRegion()->getName(); - $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); + $this->class = $class; + $this->region = $region; + $this->persister = $persister; + $this->cache = $em->getCache(); + $this->regionName = $region->getName(); + $this->uow = $em->getUnitOfWork(); + $this->metadataFactory = $em->getMetadataFactory(); + $this->cacheLogger = $config->getSecondLevelCacheLogger(); + $this->hidrator = $factory->buildEntityHydrator($em); } /** @@ -159,149 +159,6 @@ public function getSelectConditionStatementSQL($field, $value, $assoc = null, $c return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison); } - /** - * {@inheritdoc} - */ - public function afterTransactionComplete() - { - if (isset($this->queuedCache['insert'])) { - foreach ($this->queuedCache['insert'] as $item) { - - $class = $this->class; - $className = ClassUtils::getClass($item['entity']); - - if ($className !== $this->class->name) { - $class = $this->metadataFactory->getMetadataFor($className); - } - - $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($item['entity'])); - $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); - $cached = $this->cacheRegionAccess->afterInsert($key, $entry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); - } - } - } - - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - - $class = $this->class; - $className = ClassUtils::getClass($item['entity']); - - if ($className !== $this->class->name) { - $class = $this->metadataFactory->getMetadataFor($className); - } - - $key = $item['key'] ?: new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($item['entity'])); - $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $item['entity']); - $cached = $this->cacheRegionAccess->afterUpdate($key, $entry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); - } - - if ($item['lock'] !== null) { - $this->cacheRegionAccess->unlockItem($key, $item['lock']); - } - } - } - - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { - $this->cacheRegionAccess->evict($item['key']); - } - } - - $this->queuedCache = array(); - } - - /** - * {@inheritdoc} - */ - public function afterTransactionRolledBack() - { - if ( ! $this->isConcurrentRegion) { - $this->queuedCache = array(); - - return; - } - - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - $this->queuedCache = array(); - } - - /** - * {@inheritdoc} - */ - public function delete($entity) - { - $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); - $lock = null; - - if ($this->isConcurrentRegion) { - $lock = $this->cacheRegionAccess->lockItem($key); - } - - $this->persister->delete($entity); - - $this->queuedCache['delete'][] = array( - 'entity' => $entity, - 'lock' => $lock, - 'key' => $key - ); - } - - /** - * {@inheritdoc} - */ - public function update($entity) - { - $key = null; - $lock = null; - - if ($this->isConcurrentRegion) { - $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); - $lock = $this->cacheRegionAccess->lockItem($key); - } - - $this->persister->update($entity); - - $this->queuedCache['update'][] = array( - 'entity' => $entity, - 'lock' => $lock, - 'key' => $key - ); - } - - /** - * {@inheritdoc} - */ - public function executeInserts() - { - foreach ($this->persister->getInserts() as $entity) { - $this->queuedCache['insert'][] = array( - 'entity' => $entity, - 'lock' => null, - 'key' => null - ); - } - - return $this->persister->executeInserts(); - } - /** * {@inheritdoc} */ @@ -309,10 +166,9 @@ public function exists($entity, array $extraConditions = array()) { if (empty($extraConditions)) { - $region = $this->cacheRegionAccess->getRegion(); - $key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity)); + $key = new EntityCacheKey($this->class->rootEntityName, $this->class->getIdentifierValues($entity)); - if ($region->contains($key)) { + if ($this->region->contains($key)) { return true; } } @@ -323,25 +179,23 @@ public function exists($entity, array $extraConditions = array()) /** * {@inheritdoc} */ - public function getCacheRegionAcess() + public function getCacheRegion() { - return $this->cacheRegionAccess; + return $this->region; } /** - * @return \Doctrine\ORM\Cache\EntityEntryStructure + * @return \Doctrine\ORM\Cache\EntityHydrator */ - public function getCacheEntryStructure() + public function getEntityHydrator() { - return $this->cacheEntryStructure; + return $this->hidrator; } /** - * @param object $entity - * @param \Doctrine\ORM\Cache\EntityCacheKey $key - * @return boolean + * {@inheritdoc} */ - public function putEntityCache($entity, EntityCacheKey $key) + public function storeEntityCache($entity, EntityCacheKey $key) { $class = $this->class; $className = ClassUtils::getClass($entity); @@ -350,11 +204,11 @@ public function putEntityCache($entity, EntityCacheKey $key) $class = $this->metadataFactory->getMetadataFor($className); } - $entry = $this->cacheEntryStructure->buildCacheEntry($class, $key, $entity); - $cached = $this->cacheRegionAccess->put($key, $entry); + $entry = $this->hidrator->buildCacheEntry($class, $key, $entity); + $cached = $this->region->put($key, $entry); if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); + $this->cacheLogger->entityCachePut($this->regionName, $key); } return $cached; @@ -412,6 +266,16 @@ public function getOwningTable($fieldName) return $this->persister->getOwningTable($fieldName); } + /** + * {@inheritdoc} + */ + public function executeInserts() + { + $this->queuedCache['insert'] = $this->persister->getInserts(); + + return $this->persister->executeInserts(); + } + /** * {@inheritdoc} */ @@ -427,30 +291,30 @@ public function load(array $criteria, $entity = null, $assoc = null, array $hint $hash = $this->getHash($query, $criteria); $rsm = $this->getResultSetMapping(); $querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL); - $queryCache = $this->cache->getQueryCache($this->cacheRegionName); + $queryCache = $this->cache->getQueryCache($this->regionName); $result = $queryCache->get($querykey, $rsm); if ($result !== null) { if ($this->cacheLogger) { - $this->cacheLogger->queryCacheHit($this->cacheRegionName, $querykey); + $this->cacheLogger->queryCacheHit($this->regionName, $querykey); } return $result[0]; } - if(($result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy)) === null) { + if (($result = $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy)) === null) { return null; } $cached = $queryCache->put($querykey, $rsm, array($result)); if ($this->cacheLogger && $result) { - $this->cacheLogger->queryCacheMiss($this->cacheRegionName, $querykey); + $this->cacheLogger->queryCacheMiss($this->regionName, $querykey); } if ($this->cacheLogger && $cached) { - $this->cacheLogger->queryCachePut($this->cacheRegionName, $querykey); + $this->cacheLogger->queryCachePut($this->regionName, $querykey); } return $result; @@ -465,13 +329,13 @@ public function loadAll(array $criteria = array(), array $orderBy = null, $limit $hash = $this->getHash($query, $criteria); $rsm = $this->getResultSetMapping(); $querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL); - $queryCache = $this->cache->getQueryCache($this->cacheRegionName); + $queryCache = $this->cache->getQueryCache($this->regionName); $result = $queryCache->get($querykey, $rsm); if ($result !== null) { if ($this->cacheLogger) { - $this->cacheLogger->queryCacheHit($this->cacheRegionName, $querykey); + $this->cacheLogger->queryCacheHit($this->regionName, $querykey); } return $result; @@ -481,11 +345,11 @@ public function loadAll(array $criteria = array(), array $orderBy = null, $limit $cached = $queryCache->put($querykey, $rsm, $result); if ($this->cacheLogger && $result) { - $this->cacheLogger->queryCacheMiss($this->cacheRegionName, $querykey); + $this->cacheLogger->queryCacheMiss($this->regionName, $querykey); } if ($this->cacheLogger && $cached) { - $this->cacheLogger->queryCachePut($this->cacheRegionName, $querykey); + $this->cacheLogger->queryCachePut($this->regionName, $querykey); } return $result; @@ -497,7 +361,7 @@ public function loadAll(array $criteria = array(), array $orderBy = null, $limit public function loadById(array $identifier, $entity = null) { $cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier); - $cacheEntry = $this->cacheRegionAccess->get($cacheKey); + $cacheEntry = $this->region->get($cacheKey); $class = $this->class; if ($cacheEntry !== null) { @@ -506,10 +370,10 @@ public function loadById(array $identifier, $entity = null) $class = $this->metadataFactory->getMetadataFor($cacheEntry->class); } - if (($entity = $this->cacheEntryStructure->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) { + if (($entity = $this->hidrator->loadCacheEntry($class, $cacheKey, $cacheEntry, $entity)) !== null) { if ($this->cacheLogger) { - $this->cacheLogger->entityCacheHit($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + $this->cacheLogger->entityCacheHit($this->regionName, $cacheKey); } return $entity; @@ -529,15 +393,15 @@ public function loadById(array $identifier, $entity = null) $class = $this->metadataFactory->getMetadataFor($className); } - $cacheEntry = $this->cacheEntryStructure->buildCacheEntry($class, $cacheKey, $entity); - $cached = $this->cacheRegionAccess->put($cacheKey, $cacheEntry); + $cacheEntry = $this->hidrator->buildCacheEntry($class, $cacheKey, $entity); + $cached = $this->region->put($cacheKey, $cacheEntry); if ($this->cacheLogger && $cached) { - $this->cacheLogger->entityCachePut($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + $this->cacheLogger->entityCachePut($this->regionName, $cacheKey); } if ($this->cacheLogger) { - $this->cacheLogger->entityCacheMiss($this->cacheRegionAccess->getRegion()->getName(), $cacheKey); + $this->cacheLogger->entityCacheMiss($this->regionName, $cacheKey); } return $entity; @@ -568,7 +432,7 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent if ($list !== null) { if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + $this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key); } return $list; @@ -578,10 +442,10 @@ public function loadManyToManyCollection(array $assoc, $sourceEntity, Persistent $list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll); if ($hasCache && ! empty($list)) { - $persister->saveCollectionCache($key, $list); + $persister->storeCollectionCache($key, $list); if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + $this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); } } @@ -604,7 +468,7 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC if ($list !== null) { if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheHit($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + $this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key); } return $list; @@ -614,10 +478,10 @@ public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentC $list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll); if ($hasCache && ! empty($list)) { - $persister->saveCollectionCache($key, $list); + $persister->storeCollectionCache($key, $list); if ($this->cacheLogger) { - $this->cacheLogger->collectionCacheMiss($persister->getCacheRegionAcess()->getRegion()->getName(), $key); + $this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key); } } diff --git a/lib/Doctrine/ORM/Cache/Persister/CachedCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persister/CachedCollectionPersister.php new file mode 100644 index 00000000000..2318c590693 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Persister/CachedCollectionPersister.php @@ -0,0 +1,64 @@ +. + */ + +namespace Doctrine\ORM\Cache\Persister; + +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Persisters\CollectionPersister; +use Doctrine\ORM\PersistentCollection; + +/** + * Interface for second level cache collection persisters. + * + * @author Fabio B. Silva + * @since 2.5 + */ +interface CachedCollectionPersister extends CachedPersister, CollectionPersister +{ + /** + * @return \Doctrine\ORM\Mapping\ClassMetadata + */ + public function getSourceEntityMetadata(); + + /** + * @return \Doctrine\ORM\Mapping\ClassMetadata + */ + public function getTargetEntityMetadata(); + + /** + * Loads a collection from cache + * + * @param \Doctrine\ORM\PersistentCollection $collection + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * + * @return \Doctrine\ORM\PersistentCollection|null + */ + public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key); + + /** + * Stores a collection into cache + * + * @param \Doctrine\ORM\Cache\CollectionCacheKey $key + * @param array|\Doctrine\Common\Collections\Collection $elements + * + * @return void + */ + public function storeCollectionCache(CollectionCacheKey $key, $elements); +} diff --git a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteCollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Persister/CachedEntityPersister.php similarity index 60% rename from lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteCollectionRegionAccessStrategy.php rename to lib/Doctrine/ORM/Cache/Persister/CachedEntityPersister.php index b4257c2244c..adf5fa0c6ee 100644 --- a/lib/Doctrine/ORM/Cache/Access/NonStrictReadWriteCollectionRegionAccessStrategy.php +++ b/lib/Doctrine/ORM/Cache/Persister/CachedEntityPersister.php @@ -18,17 +18,28 @@ * . */ -namespace Doctrine\ORM\Cache\Access; +namespace Doctrine\ORM\Cache\Persister; -use Doctrine\ORM\Cache\CollectionRegionAccessStrategy; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Persisters\EntityPersister; /** - * Specific non-strict read/write region access strategy + * Interface for second level cache entity persisters. * - * @since 2.5 - * @author Fabio B. Silva + * @author Fabio B. Silva + * @since 2.5 */ -class NonStrictReadWriteCollectionRegionAccessStrategy extends AbstractRegionAccessStrategy implements CollectionRegionAccessStrategy +interface CachedEntityPersister extends CachedPersister, EntityPersister { + /** + * @return \Doctrine\ORM\Cache\EntityHydrator + */ + public function getEntityHydrator(); + /** + * @param object $entity + * @param \Doctrine\ORM\Cache\EntityCacheKey $key + * @return boolean + */ + public function storeEntityCache($entity, EntityCacheKey $key); } diff --git a/lib/Doctrine/ORM/Cache/Persisters/CachedPersister.php b/lib/Doctrine/ORM/Cache/Persister/CachedPersister.php similarity index 92% rename from lib/Doctrine/ORM/Cache/Persisters/CachedPersister.php rename to lib/Doctrine/ORM/Cache/Persister/CachedPersister.php index ade7f8be791..89afd32095f 100644 --- a/lib/Doctrine/ORM/Cache/Persisters/CachedPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/CachedPersister.php @@ -17,7 +17,7 @@ * . */ -namespace Doctrine\ORM\Cache\Persisters; +namespace Doctrine\ORM\Cache\Persister; /** * Interface for persister that support second level cache. @@ -40,8 +40,7 @@ public function afterTransactionRolledBack(); /** * Gets the The region access. * - *@return \Doctrine\ORM\Cache\RegionAccess + * @return \Doctrine\ORM\Cache\Region */ - public function getCacheRegionAcess(); - + public function getCacheRegion(); } diff --git a/lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersister.php new file mode 100644 index 00000000000..619a520b3c0 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersister.php @@ -0,0 +1,105 @@ +. + */ + +namespace Doctrine\ORM\Cache\Persister; + +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\PersistentCollection; + +/** + * @author Fabio B. Silva + * @since 2.5 + */ +class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister +{ + /** + * {@inheritdoc} + */ + public function afterTransactionComplete() + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->storeCollectionCache($item['key'], $item['list']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $key) { + $this->region->evict($key); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionRolledBack() + { + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function delete(PersistentCollection $collection) + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + + $this->persister->delete($collection); + + $this->queuedCache['delete'][spl_object_hash($collection)] = $key; + } + + /** + * {@inheritdoc} + */ + public function update(PersistentCollection $collection) + { + $isInitialized = $collection->isInitialized(); + $isDirty = $collection->isDirty(); + + if ( ! $isInitialized && ! $isDirty) { + return; + } + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + + // Invalidate non initialized collections OR odered collection + if ($isDirty && ! $isInitialized || isset($this->association['orderBy'])) { + + $this->persister->update($collection); + + $this->queuedCache['delete'][spl_object_hash($collection)] = $key; + + return; + } + + $this->persister->update($collection); + + $this->queuedCache['update'][spl_object_hash($collection)] = array( + 'key' => $key, + 'list' => $collection + ); + } +} diff --git a/lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersister.php new file mode 100644 index 00000000000..281f7056206 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersister.php @@ -0,0 +1,116 @@ +. + */ + +namespace Doctrine\ORM\Cache\Persister; + +use Doctrine\ORM\Cache\EntityCacheKey; + +use Doctrine\Common\Util\ClassUtils; + +/** + * Specific non-strict read/write cached entity persister + * + * @author Fabio B. Silva + * @since 2.5 + */ +class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister +{ + /** + * {@inheritdoc} + */ + public function afterTransactionComplete() + { + if (isset($this->queuedCache['insert'])) { + foreach ($this->queuedCache['insert'] as $entity) { + + $class = $this->class; + $className = ClassUtils::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $entry = $this->hidrator->buildCacheEntry($class, $key, $entity); + $cached = $this->region->put($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->regionName, $key); + } + } + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $entity) { + + $class = $this->class; + $className = ClassUtils::getClass($entity); + + if ($className !== $this->class->name) { + $class = $this->metadataFactory->getMetadataFor($className); + } + + $key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $entry = $this->hidrator->buildCacheEntry($class, $key, $entity); + $cached = $this->region->put($key, $entry); + + if ($this->cacheLogger && $cached) { + $this->cacheLogger->entityCachePut($this->regionName, $key); + } + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $key) { + $this->region->evict($key); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionRolledBack() + { + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function delete($entity) + { + $this->persister->delete($entity); + + $this->queuedCache['delete'][] = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + } + + /** + * {@inheritdoc} + */ + public function update($entity) + { + $this->persister->update($entity); + + $this->queuedCache['update'][] = $entity; + } +} diff --git a/lib/Doctrine/ORM/Cache/Access/ReadOnlyCollectionRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedCollectionPersister.php similarity index 59% rename from lib/Doctrine/ORM/Cache/Access/ReadOnlyCollectionRegionAccessStrategy.php rename to lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedCollectionPersister.php index c384ee0ad6b..20890e87d8c 100644 --- a/lib/Doctrine/ORM/Cache/Access/ReadOnlyCollectionRegionAccessStrategy.php +++ b/lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedCollectionPersister.php @@ -18,17 +18,27 @@ * . */ -namespace Doctrine\ORM\Cache\Access; +namespace Doctrine\ORM\Cache\Persister; -use Doctrine\ORM\Cache\CollectionRegionAccessStrategy; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Cache\CacheException; +use Doctrine\Common\Util\ClassUtils; /** - * Specific read-only region access strategy - * - * @since 2.5 - * @author Fabio B. Silva + * @author Fabio B. Silva + * @since 2.5 */ -class ReadOnlyCollectionRegionAccessStrategy extends AbstractRegionAccessStrategy implements CollectionRegionAccessStrategy +class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister { + /** + * {@inheritdoc} + */ + public function update(PersistentCollection $collection) + { + if ($collection->isDirty() && count($collection->getSnapshot()) > 0) { + throw CacheException::updateReadOnlyCollection(ClassUtils::getClass($collection->getOwner()), $this->association['fieldName']); + } + parent::update($collection); + } } diff --git a/lib/Doctrine/ORM/Cache/Access/ReadOnlyEntityRegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedEntityPersister.php similarity index 69% rename from lib/Doctrine/ORM/Cache/Access/ReadOnlyEntityRegionAccessStrategy.php rename to lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedEntityPersister.php index 4f4372393b5..69db37f60e1 100644 --- a/lib/Doctrine/ORM/Cache/Access/ReadOnlyEntityRegionAccessStrategy.php +++ b/lib/Doctrine/ORM/Cache/Persister/ReadOnlyCachedEntityPersister.php @@ -18,26 +18,24 @@ * . */ -namespace Doctrine\ORM\Cache\Access; +namespace Doctrine\ORM\Cache\Persister; use Doctrine\ORM\Cache\CacheException; -use Doctrine\ORM\Cache\CacheEntry; -use Doctrine\ORM\Cache\CacheKey; -use Doctrine\ORM\Cache\Lock; +use Doctrine\Common\Util\ClassUtils; /** - * Specific read-only region access strategy + * Specific read-only region entity persister * - * @since 2.5 - * @author Fabio B. Silva + * @author Fabio B. Silva + * @since 2.5 */ -class ReadOnlyEntityRegionAccessStrategy extends NonStrictReadWriteEntityRegionAccessStrategy +class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister { /** * {@inheritdoc} */ - public function afterUpdate(CacheKey $key, CacheEntry $entry, Lock $lock = null) + public function update($entity) { - throw CacheException::updateReadOnlyobject(); + throw CacheException::updateReadOnlyEntity(ClassUtils::getClass($entity)); } } diff --git a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php new file mode 100644 index 00000000000..b058355f2bd --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php @@ -0,0 +1,134 @@ +. + */ + +namespace Doctrine\ORM\Cache\Persister; + +use Doctrine\ORM\Persisters\CollectionPersister; +use Doctrine\ORM\EntityManagerInterface; + +use Doctrine\ORM\Cache\CollectionCacheKey; +use Doctrine\ORM\Cache\ConcurrentRegion; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Cache\Lock; + +/** + * @author Fabio B. Silva + * @since 2.5 + */ +class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister +{ + /** + * @var \Doctrine\ORM\Cache\ConcurrentRegion + */ + protected $region; + + /** + * @param \Doctrine\ORM\Persisters\CollectionPersister $persister The collection persister that will be cached. + * @param \Doctrine\ORM\Cache\ConcurrentRegion $region The collection region. + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param array $mapping The association mapping. + */ + public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association) + { + parent::__construct($persister, $region, $em, $association); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionComplete() + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionRolledBack() + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function delete(PersistentCollection $collection) + { + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + $lock = $this->region->readLock($key); + + $this->persister->delete($collection); + + $this->queuedCache['delete'][spl_object_hash($collection)] = array( + 'key' => $key, + 'lock' => $lock + ); + } + + /** + * {@inheritdoc} + */ + public function update(PersistentCollection $collection) + { + $isInitialized = $collection->isInitialized(); + $isDirty = $collection->isDirty(); + + if ( ! $isInitialized && ! $isDirty) { + return; + } + + $this->persister->update($collection); + + $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); + $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); + $lock = $this->region->readLock($key); + $data = array( + 'key' => $key, + 'lock' => $lock + ); + + $this->queuedCache['delete'][spl_object_hash($collection)] = $data; + } +} diff --git a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php new file mode 100644 index 00000000000..f20c14a0c8f --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php @@ -0,0 +1,131 @@ +. + */ + +namespace Doctrine\ORM\Cache\Persister; + +use Doctrine\ORM\Persisters\EntityPersister; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\EntityManagerInterface; + +use Doctrine\ORM\Cache\ConcurrentRegion; +use Doctrine\ORM\Cache\EntityCacheKey; + +/** + * Specific read-write entity persister + * + * @author Fabio B. Silva + * @since 2.5 + */ +class ReadWriteCachedEntityPersister extends AbstractEntityPersister +{ + /** + * @var \Doctrine\ORM\Cache\ConcurrentRegion + */ + protected $region; + + /** + * @param \Doctrine\ORM\Persister\EntityPersister $persister The entity persister to cache. + * @param \Doctrine\ORM\Cache\ConcurrentRegion $region The entity cache region. + * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. + * @param \Doctrine\ORM\Mapping\ClassMetadata $class The entity metadata. + */ + public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class) + { + parent::__construct($persister, $region, $em, $class); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionComplete() + { + if (isset($this->queuedCache['insert'])) { + foreach ($this->queuedCache['insert'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function afterTransactionRolledBack() + { + if (isset($this->queuedCache['update'])) { + foreach ($this->queuedCache['update'] as $item) { + $this->region->evict($item['key']); + } + } + + if (isset($this->queuedCache['delete'])) { + foreach ($this->queuedCache['delete'] as $item) { + $this->region->evict($item['key']); + } + } + + $this->queuedCache = array(); + } + + /** + * {@inheritdoc} + */ + public function delete($entity) + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = $this->region->readLock($key); + + $this->persister->delete($entity); + + $this->queuedCache['delete'][] = array( + 'lock' => $lock, + 'key' => $key + ); + } + + /** + * {@inheritdoc} + */ + public function update($entity) + { + $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); + $lock = $this->region->readLock($key); + + $this->persister->update($entity); + + $this->queuedCache['update'][] = array( + 'lock' => $lock, + 'key' => $key + ); + } +} diff --git a/lib/Doctrine/ORM/Cache/Persisters/CachedCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persisters/CachedCollectionPersister.php deleted file mode 100644 index 3d5afa6aa92..00000000000 --- a/lib/Doctrine/ORM/Cache/Persisters/CachedCollectionPersister.php +++ /dev/null @@ -1,390 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache\Persisters; - -use Doctrine\ORM\Cache\EntityCacheKey; -use Doctrine\ORM\Cache\CollectionCacheKey; -use Doctrine\ORM\Cache\ConcurrentRegionAccess; -use Doctrine\ORM\Persisters\CollectionPersister; -use Doctrine\ORM\PersistentCollection; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\Common\Util\ClassUtils; - -/** - * @author Fabio B. Silva - * @since 2.5 - */ -class CachedCollectionPersister implements CachedPersister, CollectionPersister -{ - /** - * @var \Doctrine\ORM\UnitOfWork - */ - private $uow; - - /** - * @var \Doctrine\ORM\Mapping\ClassMetadataFactory - */ - private $metadataFactory; - - /** - * @var \Doctrine\ORM\Persisters\CollectionPersister - */ - private $persister; - - /** - * @var \Doctrine\ORM\Mapping\ClassMetadata - */ - private $sourceEntity; - - /** - * @var \Doctrine\ORM\Mapping\ClassMetadata - */ - private $targetEntity; - - /** - * @var array - */ - private $association; - - /** - * @var array - */ - private $queuedCache = array(); - - /** - * @var boolean - */ - private $isConcurrentRegion = false; - - /** - * @var \Doctrine\ORM\Cache\RegionAccess|Doctrine\ORM\Cache\ConcurrentRegionAccess - */ - private $cacheRegionAccess; - - /** - * @var \Doctrine\ORM\Cache\CollectionEntryStructure - */ - private $cacheEntryStructure; - - /** - * @var \Doctrine\ORM\Cache\Logging\CacheLogger - */ - private $cacheLogger; - - public function __construct(CollectionPersister $persister, EntityManagerInterface $em, array $association) - { - $configuration = $em->getConfiguration(); - $cacheFactory = $configuration->getSecondLevelCacheFactory(); - - $this->persister = $persister; - $this->association = $association; - $this->uow = $em->getUnitOfWork(); - $this->metadataFactory = $em->getMetadataFactory(); - $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); - $this->cacheEntryStructure = $cacheFactory->buildCollectionEntryStructure($em); - $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); - $this->targetEntity = $em->getClassMetadata($association['targetEntity']); - $this->isConcurrentRegion = ($this->cacheRegionAccess instanceof ConcurrentRegionAccess); - $this->cacheRegionAccess = $cacheFactory->buildCollectionRegionAccessStrategy($this->sourceEntity, $association['fieldName']); - } - - /** - * {@inheritdoc} - */ - public function getCacheRegionAcess() - { - return $this->cacheRegionAccess; - } - - /** - * {@inheritdoc} - */ - public function getSourceEntityMetadata() - { - return $this->sourceEntity; - } - - /** - * {@inheritdoc} - */ - public function getTargetEntityMetadata() - { - return $this->targetEntity; - } - - /** - * {@inheritdoc} - */ - public function afterTransactionComplete() - { - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - - $this->saveCollectionCache($item['key'], $item['list'], $item['lock']); - - if ($item['lock'] !== null) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - } - - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { - $this->cacheRegionAccess->evict($item['key']); - } - } - - $this->queuedCache = array(); - } - - /** - * {@inheritdoc} - */ - public function afterTransactionRolledBack() - { - if ( ! $this->isConcurrentRegion) { - $this->queuedCache = array(); - - return; - } - - if (isset($this->queuedCache['update'])) { - foreach ($this->queuedCache['update'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - if (isset($this->queuedCache['delete'])) { - foreach ($this->queuedCache['delete'] as $item) { - $this->cacheRegionAccess->unlockItem($item['key'], $item['lock']); - } - } - - $this->queuedCache = array(); - } - - /** - * @param \Doctrine\ORM\PersistentCollection $collection - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key - * - * @return \Doctrine\ORM\PersistentCollection|null - */ - public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key) - { - - if (($cache = $this->cacheRegionAccess->get($key)) === null) { - return null; - } - - if (($cache = $this->cacheEntryStructure->loadCacheEntry($this->sourceEntity, $key, $cache, $collection)) === null) { - return null; - } - - return $cache; - } - - /** - * @param \Doctrine\ORM\Cache\CollectionCacheKey $key - * @param array|\Doctrine\Common\Collections\Collection $elements - * - * @return void - */ - public function saveCollectionCache(CollectionCacheKey $key, $elements) - { - $targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName); - $targetRegionAcess = $targetPersister->getCacheRegionAcess(); - $targetStructure = $targetPersister->getCacheEntryStructure(); - $targetRegion = $targetRegionAcess->getRegion(); - $entry = $this->cacheEntryStructure->buildCacheEntry($this->targetEntity, $key, $elements); - - foreach ($entry->identifiers as $index => $identifier) { - $entityKey = new EntityCacheKey($this->targetEntity->rootEntityName, $identifier); - - - if ($targetRegion->contains($entityKey)) { - continue; - } - - $class = $this->targetEntity; - $className = ClassUtils::getClass($elements[$index]); - - if ($className !== $this->targetEntity->name) { - $class = $this->metadataFactory->getMetadataFor($className); - } - - $entity = $elements[$index]; - $entityEntry = $targetStructure->buildCacheEntry($class, $entityKey, $entity); - - $targetRegionAcess->put($entityKey, $entityEntry); - } - - $cached = $this->cacheRegionAccess->put($key, $entry); - - if ($this->cacheLogger && $cached) { - $this->cacheLogger->collectionCachePut($this->cacheRegionAccess->getRegion()->getName(), $key); - } - } - - /** - * {@inheritdoc} - */ - public function contains(PersistentCollection $collection, $element) - { - return $this->persister->contains($collection, $element); - } - - /** - * {@inheritdoc} - */ - public function containsKey(PersistentCollection $collection, $key) - { - return $this->persister->containsKey($collection, $key); - } - - /** - * {@inheritdoc} - */ - public function count(PersistentCollection $collection) - { - $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); - $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - $entry = $this->cacheRegionAccess->get($key); - - if ($entry !== null) { - return count($entry->identifiers); - } - - return $this->persister->count($collection); - } - - /** - * {@inheritdoc} - */ - public function delete(PersistentCollection $collection) - { - $lock = null; - $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); - $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - - if ($this->isConcurrentRegion) { - $lock = $this->cacheRegionAccess->lockItem($key); - } - - $this->persister->delete($collection); - - $this->queuedCache['delete'][spl_object_hash($collection)] = array( - 'list' => null, - 'key' => $key, - 'lock' => $lock - ); - } - - /** - * {@inheritdoc} - */ - public function update(PersistentCollection $collection) - { - $isInitialized = $collection->isInitialized(); - $isDirty = $collection->isDirty(); - - if ( ! $isInitialized && ! $isDirty) { - return; - } - - $lock = null; - $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); - $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - - if ($this->isConcurrentRegion) { - $lock = $this->cacheRegionAccess->lockItem($key); - } - - $data = array( - 'list' => null, - 'key' => $key, - 'lock' => $lock - ); - - // Invalidate non initialized collections OR odered collection - if ($isDirty && ! $isInitialized || isset($this->association['orderBy'])) { - - $this->persister->update($collection); - - $this->queuedCache['delete'][spl_object_hash($collection)] = $data; - - return; - } - - $this->persister->update($collection); - - $data['list'] = $collection; - - $this->queuedCache['update'][spl_object_hash($collection)] = $data; - } - - /** - * {@inheritdoc} - */ - public function deleteRows(PersistentCollection $collection) - { - $this->persister->deleteRows($collection); - } - - /** - * {@inheritdoc} - */ - public function insertRows(PersistentCollection $collection) - { - $this->persister->insertRows($collection); - } - - /** - * {@inheritdoc} - */ - public function get(PersistentCollection $collection, $index) - { - return $this->persister->get($collection, $index); - } - - /** - * {@inheritdoc} - */ - public function removeElement(PersistentCollection $collection, $element) - { - return $this->persister->removeElement($collection, $element); - } - - /** - * {@inheritdoc} - */ - public function removeKey(PersistentCollection $collection, $key) - { - return $this->persister->removeKey($collection, $key); - } - - /** - * {@inheritdoc} - */ - public function slice(PersistentCollection $collection, $offset, $length = null) - { - return $this->persister->slice($collection, $offset, $length); - } -} diff --git a/lib/Doctrine/ORM/Cache/QueryCache.php b/lib/Doctrine/ORM/Cache/QueryCache.php index bb00e2f8026..7bdc0d33789 100644 --- a/lib/Doctrine/ORM/Cache/QueryCache.php +++ b/lib/Doctrine/ORM/Cache/QueryCache.php @@ -37,17 +37,17 @@ interface QueryCache public function clear(); /** - * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\ResultSetMapping $rsm - * @param array $result + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\ResultSetMapping $rsm + * @param array $result * * @return boolean */ public function put(QueryCacheKey $key, ResultSetMapping $rsm, array $result); /** - * @param \Doctrine\ORM\Cache\QueryCacheKey $key - * @param \Doctrine\ORM\Query\ResultSetMapping $rsm + * @param \Doctrine\ORM\Cache\QueryCacheKey $key + * @param \Doctrine\ORM\Query\ResultSetMapping $rsm * * @return void */ diff --git a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php index e638eb0e6bb..672af24e549 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php @@ -46,4 +46,4 @@ public function __construct($result) $this->result = $result; $this->time = time(); } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/QueryCacheKey.php b/lib/Doctrine/ORM/Cache/QueryCacheKey.php index f20092ffe6a..2355416827e 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheKey.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheKey.php @@ -27,13 +27,8 @@ * @since 2.5 * @author Fabio B. Silva */ -class QueryCacheKey implements CacheKey +class QueryCacheKey extends CacheKey { - /** - * @var string - */ - private $hash; - /** * @var integer */ @@ -55,12 +50,4 @@ public function __construct($hash, $lifetime, $cacheMode = 3) $this->lifetime = $lifetime; $this->cacheMode = $cacheMode; } - - /** - * {@inheritdoc} - */ - public function hash() - { - return $this->hash; - } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/QueryCacheValidator.php b/lib/Doctrine/ORM/Cache/QueryCacheValidator.php index dcd50517689..b4dfa79aa5a 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheValidator.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheValidator.php @@ -39,4 +39,4 @@ interface QueryCacheValidator * @return boolean */ public function isValid(QueryCacheKey $key, QueryCacheEntry $entry); -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/Region.php b/lib/Doctrine/ORM/Cache/Region.php index 90c84e41576..16609400fc3 100644 --- a/lib/Doctrine/ORM/Cache/Region.php +++ b/lib/Doctrine/ORM/Cache/Region.php @@ -83,4 +83,4 @@ public function evict(CacheKey $key); * @throws \Doctrine\ORM\Cache\CacheException Indicates problem accessing the region. */ public function evictAll(); -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index cfc1606b36b..785dd2d8d9d 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -85,7 +85,7 @@ public function getCache() */ private function entryKey(CacheKey $key) { - return sprintf("%s.values[%s]", $this->name, $key->hash()); + return sprintf("%s.values[%s]", $this->name, $key->hash); } /** @@ -175,4 +175,4 @@ public function evictAll() return empty($entries); } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Cache/RegionAccessStrategy.php b/lib/Doctrine/ORM/Cache/RegionAccessStrategy.php deleted file mode 100644 index bc37ad1cb3b..00000000000 --- a/lib/Doctrine/ORM/Cache/RegionAccessStrategy.php +++ /dev/null @@ -1,75 +0,0 @@ -. - */ - -namespace Doctrine\ORM\Cache; - -/** - * Interface for region access strategies. - * - * @since 2.5 - * @author Fabio B. Silva - */ -interface RegionAccessStrategy -{ - /** - * Get the wrapped data cache region - * - * @return \Doctrine\ORM\Cache\Region The underlying region - */ - public function getRegion(); - - /** - * Attempt to retrieve an object from the cache. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to be retrieved. - * - * @return \Doctrine\ORM\Cache\CacheEntry The cached entry or null - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function get(CacheKey $key); - - /** - * Attempt to cache an object, after loading from the database. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key. - * @param \Doctrine\ORM\Cache\CacheEntry $entry The cache entry. - * - * @return TRUE if the object was successfully cached. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function put(CacheKey $key, CacheEntry $entry); - - /** - * Forcibly evict an item from the cache immediately without regard for locks. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The cache key of the item to remove. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function evict(CacheKey $key); - - /** - * Forcibly evict all items from the cache immediately without regard for locks. - * - * @throws \Doctrine\ORM\Cache\CacheException - */ - public function evictAll(); -} diff --git a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php index d2fdac92755..c213beefef8 100644 --- a/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php +++ b/lib/Doctrine/ORM/Cache/TimestampQueryCacheValidator.php @@ -40,4 +40,4 @@ public function isValid(QueryCacheKey $key, QueryCacheEntry $entry) return ($entry->time + $key->lifetime) > time(); } -} \ No newline at end of file +} diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 8ff80495946..1e1f45d5689 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -39,6 +39,7 @@ use Doctrine\ORM\Cache\Logging\CacheLogger; use Doctrine\ORM\Cache\QueryCacheValidator; use Doctrine\ORM\Cache\TimestampQueryCacheValidator; +use Doctrine\ORM\Cache\Persisters\CachedPersisterFactory; /** * Configuration container for all configuration options of Doctrine. @@ -354,6 +355,34 @@ public function setSecondLevelQueryValidator(QueryCacheValidator $validator) $this->_attributes['secondLevelCacheQueryValidator'] = $validator; } + /** + * Set the persister factory. + * + * @since 2.5 + * + * @param \Doctrine\ORM\Persister\PersisterFactory $persisterFactory + */ + public function setSecondLevelCachePersisterFactory($persisterFactory) + { + $this->_attributes['persisterFactory'] = $persisterFactory; + } + + /** + * Get the persister factory. + * + * @since 2.5 + * + * @return \Doctrine\ORM\Persister\PersisterFactory + */ + public function getSecondLevelCachePersisterFactory() + { + if ( ! isset($this->_attributes['cachedPersisterFactory'])){ + $this->_attributes['cachedPersisterFactory'] = new CachedPersisterFactory(); + } + + return $this->_attributes['cachedPersisterFactory']; + } + /** * Gets the cache driver implementation that is used for the query cache (SQL cache). * diff --git a/lib/Doctrine/ORM/Query/SqlWalker.php b/lib/Doctrine/ORM/Query/SqlWalker.php index fb61779198f..0a2db86cfe3 100644 --- a/lib/Doctrine/ORM/Query/SqlWalker.php +++ b/lib/Doctrine/ORM/Query/SqlWalker.php @@ -401,12 +401,13 @@ private function _generateOrderedCollectionOrderByItems() foreach ($this->selectedClasses as $selectedClass) { $dqlAlias = $selectedClass['dqlAlias']; $qComp = $this->queryComponents[$dqlAlias]; - $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name); if ( ! isset($qComp['relation']['orderBy'])) { continue; } + $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name); + foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) { $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform); $tableName = ($qComp['metadata']->isInheritanceTypeJoined()) diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index de3867b4bb7..3cbe8f05358 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -38,13 +38,10 @@ use Doctrine\ORM\Event\PostFlushEventArgs; use Doctrine\ORM\Event\ListenersInvoker; -use Doctrine\ORM\Cache\Persisters\CachedCollectionPersister; -use Doctrine\ORM\Cache\Persisters\CachedEntityPersister; -use Doctrine\ORM\Cache\Persisters\CachedPersister; +use Doctrine\ORM\Cache\Persister\CachedPersister; use Doctrine\ORM\Persisters\BasicEntityPersister; use Doctrine\ORM\Persisters\SingleTablePersister; use Doctrine\ORM\Persisters\JoinedSubclassPersister; -use Doctrine\ORM\Persisters\UnionSubclassPersister; use Doctrine\ORM\Persisters\OneToManyPersister; use Doctrine\ORM\Persisters\ManyToManyPersister; @@ -3012,7 +3009,9 @@ public function getEntityPersister($entityName) } if ($this->hasCache && $class->cache !== null) { - $persister = new CachedEntityPersister($persister, $this->em, $class); + $persister = $this->em->getConfiguration() + ->getSecondLevelCacheFactory() + ->buildCachedEntityPersister($this->em, $persister, $class); } $this->persisters[$entityName] = $persister; @@ -3042,7 +3041,9 @@ public function getCollectionPersister(array $association) : new ManyToManyPersister($this->em); if ($this->hasCache && isset($association['cache'])) { - $persister = new CachedCollectionPersister($persister, $this->em, $association); + $persister = $this->em->getConfiguration() + ->getSecondLevelCacheFactory() + ->buildCachedCollectionPersister($this->em, $persister, $association); } $this->collectionPersisters[$role] = $persister; diff --git a/tests/Doctrine/Tests/Mocks/CacheKeyMock.php b/tests/Doctrine/Tests/Mocks/CacheKeyMock.php index 2a98e6b8ccc..078cd15ebfb 100644 --- a/tests/Doctrine/Tests/Mocks/CacheKeyMock.php +++ b/tests/Doctrine/Tests/Mocks/CacheKeyMock.php @@ -4,16 +4,11 @@ use Doctrine\ORM\Cache\CacheKey; -class CacheKeyMock implements CacheKey +class CacheKeyMock extends CacheKey { function __construct($hash) { $this->hash = $hash; } - - public function hash() - { - return $this->hash; - } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php index 451c5947d38..2cd7da55af9 100644 --- a/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php +++ b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php @@ -44,14 +44,14 @@ public function addException($method, \Exception $e) public function setLock(CacheKey $key, Lock $lock) { - $this->locks[$key->hash()] = $lock; + $this->locks[$key->hash] = $lock; } public function contains(CacheKey $key) { $this->calls[__FUNCTION__][] = array('key' => $key); - if (isset($this->locks[$key->hash()])) { + if (isset($this->locks[$key->hash])) { return false; } @@ -84,7 +84,7 @@ public function get(CacheKey $key) $this->throwException(__FUNCTION__); - if (isset($this->locks[$key->hash()])) { + if (isset($this->locks[$key->hash])) { return null; } @@ -106,9 +106,9 @@ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) $this->throwException(__FUNCTION__); - if (isset($this->locks[$key->hash()])) { + if (isset($this->locks[$key->hash])) { - if ($lock !== null && $this->locks[$key->hash()]->value === $lock->value) { + if ($lock !== null && $this->locks[$key->hash]->value === $lock->value) { return $this->region->put($key, $entry); } @@ -124,11 +124,11 @@ public function readLock(CacheKey $key) $this->throwException(__FUNCTION__); - if (isset($this->locks[$key->hash()])) { + if (isset($this->locks[$key->hash])) { return null; } - return $this->locks[$key->hash()] = Lock::createLockRead(); + return $this->locks[$key->hash] = Lock::createLockRead(); } public function readUnlock(CacheKey $key, Lock $lock) @@ -137,48 +137,14 @@ public function readUnlock(CacheKey $key, Lock $lock) $this->throwException(__FUNCTION__); - if ( ! isset($this->locks[$key->hash()])) { + if ( ! isset($this->locks[$key->hash])) { return; } - if ($this->locks[$key->hash()]->value !== $lock->value) { + if ($this->locks[$key->hash]->value !== $lock->value) { throw LockException::unexpectedLockValue($lock); } - unset($this->locks[$key->hash()]); - } - - public function writeLock(CacheKey $key, Lock $lock = null) - { - $this->calls[__FUNCTION__][] = array('key' => $key, 'lock' => $lock); - - $this->throwException(__FUNCTION__); - - if (isset($this->locks[$key->hash()])) { - if ($lock !== null && $this->locks[$key->hash()]->value === $lock->value) { - return $this->locks[$key->hash()] = Lock::createLockWrite(); - } - - return null; - } - - return $this->locks[$key->hash()] = Lock::createLockWrite(); - } - - public function writeUnlock(CacheKey $key, Lock $lock) - { - $this->calls[__FUNCTION__][] = array('key' => $key, 'lock' => $lock); - - $this->throwException(__FUNCTION__); - - if ( ! isset($this->locks[$key->hash()])) { - return; - } - - if ($this->locks[$key->hash()]->value !== $lock->value) { - throw LockException::unexpectedLockValue($lock); - } - - unset($this->locks[$key->hash()]); + unset($this->locks[$key->hash]); } } diff --git a/tests/Doctrine/Tests/Models/Cache/Travel.php b/tests/Doctrine/Tests/Models/Cache/Travel.php index 3fa6444d42b..75e3275a489 100644 --- a/tests/Doctrine/Tests/Models/Cache/Travel.php +++ b/tests/Doctrine/Tests/Models/Cache/Travel.php @@ -34,7 +34,7 @@ class Travel /** * @Cache - * + * * @ManyToMany(targetEntity="City", inversedBy="travels", cascade={"persist", "remove"}) * @JoinTable(name="cache_visited_cities", * joinColumns={ diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php deleted file mode 100644 index 86c7748bdd2..00000000000 --- a/tests/Doctrine/Tests/ORM/Cache/AbstractEntityRegionAccessTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->afterInsert($key1, new CacheEntryMock(array('value' => 'foo'))); - $this->regionAccess->afterInsert($key2, new CacheEntryMock(array('value' => 'bar'))); - - $this->assertNotNull($this->regionAccess->get($key1)); - $this->assertNotNull($this->regionAccess->get($key2)); - - $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); - } - - public function testAfterUpdate() - { - $key1 = new CacheKeyMock('key.1'); - $key2 = new CacheKeyMock('key.2'); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->afterUpdate($key1, new CacheEntryMock(array('value' => 'foo'))); - $this->regionAccess->afterUpdate($key2, new CacheEntryMock(array('value' => 'bar'))); - - $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); - } -} diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php deleted file mode 100644 index 3e8888ffef0..00000000000 --- a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionAccessTest.php +++ /dev/null @@ -1,118 +0,0 @@ -cache = $this->createCache(); - $this->region = $this->createRegion($this->cache); - $this->regionAccess = $this->createRegionAccess($this->region); - } - - /** - * @return \Doctrine\ORM\Cache\RegionAccess - */ - abstract protected function createRegionAccess(Region $region); - - /** - * @param \Doctrine\Common\Cache\Cache $cache - * - * @return \Doctrine\ORM\Cache\Region - */ - protected function createRegion(Cache $cache) - { - $name = strtolower(str_replace('\\', '.', get_called_class())); - - return new DefaultRegion($name, $cache); - } - - /** - * @return \Doctrine\Common\Cache\Cache - */ - protected function createCache() - { - return new ArrayCache(); - } - - public function testGetRegion() - { - $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->regionAccess->getRegion()); - } - - static public function dataProviderCacheValues() - { - return array( - array(new CacheKeyMock('key.1'), new CacheEntryMock(array('id'=>1, 'name' => 'bar'))), - array(new CacheKeyMock('key.2'), new CacheEntryMock(array('id'=>2, 'name' => 'foo'))), - ); - } - - /** - * @dataProvider dataProviderCacheValues - */ - public function testPutGetAndEvict($key, $entry) - { - $this->assertNull($this->regionAccess->get($key)); - - $this->regionAccess->put($key, $entry); - - $this->assertEquals($entry, $this->regionAccess->get($key)); - - $this->regionAccess->evict($key); - - $this->assertNull($this->regionAccess->get($key)); - } - - public function testEvictAll() - { - $key1 = new CacheKeyMock('key.1'); - $key2 = new CacheKeyMock('key.2'); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - - $this->regionAccess->put($key1, new CacheEntryMock(array('value' => 'foo'))); - $this->regionAccess->put($key2, new CacheEntryMock(array('value' => 'bar'))); - - $this->assertNotNull($this->regionAccess->get($key1)); - $this->assertNotNull($this->regionAccess->get($key2)); - - $this->assertEquals(new CacheEntryMock(array('value' => 'foo')), $this->regionAccess->get($key1)); - $this->assertEquals(new CacheEntryMock(array('value' => 'bar')), $this->regionAccess->get($key2)); - - $this->regionAccess->evictAll(); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNull($this->regionAccess->get($key2)); - } -} diff --git a/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php b/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php index 49dca59ca90..e0fd5d2a460 100644 --- a/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/CacheKeyTest.php @@ -15,7 +15,7 @@ public function testEntityCacheKeyIdentifierCollision() $key1 = new EntityCacheKey('Foo', array('id'=>1)); $key2 = new EntityCacheKey('Bar', array('id'=>1)); - $this->assertNotEquals($key1->hash(), $key2->hash()); + $this->assertNotEquals($key1->hash, $key2->hash); } public function testEntityCacheKeyIdentifierType() @@ -23,7 +23,7 @@ public function testEntityCacheKeyIdentifierType() $key1 = new EntityCacheKey('Foo', array('id'=>1)); $key2 = new EntityCacheKey('Foo', array('id'=>'1')); - $this->assertEquals($key1->hash(), $key2->hash()); + $this->assertEquals($key1->hash, $key2->hash); } public function testEntityCacheKeyIdentifierOrder() @@ -31,7 +31,7 @@ public function testEntityCacheKeyIdentifierOrder() $key1 = new EntityCacheKey('Foo', array('foo_bar'=>1, 'bar_foo'=> 2)); $key2 = new EntityCacheKey('Foo', array('bar_foo'=>2, 'foo_bar'=> 1)); - $this->assertEquals($key1->hash(), $key2->hash()); + $this->assertEquals($key1->hash, $key2->hash); } public function testCollectionCacheKeyIdentifierType() @@ -39,7 +39,7 @@ public function testCollectionCacheKeyIdentifierType() $key1 = new CollectionCacheKey('Foo', 'assoc', array('id'=>1)); $key2 = new CollectionCacheKey('Foo', 'assoc', array('id'=>'1')); - $this->assertEquals($key1->hash(), $key2->hash()); + $this->assertEquals($key1->hash, $key2->hash); } public function testCollectionCacheKeyIdentifierOrder() @@ -47,7 +47,7 @@ public function testCollectionCacheKeyIdentifierOrder() $key1 = new CollectionCacheKey('Foo', 'assoc', array('foo_bar'=>1, 'bar_foo'=> 2)); $key2 = new CollectionCacheKey('Foo', 'assoc', array('bar_foo'=>2, 'foo_bar'=> 1)); - $this->assertEquals($key1->hash(), $key2->hash()); + $this->assertEquals($key1->hash, $key2->hash); } public function testCollectionCacheKeyIdentifierCollision() @@ -55,7 +55,7 @@ public function testCollectionCacheKeyIdentifierCollision() $key1 = new CollectionCacheKey('Foo', 'assoc', array('id'=>1)); $key2 = new CollectionCacheKey('Bar', 'assoc', array('id'=>1)); - $this->assertNotEquals($key1->hash(), $key2->hash()); + $this->assertNotEquals($key1->hash, $key2->hash); } public function testCollectionCacheKeyAssociationCollision() @@ -63,6 +63,6 @@ public function testCollectionCacheKeyAssociationCollision() $key1 = new CollectionCacheKey('Foo', 'assoc1', array('id'=>1)); $key2 = new CollectionCacheKey('Foo', 'assoc2', array('id'=>1)); - $this->assertNotEquals($key1->hash(), $key2->hash()); + $this->assertNotEquals($key1->hash, $key2->hash); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php index be75cc47ae8..eda38cfe7a1 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheTest.php @@ -49,7 +49,7 @@ private function putEntityCacheEntry($className, array $identifier, array $data) $cacheEntry = new EntityCacheEntry($metadata->name, $data); $persister = $this->em->getUnitOfWork()->getEntityPersister($metadata->rootEntityName); - $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); + $persister->getCacheRegion()->put($cacheKey, $cacheEntry); } /** @@ -65,7 +65,7 @@ private function putCollectionCacheEntry($className, $association, array $ownerI $cacheEntry = new CollectionCacheEntry($data); $persister = $this->em->getUnitOfWork()->getCollectionPersister($metadata->getAssociationMapping($association)); - $persister->getCacheRegionAcess()->put($cacheKey, $cacheEntry); + $persister->getCacheRegion()->put($cacheKey, $cacheEntry); } public function testImplementsCache() @@ -75,14 +75,14 @@ public function testImplementsCache() public function testGetEntityCacheRegionAccess() { - $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccessStrategy', $this->cache->getEntityCacheRegionAccess(State::CLASSNAME)); - $this->assertNull($this->cache->getEntityCacheRegionAccess(self::NON_CACHEABLE_ENTITY)); + $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->cache->getEntityCacheRegion(State::CLASSNAME)); + $this->assertNull($this->cache->getEntityCacheRegion(self::NON_CACHEABLE_ENTITY)); } public function testGetCollectionCacheRegionAccess() { - $this->assertInstanceOf('Doctrine\ORM\Cache\RegionAccessStrategy', $this->cache->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')); - $this->assertNull($this->cache->getCollectionCacheRegionAccess(self::NON_CACHEABLE_ENTITY, 'phonenumbers')); + $this->assertInstanceOf('Doctrine\ORM\Cache\Region', $this->cache->getCollectionCacheRegion(State::CLASSNAME, 'cities')); + $this->assertNull($this->cache->getCollectionCacheRegion(self::NON_CACHEABLE_ENTITY, 'phonenumbers')); } public function testContainsEntity() diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionHydratorTest.php similarity index 87% rename from tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php rename to tests/Doctrine/Tests/ORM/Cache/DefaultCollectionHydratorTest.php index 6419bc8e49e..74082d8a7bb 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCollectionHydratorTest.php @@ -12,15 +12,15 @@ use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\CollectionCacheEntry; use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\Cache\DefaultCollectionEntryStructure; +use Doctrine\ORM\Cache\DefaultCollectionHydrator; /** * @group DDC-2183 */ -class CollectionEntryStructureTest extends OrmFunctionalTestCase +class DefaultCollectionHydratorTest extends OrmFunctionalTestCase { /** - * @var \Doctrine\ORM\Cache\CollectionEntryStructure + * @var \Doctrine\ORM\Cache\CollectionHydrator */ private $structure; @@ -29,17 +29,17 @@ protected function setUp() $this->enableSecondLevelCache(); parent::setUp(); - $this->structure = new DefaultCollectionEntryStructure($this->_em); + $this->structure = new DefaultCollectionHydrator($this->_em); } public function testImplementsCollectionEntryStructure() { - $this->assertInstanceOf('Doctrine\ORM\Cache\CollectionEntryStructure', $this->structure); + $this->assertInstanceOf('Doctrine\ORM\Cache\DefaultCollectionHydrator', $this->structure); } public function testLoadCacheCollection() { - $targetRegion = $this->_em->getCache()->getEntityCacheRegionAccess(City::CLASSNAME); + $targetRegion = $this->_em->getCache()->getEntityCacheRegion(City::CLASSNAME); $entry = new CollectionCacheEntry(array( array('id'=>31), array('id'=>32), diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php similarity index 91% rename from tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php rename to tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php index 59dbdee37c2..4c44430b064 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultEntityEntryStructureTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultEntityHydratorTest.php @@ -8,16 +8,15 @@ use Doctrine\ORM\Cache\EntityCacheEntry; use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Country; -use Doctrine\ORM\Cache\DefaultCacheFactory; -use Doctrine\ORM\Cache\DefaultEntityEntryStructure; +use Doctrine\ORM\Cache\DefaultEntityHydrator; /** * @group DDC-2183 */ -class EntityEntryStructureTest extends OrmTestCase +class DefaultEntityHydratorTest extends OrmTestCase { /** - * @var \Doctrine\ORM\Cache\EntityEntryStructure + * @var \Doctrine\ORM\Cache\EntityHydrator */ private $structure; @@ -31,12 +30,12 @@ protected function setUp() parent::setUp(); $this->em = $this->_getTestEntityManager(); - $this->structure = new DefaultEntityEntryStructure($this->em); + $this->structure = new DefaultEntityHydrator($this->em); } public function testImplementsEntityEntryStructure() { - $this->assertInstanceOf('Doctrine\ORM\Cache\EntityEntryStructure', $this->structure); + $this->assertInstanceOf('\Doctrine\ORM\Cache\EntityHydrator', $this->structure); } public function testCreateEntity() diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php index 33d1b199b81..ede4176c2be 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php @@ -12,8 +12,6 @@ use Doctrine\Tests\Models\Cache\City; use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\Travel; -use Doctrine\ORM\Cache\CacheFactory; -use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Tests\Models\Generic\BooleanModel; use Doctrine\ORM\Cache\EntityCacheEntry; @@ -505,7 +503,7 @@ public function testNotCacheableEntityException() } -class CacheFactoryDefaultQueryCacheTest implements CacheFactory +class CacheFactoryDefaultQueryCacheTest extends \Doctrine\ORM\Cache\DefaultCacheFactory { private $queryCache; private $region; @@ -516,34 +514,13 @@ public function __construct(DefaultQueryCache $queryCache, CacheRegionMock $regi $this->region = $region; } - public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) - { - return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteEntityRegionAccessStrategy($this->region); - } - - public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) - { - return new \Doctrine\ORM\Cache\Access\NonStrictReadWriteCollectionRegionAccessStrategy($this->region); - } - public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { return $this->queryCache; } - public function buildCollectionEntryStructure(EntityManagerInterface $em) + protected function createRegion($regionName) { - return new \Doctrine\ORM\Cache\DefaultCollectionEntryStructure($em); - } - - public function buildEntityEntryStructure(EntityManagerInterface $em) - { - return new \Doctrine\ORM\Cache\DefaultEntityEntryStructure($em); - } - - private function createRegion($regionName) - { - return $this->region; } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php b/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php deleted file mode 100644 index 47e7b91934f..00000000000 --- a/tests/Doctrine/Tests/ORM/Cache/NonStrictReadWriteCollectionRegionAccessStrategyTest.php +++ /dev/null @@ -1,17 +0,0 @@ -enableSecondLevelCache(); + parent::setUp(); + + $this->em = $this->_getTestEntityManager(); + $this->region = $this->createRegion(); + $this->collectionPersister = $this->getMock('Doctrine\ORM\Persisters\CollectionPersister', $this->collectionPersisterMockMethods); + } + + /** + * @return \Doctrine\ORM\Cache\Region + */ + protected function createRegion() + { + return $this->getMock('Doctrine\ORM\Cache\Region', $this->regionMockMethods); + } + + /** + * @return \Doctrine\ORM\PersistentCollection + */ + protected function createCollection($owner, $assoc = null, $class = null, $elements = null) + { + $em = $this->em; + $class = $class ?: $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\State'); + $assoc = $assoc ?: $class->associationMappings['cities']; + $coll = new \Doctrine\ORM\PersistentCollection($em, $class, $elements ?: new ArrayCollection); + + $coll->setOwner($owner, $assoc); + $coll->setInitialized(true); + + return $coll; + } + + protected function createPersisterDefault() + { + $assoc = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\State')->associationMappings['cities']; + + return $this->createPersister($this->em, $this->collectionPersister, $this->region, $assoc); + } + + public function testImplementsEntityPersister() + { + $persister = $this->createPersisterDefault(); + + $this->assertInstanceOf('Doctrine\ORM\Persisters\CollectionPersister', $persister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedPersister', $persister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $persister); + } + + public function testInvokeDelete() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('delete') + ->with($this->equalTo($collection)); + + $this->assertNull($persister->delete($collection)); + } + + public function testInvokeUpdate() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $collection->setDirty(true); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('update') + ->with($this->equalTo($collection)); + + $this->assertNull($persister->update($collection)); + } + + public function testInvokeDeleteRows() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('deleteRows') + ->with($this->equalTo($collection)); + + $this->assertNull($persister->deleteRows($collection)); + } + + public function testInvokeInsertRows() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('insertRows') + ->with($this->equalTo($collection)); + + $this->assertNull($persister->insertRows($collection)); + } + + public function testInvokeCount() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('count') + ->with($this->equalTo($collection)) + ->will($this->returnValue(0)); + + $this->assertEquals(0, $persister->count($collection)); + } + + public function testInvokEslice() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $slice = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('slice') + ->with($this->equalTo($collection), $this->equalTo(1), $this->equalTo(2)) + ->will($this->returnValue($slice)); + + $this->assertEquals($slice, $persister->slice($collection, 1 , 2)); + } + + public function testInvokeContains() + { + $entity = new State("Foo"); + $element = new State("Bar"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('contains') + ->with($this->equalTo($collection), $this->equalTo($element)) + ->will($this->returnValue(false)); + + $this->assertFalse($persister->contains($collection,$element)); + } + + public function testInvokeContainsKey() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('containsKey') + ->with($this->equalTo($collection), $this->equalTo(0)) + ->will($this->returnValue(false)); + + $this->assertFalse($persister->containsKey($collection, 0)); + } + + public function testInvokeRemoveElement() + { + $entity = new State("Foo"); + $element = new State("Bar"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('removeElement') + ->with($this->equalTo($collection), $this->equalTo($element)) + ->will($this->returnValue(false)); + + $this->assertFalse($persister->removeElement($collection, $element)); + } + + public function testInvokeRemoveKey() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('removeKey') + ->with($this->equalTo($collection), $this->equalTo(0)) + ->will($this->returnValue(false)); + + $this->assertFalse($persister->removeKey($collection, 0)); + } + + public function testInvokeGet() + { + $entity = new State("Foo"); + $element = new State("Bar"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->collectionPersister->expects($this->once()) + ->method('get') + ->with($this->equalTo($collection), $this->equalTo(0)) + ->will($this->returnValue($element)); + + $this->assertEquals($element, $persister->get($collection, 0)); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php new file mode 100644 index 00000000000..c3d550a7377 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php @@ -0,0 +1,415 @@ +enableSecondLevelCache(); + parent::setUp(); + + $this->em = $this->_getTestEntityManager(); + $this->region = $this->createRegion(); + $this->entityPersister = $this->getMock('Doctrine\ORM\Persisters\EntityPersister', $this->entityPersisterMockMethods); + } + + /** + * @return \Doctrine\ORM\Cache\Region + */ + protected function createRegion() + { + return $this->getMock('Doctrine\ORM\Cache\Region', $this->regionMockMethods); + } + + /** + * @return Doctrine\ORM\Cache\Persister\AbstractEntityPersister + */ + protected function createPersisterDefault() + { + return $this->createPersister($this->em, $this->entityPersister, $this->region, $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country')); + } + + public function testImplementsEntityPersister() + { + $persister = $this->createPersisterDefault(); + + $this->assertInstanceOf('Doctrine\ORM\Persisters\EntityPersister', $persister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedPersister', $persister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $persister); + } + + public function testInvokeAddInsert() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('addInsert') + ->with($this->equalTo($entity)); + + $this->assertNull($persister->addInsert($entity)); + } + + public function testInvokeGetInserts() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('getInserts') + ->will($this->returnValue(array($entity))); + + $this->assertEquals(array($entity), $persister->getInserts()); + } + + public function testInvokeGetSelectSQL() + { + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('getSelectSQL') + ->with($this->equalTo(array('name'=>'Foo'))) + ->will($this->returnValue('SELECT * FROM foo WERE name = ?')); + + $this->assertEquals('SELECT * FROM foo WERE name = ?', $persister->getSelectSQL(array('name'=>'Foo'))); + } + + public function testInvokeGetInsertSQL() + { + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('getInsertSQL') + ->will($this->returnValue('INSERT INTO foo (?)')); + + $this->assertEquals('INSERT INTO foo (?)', $persister->getInsertSQL()); + } + + public function testInvokeExpandParameters() + { + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('expandParameters') + ->with($this->equalTo(array('name'=>'Foo'))) + ->will($this->returnValue(array('name'=>'Foo'))); + + $this->assertEquals(array('name'=>'Foo'), $persister->expandParameters(array('name'=>'Foo'))); + } + + public function testInvokeSelectConditionStatementSQL() + { + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('getSelectConditionStatementSQL') + ->with($this->equalTo('id'), $this->equalTo(1)) + ->will($this->returnValue('name = 1')); + + $this->assertEquals('name = 1', $persister->getSelectConditionStatementSQL('id', 1)); + } + + public function testInvokeExecuteInserts() + { + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('executeInserts') + ->will($this->returnValue(array('id' => 1))); + + $this->assertEquals(array('id' => 1), $persister->executeInserts()); + } + + public function testInvokeUpdate() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('update') + ->with($this->equalTo($entity)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->assertNull($persister->update($entity)); + } + + public function testInvokeDelete() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('delete') + ->with($this->equalTo($entity)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->assertNull($persister->delete($entity)); + } + + public function testInvokeGetOwningTable() + { + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('getOwningTable') + ->with($this->equalTo('name')) + ->will($this->returnValue('t')); + + $this->assertEquals('t', $persister->getOwningTable('name')); + } + + public function testInvokeLoad() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('load') + ->with($this->equalTo(array('id' => 1)), $this->equalTo($entity)) + ->will($this->returnValue($entity)); + + $this->assertEquals($entity, $persister->load(array('id' => 1), $entity)); + } + + public function testInvokeLoadAll() + { + $rsm = new ResultSetMappingBuilder($this->em); + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $rsm->addEntityResult(Country::CLASSNAME, 'c'); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $this->entityPersister->expects($this->once()) + ->method('loadAll') + ->with($this->equalTo(array('id' => 1))) + ->will($this->returnValue(array($entity))); + + $this->entityPersister->expects($this->once()) + ->method('getResultSetMapping') + ->will($this->returnValue($rsm)); + + $this->assertEquals(array($entity), $persister->loadAll(array('id' => 1))); + } + + public function testInvokeLoadById() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('loadById') + ->with($this->equalTo(array('id' => 1)), $this->equalTo($entity)) + ->will($this->returnValue($entity)); + + $this->assertEquals($entity, $persister->loadById(array('id' => 1), $entity)); + } + + public function testInvokeLoadOneToOneEntity() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('loadOneToOneEntity') + ->with($this->equalTo(array()), $this->equalTo('foo'), $this->equalTo(array('id' => 11))) + ->will($this->returnValue($entity)); + + $this->assertEquals($entity, $persister->loadOneToOneEntity(array(), 'foo', array('id' => 11))); + } + + public function testInvokeRefresh() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('refresh') + ->with($this->equalTo(array('id' => 1)), $this->equalTo($entity)) + ->will($this->returnValue($entity)); + + $this->assertNull($persister->refresh(array('id' => 1), $entity)); + } + + public function testInvokeLoadCriteria() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + $criteria = new Criteria(); + + $this->entityPersister->expects($this->once()) + ->method('loadCriteria') + ->with($this->equalTo($criteria)) + ->will($this->returnValue(array($entity))); + + $this->assertEquals(array($entity), $persister->loadCriteria($criteria)); + } + + public function testInvokeGetManyToManyCollection() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('getManyToManyCollection') + ->with($this->equalTo(array()), $this->equalTo('Foo'), $this->equalTo(1), $this->equalTo(2)) + ->will($this->returnValue(array($entity))); + + $this->assertEquals(array($entity), $persister->getManyToManyCollection(array(), 'Foo', 1 ,2)); + } + + public function testInvokeGetOneToManyCollection() + { + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('getOneToManyCollection') + ->with($this->equalTo(array()), $this->equalTo('Foo'), $this->equalTo(1), $this->equalTo(2)) + ->will($this->returnValue(array($entity))); + + $this->assertEquals(array($entity), $persister->getOneToManyCollection(array(), 'Foo', 1 ,2)); + } + + public function testInvokeLoadManyToManyCollection() + { + $mapping = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country'); + $assoc = array('type' => 1); + $coll = new PersistentCollection($this->em, 'Foo', $mapping); + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('loadManyToManyCollection') + ->with($this->equalTo($assoc), $this->equalTo('Foo'), $coll) + ->will($this->returnValue(array($entity))); + + $this->assertEquals(array($entity), $persister->loadManyToManyCollection($assoc, 'Foo', $coll)); + } + + public function testInvokeLoadOneToManyCollection() + { + $mapping = $this->em->getClassMetadata('Doctrine\Tests\Models\Cache\Country'); + $assoc = array('type' => 1); + $coll = new PersistentCollection($this->em, 'Foo', $mapping); + $persister = $this->createPersisterDefault(); + $entity = new Country("Foo"); + + $this->entityPersister->expects($this->once()) + ->method('loadOneToManyCollection') + ->with($this->equalTo($assoc), $this->equalTo('Foo'), $coll) + ->will($this->returnValue(array($entity))); + + $this->assertEquals(array($entity), $persister->loadOneToManyCollection($assoc, 'Foo', $coll)); + } + + public function testInvokeLock() + { + $identifier = array('id' => 1); + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('lock') + ->with($this->equalTo($identifier), $this->equalTo(1)); + + $this->assertNull($persister->lock($identifier, 1)); + } + + public function testInvokeExists() + { + $entity = new Country("Foo"); + $persister = $this->createPersisterDefault(); + + $this->entityPersister->expects($this->once()) + ->method('exists') + ->with($this->equalTo($entity), $this->equalTo(array())); + + $this->assertNull($persister->exists($entity, array())); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersisterTest.php new file mode 100644 index 00000000000..2a2b626926d --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedCollectionPersisterTest.php @@ -0,0 +1,23 @@ +createPersisterDefault(); + $entity = new Country("Foo"); + + $persister->update($entity); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php new file mode 100644 index 00000000000..be4d13d537a --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php @@ -0,0 +1,41 @@ +getMock('Doctrine\ORM\Cache\ConcurrentRegion', $this->regionMockMethods); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php new file mode 100644 index 00000000000..ff1133649c9 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php @@ -0,0 +1,42 @@ +getMock('Doctrine\ORM\Cache\ConcurrentRegion', $this->regionMockMethods); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyCollectionRegionAccessStrategyTest.php b/tests/Doctrine/Tests/ORM/Cache/ReadOnlyCollectionRegionAccessStrategyTest.php deleted file mode 100644 index d521085f70c..00000000000 --- a/tests/Doctrine/Tests/ORM/Cache/ReadOnlyCollectionRegionAccessStrategyTest.php +++ /dev/null @@ -1,17 +0,0 @@ -regionAccess->afterUpdate(new CacheKeyMock('key'), new CacheEntryMock(array('value' => 'foo'))); - } -} diff --git a/tests/Doctrine/Tests/ORM/Cache/ReadWriteCollectionRegionAccessStrategyTest.php b/tests/Doctrine/Tests/ORM/Cache/ReadWriteCollectionRegionAccessStrategyTest.php deleted file mode 100644 index 58e33bc69ea..00000000000 --- a/tests/Doctrine/Tests/ORM/Cache/ReadWriteCollectionRegionAccessStrategyTest.php +++ /dev/null @@ -1,29 +0,0 @@ - 'foo')); - $entry2 = new CacheEntryMock(array('value' => 'bar')); - - $this->regionAccess->put($key1, $entry1); - $this->regionAccess->put($key2, $entry2); - - $this->assertNotNull($this->regionAccess->get($key1)); - $this->assertNotNull($this->regionAccess->get($key2)); - - $lock1 = $this->regionAccess->lockItem($key1); - $lock2 = $this->regionAccess->lockItem($key1); - - $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock1); - $this->assertNull($lock2); - - $this->assertNull($this->regionAccess->get($key1)); - $this->assertNotNull($this->regionAccess->get($key2)); - - $this->regionAccess->unlockItem($key1, $lock1); - - $this->assertNotNull($this->regionAccess->get($key1)); - $this->assertNotNull($this->regionAccess->get($key2)); - - $this->assertEquals($entry1, $this->regionAccess->get($key1)); - $this->assertEquals($entry2, $this->regionAccess->get($key2)); - } - - public function testLockWriteOnUpdate() - { - $key = new CacheKeyMock('key.1'); - $entry = new CacheEntryMock(array('value' => 'foo')); - $entry1 = new CacheEntryMock(array('value' => 'foo')); - $region = $this->regionAccess->getRegion(); - - $this->assertTrue($this->regionAccess->afterInsert($key, $entry)); - $this->assertEquals($entry, $this->regionAccess->get($key)); - - $lock = $this->regionAccess->lockItem($key); - $this->assertNull($this->regionAccess->get($key)); - $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock); - - //Somehow another proc get the lock - $region->setLock($key, Lock::createLockRead()); - - $this->assertFalse($this->regionAccess->afterUpdate($key, $entry1, $lock)); - } - - public function testExceptionShouldUnlockItem() - { - $key = new CacheKeyMock('key.1'); - $entry = new CacheEntryMock(array('value' => 'foo')); - $region = $this->regionAccess->getRegion(); - - $this->assertTrue($this->regionAccess->afterInsert($key, $entry)); - $this->assertEquals($entry, $this->regionAccess->get($key)); - - $lock = $this->regionAccess->lockItem($key); - $this->assertNull($this->regionAccess->get($key)); - $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock); - - $region->addException('put', new \RuntimeException('Some concurrency exception')); - - try { - $this->regionAccess->afterUpdate($key, $entry, $lock); - - $this->fail('Expected Exception'); - - } catch (\Doctrine\ORM\Cache\CacheException $exc) { - $this->assertEquals('Some concurrency exception', $exc->getMessage()); - } - - $this->assertNotNull($this->regionAccess->get($key)); - $this->assertEquals($entry, $this->regionAccess->get($key)); - } -} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php index 53c3ff37bf4..fa8e20b531a 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheAbstractTest.php @@ -183,12 +183,12 @@ protected function loadFixturesAttractionsInfo() protected function getEntityRegion($className) { - return $this->cache->getEntityCacheRegionAccess($className)->getRegion()->getName(); + return $this->cache->getEntityCacheRegion($className)->getName(); } protected function getCollectionRegion($className, $association) { - return $this->cache->getCollectionCacheRegionAccess($className, $association)->getRegion()->getName(); + return $this->cache->getCollectionCacheRegion($className, $association)->getName(); } protected function getDefaultQueryRegionName() diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php index d94c39cc1b9..c3f08d83fe2 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php @@ -2,21 +2,13 @@ namespace Doctrine\Tests\ORM\Functional; - -use Doctrine\ORM\Cache\Access\ReadWriteCollectionRegionAccessStrategy; -use Doctrine\ORM\Cache\Access\ReadWriteEntityRegionAccessStrategy; -use Doctrine\ORM\Cache\DefaultCollectionEntryStructure; -use Doctrine\ORM\Cache\DefaultEntityEntryStructure; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\Tests\Mocks\ConcurrentRegionMock; use Doctrine\ORM\Cache\Region\DefaultRegion; -use Doctrine\ORM\Cache\DefaultQueryCache; -use Doctrine\ORM\EntityManagerInterface; use Doctrine\Tests\Models\Cache\Country; -use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\Tests\Models\Cache\State; -use Doctrine\ORM\Cache\CacheFactory; use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Cache\Lock; @@ -30,6 +22,8 @@ class SecondLevelCacheConcurrentTest extends SecondLevelCacheAbstractTest */ private $cacheFactory; + private $countryMetadata; + protected function setUp() { $this->enableSecondLevelCache(); @@ -38,82 +32,44 @@ protected function setUp() $this->cacheFactory = new CacheFactorySecondLevelCacheConcurrentTest($this->getSharedSecondLevelCacheDriverImpl()); $this->_em->getConfiguration()->setSecondLevelCacheFactory($this->cacheFactory); - } - public function testBasicConcurrentEntityReadLock() - { - $this->loadFixturesCountries(); - $this->_em->clear(); + $this->countryMetadata = $this->_em->getClassMetadata(Country::CLASSNAME); + $countryMetadata = clone $this->countryMetadata; - $countryId = $this->countries[0]->getId(); - $cacheId = new EntityCacheKey(Country::CLASSNAME, array('id'=>$countryId)); - $region = $this->_em->getCache()->getEntityCacheRegionAccess(Country::CLASSNAME)->getRegion(); + $countryMetadata->cache['usage'] = ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE; - $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); - - /** @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ - $region->setLock($cacheId, Lock::createLockRead()); // another proc lock the entity cache - - $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + $this->_em->getMetadataFactory()->setMetadataFor(Country::CLASSNAME, $countryMetadata); + } - $queryCount = $this->getCurrentQueryCount(); - $country = $this->_em->find(Country::CLASSNAME, $countryId); + protected function tearDown() + { + parent::tearDown(); - $this->assertInstanceOf(Country::CLASSNAME, $country); - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); - $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); + $this->_em->getMetadataFactory()->setMetadataFor(Country::CLASSNAME, $this->countryMetadata); } - public function testBasicConcurrentEntityWriteLock() + public function testBasicConcurrentEntityReadLock() { $this->loadFixturesCountries(); $this->_em->clear(); - $lock = Lock::createLockWrite(); $countryId = $this->countries[0]->getId(); - $cacheKey = new EntityCacheKey(Country::CLASSNAME, array('id'=>$countryId)); - $region = $this->cache->getEntityCacheRegionAccess(Country::CLASSNAME)->getRegion(); + $cacheId = new EntityCacheKey(Country::CLASSNAME, array('id'=>$countryId)); + $region = $this->_em->getCache()->getEntityCacheRegion(Country::CLASSNAME); $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); /** @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ - $region->setLock($cacheKey, $lock); // another proc lock the entity cache - - $queryCount = $this->getCurrentQueryCount(); - $country = $this->_em->find(Country::CLASSNAME, $countryId); // Cache locked, goes straight to the database + $region->setLock($cacheId, Lock::createLockRead()); // another proc lock the entity cache - $this->assertInstanceOf(Country::CLASSNAME, $country); - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); - $country->setName('Foo 1'); - $this->_em->persist($country); - $this->_em->flush(); - $this->_em->clear(); - $queryCount = $this->getCurrentQueryCount(); - $country = $this->_em->find(Country::CLASSNAME, $countryId); // Cache locked, goes straight to the database + $country = $this->_em->find(Country::CLASSNAME, $countryId); $this->assertInstanceOf(Country::CLASSNAME, $country); $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); - $this->assertEquals('Foo 1', $country->getName()); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); - - /** @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ - $region->writeUnlock($cacheKey, $lock); // another proc unlock - $region->evict($cacheKey); // and clear the cache. - - $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $countryId)); - - $this->_em->clear(); - - $queryCount = $this->getCurrentQueryCount(); - $country = $this->_em->find(Country::CLASSNAME, $countryId); // No cache - - $this->assertInstanceOf(Country::CLASSNAME, $country); - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); - $this->assertEquals('Foo 1', $country->getName()); - $this->assertTrue($this->cache->containsEntity(Country::CLASSNAME, $countryId)); } public function testBasicConcurrentCollectionReadLock() @@ -136,7 +92,7 @@ public function testBasicConcurrentCollectionReadLock() $stateId = $this->states[0]->getId(); $cacheId = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>$stateId)); - $region = $this->_em->getCache()->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')->getRegion(); + $region = $this->_em->getCache()->getCollectionCacheRegion(State::CLASSNAME, 'cities'); $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); @@ -164,123 +120,16 @@ public function testBasicConcurrentCollectionReadLock() $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); } - - public function testBasicConcurrentCollectionWriteLock() - { - $this->loadFixturesCountries(); - $this->loadFixturesStates(); - $this->loadFixturesCities(); - - $this->_em->clear(); - $this->evictRegions(); - - $lock = Lock::createLockWrite(); - $stateId = $this->states[0]->getId(); - $state = $this->_em->find(State::CLASSNAME, $stateId); - - $this->assertInstanceOf(State::CLASSNAME, $state); - $this->assertCount(2, $state->getCities()); - - $this->_em->clear(); - $this->secondLevelCacheLogger->clearStats(); - - $stateId = $this->states[0]->getId(); - $cacheKey = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>$stateId)); - $region = $this->_em->getCache()->getCollectionCacheRegionAccess(State::CLASSNAME, 'cities')->getRegion(); - - $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); - - /* @var $region \Doctrine\Tests\Mocks\ConcurrentRegionMock */ - $region->setLock($cacheKey, $lock); // another proc lock entity cache - - $queryCount = $this->getCurrentQueryCount(); - $state = $this->_em->find(State::CLASSNAME, $stateId); - - $this->assertInstanceOf(State::CLASSNAME, $state); - $this->assertCount(2, $state->getCities()); // Cache locked, goes straight to database - - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); - $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); - - $city0 = $state->getCities()->get(0); - $city1 = $state->getCities()->get(1); - - $state->getCities()->remove(0); - - $this->_em->remove($city1); - $this->_em->persist($state); - $this->_em->flush(); - $this->_em->clear(); - - $queryCount = $this->getCurrentQueryCount(); - $state = $this->_em->find(State::CLASSNAME, $stateId); - - $this->assertInstanceOf(State::CLASSNAME, $state); - $this->assertCount(1, $state->getCities()); // Cache locked, goes straight to database - - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); - $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); - - /* @var \Doctrine\Tests\Mocks\ConcurrentRegionMock */ - $region->writeUnlock($cacheKey, $lock); // another proc unlock - $region->evict($cacheKey); // and clear the cache. - - $this->assertFalse($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); - - $this->_em->clear(); - - $queryCount = $this->getCurrentQueryCount(); - $state = $this->_em->find(State::CLASSNAME, $stateId); - - $this->assertInstanceOf(State::CLASSNAME, $state); - $this->assertCount(1, $state->getCities()); //No Cache, goes to database - $this->assertEquals($queryCount + 1, $this->getCurrentQueryCount()); - $this->assertTrue($this->cache->containsCollection(State::CLASSNAME, 'cities', $stateId)); - } } -class CacheFactorySecondLevelCacheConcurrentTest implements CacheFactory +class CacheFactorySecondLevelCacheConcurrentTest extends \Doctrine\ORM\Cache\DefaultCacheFactory { public function __construct(Cache $cache) { $this->cache = $cache; } - public function buildEntityRegionAccessStrategy(ClassMetadata $metadata) - { - $regionName = $metadata->cache['region']; - $region = $this->createRegion($regionName); - $access = new ReadWriteEntityRegionAccessStrategy($region); - - return $access; - } - - public function buildCollectionRegionAccessStrategy(ClassMetadata $metadata, $fieldName) - { - $mapping = $metadata->getAssociationMapping($fieldName); - $regionName = $mapping['cache']['region']; - $region = $this->createRegion($regionName); - $access = new ReadWriteCollectionRegionAccessStrategy($region); - - return $access; - } - - public function buildQueryCache(EntityManagerInterface $em, $regionName = null) - { - return new DefaultQueryCache($em, $this->createRegion($regionName ?: Cache::DEFAULT_QUERY_REGION_NAME)); - } - - public function buildCollectionEntryStructure(EntityManagerInterface $em) - { - return new DefaultCollectionEntryStructure($em); - } - - public function buildEntityEntryStructure(EntityManagerInterface $em) - { - return new DefaultEntityEntryStructure($em); - } - - private function createRegion($regionName) + protected function createRegion($regionName) { $region = new DefaultRegion($regionName, $this->cache); $mock = new ConcurrentRegionMock($region); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php index 1c21b4305ff..b063cd319cb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheJoinTableInheritanceTest.php @@ -15,9 +15,9 @@ class SecondLevelCacheJoinTableInheritanceTest extends SecondLevelCacheAbstractT { public function testUseSameRegion() { - $infoRegion = $this->cache->getEntityCacheRegionAccess(AttractionInfo::CLASSNAME)->getRegion(); - $contactRegion = $this->cache->getEntityCacheRegionAccess(AttractionContactInfo::CLASSNAME)->getRegion(); - $locationRegion = $this->cache->getEntityCacheRegionAccess(AttractionLocationInfo::CLASSNAME)->getRegion(); + $infoRegion = $this->cache->getEntityCacheRegion(AttractionInfo::CLASSNAME); + $contactRegion = $this->cache->getEntityCacheRegion(AttractionContactInfo::CLASSNAME); + $locationRegion = $this->cache->getEntityCacheRegion(AttractionLocationInfo::CLASSNAME); $this->assertEquals($infoRegion->getName(), $contactRegion->getName()); $this->assertEquals($infoRegion->getName(), $locationRegion->getName()); diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php index ac18bfe0d03..9b1b993a0af 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheManyToManyTest.php @@ -14,6 +14,8 @@ class SecondLevelCacheManyToManyTest extends SecondLevelCacheAbstractTest { public function testShouldPutManyToManyCollectionOwningSideOnPersist() { + $this->evictRegions(); + $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); @@ -35,6 +37,8 @@ public function testShouldPutManyToManyCollectionOwningSideOnPersist() public function testPutAndLoadManyToManyRelation() { + $this->evictRegions(); + $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); @@ -140,6 +144,8 @@ public function testPutAndLoadManyToManyRelation() public function testStoreManyToManyAssociationWhitCascade() { + $this->evictRegions(); + $this->loadFixturesCountries(); $this->loadFixturesStates(); $this->loadFixturesCities(); @@ -175,25 +181,34 @@ public function testStoreManyToManyAssociationWhitCascade() $this->assertInstanceOf(Travel::CLASSNAME, $t1); $this->assertCount(3, $t1->getVisitedCities()); $this->assertEquals($queryCount1, $this->getCurrentQueryCount()); + } - $t1->removeVisitedCity($t1->getVisitedCities()->get(1)); + /** + * @expectedException \Doctrine\ORM\Cache\CacheException + * @expectedExceptionMessage Cannot update a readonly collection "Doctrine\Tests\Models\Cache\Travel#visitedCities + */ + public function testReadOnlyCollection() + { + $this->evictRegions(); + + $this->loadFixturesCountries(); + $this->loadFixturesStates(); + $this->loadFixturesCities(); + $this->loadFixturesTraveler(); + $this->loadFixturesTravels(); - $this->_em->persist($t1); - $this->_em->flush(); $this->_em->clear(); - $this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $travel->getId())); - $this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $traveler->getId())); - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[0]->getId())); - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[1]->getId())); - $this->assertTrue($this->cache->containsEntity(City::CLASSNAME, $this->cities[3]->getId())); - $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $travel->getId())); + $this->assertTrue($this->cache->containsEntity(Travel::CLASSNAME, $this->travels[0]->getId())); + $this->assertTrue($this->cache->containsCollection(Travel::CLASSNAME, 'visitedCities', $this->travels[0]->getId())); - $queryCount2 = $this->getCurrentQueryCount(); - $t2 = $this->_em->find(Travel::CLASSNAME, $travel->getId()); + $travel = $this->_em->find(Travel::CLASSNAME, $this->travels[0]->getId()); - $this->assertInstanceOf(Travel::CLASSNAME, $t1); - $this->assertCount(2, $t2->getVisitedCities()); - $this->assertEquals($queryCount2, $this->getCurrentQueryCount()); + $this->assertCount(3, $travel->getVisitedCities()); + + $travel->getVisitedCities()->remove(0); + + $this->_em->persist($travel); + $this->_em->flush(); } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php index bab865a5d0c..99703f2b664 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheQueryCacheTest.php @@ -170,7 +170,7 @@ public function testQueryCacheModeRefresh() $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[0]->getId())); $this->assertFalse($this->cache->containsEntity(Country::CLASSNAME, $this->countries[1]->getId())); - $region = $this->cache->getEntityCacheRegionAccess(Country::CLASSNAME)->getRegion(); + $region = $this->cache->getEntityCacheRegion(Country::CLASSNAME); $queryCount = $this->getCurrentQueryCount(); $dql = 'SELECT c FROM Doctrine\Tests\Models\Cache\Country c'; $result = $this->_em->createQuery($dql) diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php index 813da64e1fa..ca546bb1037 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheRepositoryTest.php @@ -7,7 +7,7 @@ /** * @group DDC-2183 */ -class getSelectSQLTest extends SecondLevelCacheAbstractTest +class SecondLevelCacheRepositoryTest extends SecondLevelCacheAbstractTest { public function testRepositoryCacheFind() { diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php index 16f35cc7402..98c822ca548 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheSingleTableInheritanceTest.php @@ -15,10 +15,10 @@ class SecondLevelCacheSingleTableInheritanceTest extends SecondLevelCacheAbstrac { public function testUseSameRegion() { - $attractionRegion = $this->cache->getEntityCacheRegionAccess(Attraction::CLASSNAME)->getRegion(); - $restaurantRegion = $this->cache->getEntityCacheRegionAccess(Restaurant::CLASSNAME)->getRegion(); - $beachRegion = $this->cache->getEntityCacheRegionAccess(Beach::CLASSNAME)->getRegion(); - $barRegion = $this->cache->getEntityCacheRegionAccess(Bar::CLASSNAME)->getRegion(); + $attractionRegion = $this->cache->getEntityCacheRegion(Attraction::CLASSNAME); + $restaurantRegion = $this->cache->getEntityCacheRegion(Restaurant::CLASSNAME); + $beachRegion = $this->cache->getEntityCacheRegion(Beach::CLASSNAME); + $barRegion = $this->cache->getEntityCacheRegion(Bar::CLASSNAME); $this->assertEquals($attractionRegion->getName(), $restaurantRegion->getName()); $this->assertEquals($attractionRegion->getName(), $beachRegion->getName()); From f99c04aaf7119da918746a7d7ba5af1e444e9c11 Mon Sep 17 00:00:00 2001 From: fabios Date: Wed, 25 Sep 2013 15:06:00 -0400 Subject: [PATCH 66/71] Coverage improvement --- docs/en/reference/second-level-cache.rst | 19 +- .../ORM/Cache/DefaultCacheFactory.php | 23 +- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 2 +- lib/Doctrine/ORM/Cache/LockException.php | 1 + .../ReadWriteCachedCollectionPersister.php | 14 +- .../ReadWriteCachedEntityPersister.php | 14 +- .../ORM/Cache/Region/DefaultRegion.php | 51 ++-- .../ORM/Mapping/ClassMetadataInfo.php | 8 +- .../ORM/Cache/DefaultCacheFactoryTest.php | 210 ++++++++++++++ .../Tests/ORM/Cache/DefaultQueryCacheTest.php | 2 +- .../AbstractCollectionPersisterTest.php | 2 +- .../Persister/AbstractEntityPersisterTest.php | 20 +- ...rictReadWriteCachedEntityPersisterTest.php | 116 ++++++++ ...ReadWriteCachedCollectionPersisterTest.php | 260 ++++++++++++++++++ .../ReadWriteCachedEntityPersisterTest.php | 192 +++++++++++++ .../SecondLevelCacheConcurrentTest.php | 4 +- .../ORM/Mapping/AbstractMappingDriverTest.php | 6 +- .../ORM/Performance/SecondLevelCacheTest.php | 18 +- .../Doctrine/Tests/OrmFunctionalTestCase.php | 2 +- tests/Doctrine/Tests/OrmTestCase.php | 2 +- 20 files changed, 865 insertions(+), 101 deletions(-) create mode 100644 tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php diff --git a/docs/en/reference/second-level-cache.rst b/docs/en/reference/second-level-cache.rst index a0763dbc808..bfe8ad28b81 100644 --- a/docs/en/reference/second-level-cache.rst +++ b/docs/en/reference/second-level-cache.rst @@ -143,28 +143,11 @@ Defines contract for concurrently managed data region. interface ConcurrentRegion extends Region { - /** - * Attempts to write lock the mapping for the given key. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from writeLock - * - * @return \Doctrine\ORM\Cache\Lock - */ - public function writeLock(CacheKey $key, Lock $lock = null); - - /** - * Attempts to write unlock the mapping for the given key. - * - * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to unlock. - * @param \Doctrine\ORM\Cache\Lock $lock The lock previously obtained from writeLock - */ - public function writeUnlock(CacheKey $key, Lock $lock); - /** * Attempts to read lock the mapping for the given key. * * @param \Doctrine\ORM\Cache\CacheKey $key The key of the item to lock. + * * @return \Doctrine\ORM\Cache\Lock A lock instance or NULL if the lock already exists. */ public function readLock(CacheKey $key); diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index f203989beea..fc1c90f14d7 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -67,9 +67,8 @@ public function __construct(Configuration $configuration, CacheDriver $cache) */ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata) { - $regionName = $metadata->cache['region']; $usage = $metadata->cache['usage']; - $region = $this->createRegion($regionName); + $region = $this->createRegion($metadata->cache); if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata); @@ -91,9 +90,8 @@ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPer */ public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, $mapping) { - $regionName = $mapping['cache']['region']; $usage = $mapping['cache']['usage']; - $region = $this->createRegion($regionName); + $region = $this->createRegion($mapping['cache']); if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping); @@ -103,7 +101,7 @@ public function buildCachedCollectionPersister(EntityManagerInterface $em, Colle return new NonStrictReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); } - if ($usage === ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE) { + if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) { return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping); } @@ -115,7 +113,10 @@ public function buildCachedCollectionPersister(EntityManagerInterface $em, Colle */ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { - return new DefaultQueryCache($em, $this->createRegion($regionName ?: Cache::DEFAULT_QUERY_REGION_NAME)); + return new DefaultQueryCache($em, $this->createRegion(array( + 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME, + 'usage' => ClassMetadata::CACHE_USAGE_READ_ONLY, + ))); } /** @@ -135,14 +136,14 @@ public function buildEntityHydrator(EntityManagerInterface $em) } /** - * @param string $regionName + * @param array $cache * - * @return \Doctrine\ORM\Cache\Region + * @return \Doctrine\ORM\Cache\Region\DefaultRegion */ - protected function createRegion($regionName) + protected function createRegion(array $cache) { - return new DefaultRegion($regionName, $this->cache, array( - 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($regionName) + return new DefaultRegion($cache['region'], $this->cache, array( + 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($cache['region']) )); } } diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 5f51830d1b4..7fe70728754 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -161,7 +161,7 @@ public function get(QueryCacheKey $key, ResultSetMapping $rsm) $collection->setInitialized(true); } - $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);; + $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints); } return $result; diff --git a/lib/Doctrine/ORM/Cache/LockException.php b/lib/Doctrine/ORM/Cache/LockException.php index 0322eb51f4d..d4c76240aa9 100644 --- a/lib/Doctrine/ORM/Cache/LockException.php +++ b/lib/Doctrine/ORM/Cache/LockException.php @@ -1,4 +1,5 @@ @@ -101,6 +100,10 @@ public function delete(PersistentCollection $collection) $this->persister->delete($collection); + if ($lock === null) { + return; + } + $this->queuedCache['delete'][spl_object_hash($collection)] = array( 'key' => $key, 'lock' => $lock @@ -124,11 +127,14 @@ public function update(PersistentCollection $collection) $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); $lock = $this->region->readLock($key); - $data = array( + + if ($lock === null) { + return; + } + + $this->queuedCache['update'][spl_object_hash($collection)] = array( 'key' => $key, 'lock' => $lock ); - - $this->queuedCache['delete'][spl_object_hash($collection)] = $data; } } diff --git a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php index f20c14a0c8f..c4031fff078 100644 --- a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php @@ -56,12 +56,6 @@ public function __construct(EntityPersister $persister, ConcurrentRegion $region */ public function afterTransactionComplete() { - if (isset($this->queuedCache['insert'])) { - foreach ($this->queuedCache['insert'] as $item) { - $this->region->evict($item['key']); - } - } - if (isset($this->queuedCache['update'])) { foreach ($this->queuedCache['update'] as $item) { $this->region->evict($item['key']); @@ -107,6 +101,10 @@ public function delete($entity) $this->persister->delete($entity); + if ($lock === null) { + return; + } + $this->queuedCache['delete'][] = array( 'lock' => $lock, 'key' => $key @@ -123,6 +121,10 @@ public function update($entity) $this->persister->update($entity); + if ($lock === null) { + return; + } + $this->queuedCache['update'][] = array( 'lock' => $lock, 'key' => $key diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index 785dd2d8d9d..9e1841bd6bb 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -34,6 +34,8 @@ */ class DefaultRegion implements Region { + const ENTRY_KEY = '_entry_'; + /** * @var \Doctrine\Common\Cache\Cache */ @@ -44,13 +46,18 @@ class DefaultRegion implements Region */ private $name; + /** + * @var string + */ + private $mapKey; + /** * @var integer */ private $lifetime = 0; /** - * @param strgin $name + * @param string $name * @param \Doctrine\Common\Cache\Cache $cache * @param array $configuration */ @@ -58,6 +65,7 @@ public function __construct($name, Cache $cache, array $configuration = array()) { $this->name = $name; $this->cache = $cache; + $this->mapKey = "{$this->name}_map"; if (isset($configuration['lifetime']) && $configuration['lifetime'] > 0) { $this->lifetime = (integer) $configuration['lifetime']; @@ -80,28 +88,12 @@ public function getCache() return $this->cache; } - /** - * @return string - */ - private function entryKey(CacheKey $key) - { - return sprintf("%s.values[%s]", $this->name, $key->hash); - } - - /** - * @return string - */ - private function entriesMapKey() - { - return sprintf("%s[entries]", $this->name); - } - /** * {@inheritdoc} */ public function contains(CacheKey $key) { - return $this->cache->contains($this->entryKey($key)); + return $this->cache->contains($this->name . self::ENTRY_KEY . $key->hash); } /** @@ -109,7 +101,7 @@ public function contains(CacheKey $key) */ public function get(CacheKey $key) { - return $this->cache->fetch($this->entryKey($key)) ?: null; + return $this->cache->fetch($this->name . self::ENTRY_KEY . $key->hash) ?: null; } /** @@ -117,14 +109,13 @@ public function get(CacheKey $key) */ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) { - $entriesKey = $this->entriesMapKey(); - $entryKey = $this->entryKey($key); - $entries = $this->cache->fetch($entriesKey); + $entryKey = $this->name . self::ENTRY_KEY . $key->hash; + $entries = $this->cache->fetch($this->mapKey); - $entries[$entryKey] = true; + $entries[$entryKey] = 1; if ($this->cache->save($entryKey, $entry, $this->lifetime)) { - $this->cache->save($entriesKey, $entries); + $this->cache->save($this->mapKey, $entries); return true; } @@ -137,15 +128,14 @@ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) */ public function evict(CacheKey $key) { - $entriesKey = $this->entriesMapKey(); - $entryKey = $this->entryKey($key); - $entries = $this->cache->fetch($entriesKey); + $entryKey = $this->name . self::ENTRY_KEY . $key->hash; + $entries = $this->cache->fetch($this->mapKey); if ($this->cache->delete($entryKey)) { unset($entries[$entryKey]); - $this->cache->save($entriesKey, $entries); + $this->cache->save($this->mapKey, $entries); return true; } @@ -158,8 +148,7 @@ public function evict(CacheKey $key) */ public function evictAll() { - $entriesKey = $this->entriesMapKey(); - $entries = $this->cache->fetch($entriesKey); + $entries = $this->cache->fetch($this->mapKey); if ( ! is_array($entries) || empty($entries)) { return true; @@ -171,7 +160,7 @@ public function evictAll() } } - $this->cache->save($entriesKey, $entries); + $this->cache->save($this->mapKey, $entries); return empty($entries); } diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 125088610a1..d0be5f2ed9e 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -1014,7 +1014,7 @@ public function enableCache(array $cache) } if ( ! isset($cache['region'])) { - $cache['region'] = strtolower(str_replace('\\', '.', $this->rootEntityName)); + $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)); } $this->cache = $cache; @@ -1028,11 +1028,13 @@ public function enableCache(array $cache) public function enableAssociationCache($fieldName, array $cache) { if ( ! isset($cache['usage'])) { - $cache['usage'] = self::CACHE_USAGE_READ_ONLY; + $cache['usage'] = isset($this->cache['usage']) + ? $this->cache['usage'] + : self::CACHE_USAGE_READ_ONLY; } if ( ! isset($cache['region'])) { - $cache['region'] = strtolower(str_replace('\\', '.', $this->rootEntityName)) . '::' . $fieldName; + $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName; } $this->associationMappings[$fieldName]['cache'] = $cache; diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php new file mode 100644 index 00000000000..42d68ced926 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php @@ -0,0 +1,210 @@ +enableSecondLevelCache(); + parent::setUp(); + + $this->em = $this->_getTestEntityManager(); + + + $arguments = array($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl()); + $this->factory = $this->getMock('\Doctrine\ORM\Cache\DefaultCacheFactory', array( + 'createRegion' + ), $arguments); + } + + public function testInplementsCacheFactory() + { + $this->assertInstanceOf('Doctrine\ORM\Cache\CacheFactory', $this->factory); + } + + public function testBuildCachedEntityPersisterReadOnly() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = clone $em->getClassMetadata($entityName); + $persister = new BasicEntityPersister($em, $metadata); + $region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl())); + + $metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_READ_ONLY; + + $this->factory->expects($this->once()) + ->method('createRegion') + ->with($this->equalTo($metadata->cache)) + ->will($this->returnValue($region)); + + + $cachedPersister = $this->factory->buildCachedEntityPersister($em, $persister, $metadata); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadOnlyCachedEntityPersister', $cachedPersister); + } + + public function testBuildCachedEntityPersisterReadWrite() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = clone $em->getClassMetadata($entityName); + $persister = new BasicEntityPersister($em, $metadata); + $region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl())); + + $metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_READ_WRITE; + + $this->factory->expects($this->once()) + ->method('createRegion') + ->with($this->equalTo($metadata->cache)) + ->will($this->returnValue($region)); + + $cachedPersister = $this->factory->buildCachedEntityPersister($em, $persister, $metadata); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', $cachedPersister); + } + + public function testBuildCachedEntityPersisterNonStrictReadWrite() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = clone $em->getClassMetadata($entityName); + $persister = new BasicEntityPersister($em, $metadata); + $region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl())); + + $metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE; + + $this->factory->expects($this->once()) + ->method('createRegion') + ->with($this->equalTo($metadata->cache)) + ->will($this->returnValue($region)); + + $cachedPersister = $this->factory->buildCachedEntityPersister($em, $persister, $metadata); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedEntityPersister', $cachedPersister); + } + + public function testBuildCachedCollectionPersisterReadOnly() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = $em->getClassMetadata($entityName); + $mapping = $metadata->associationMappings['cities']; + $persister = new OneToManyPersister($em); + $region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl())); + + $mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_READ_ONLY; + + $this->factory->expects($this->once()) + ->method('createRegion') + ->with($this->equalTo($mapping['cache'])) + ->will($this->returnValue($region)); + + + $cachedPersister = $this->factory->buildCachedCollectionPersister($em, $persister, $mapping); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $cachedPersister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadOnlyCachedCollectionPersister', $cachedPersister); + } + + public function testBuildCachedCollectionPersisterReadWrite() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = $em->getClassMetadata($entityName); + $mapping = $metadata->associationMappings['cities']; + $persister = new OneToManyPersister($em); + $region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl())); + + $mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_READ_WRITE; + + $this->factory->expects($this->once()) + ->method('createRegion') + ->with($this->equalTo($mapping['cache'])) + ->will($this->returnValue($region)); + + $cachedPersister = $this->factory->buildCachedCollectionPersister($em, $persister, $mapping); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $cachedPersister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', $cachedPersister); + } + + public function testBuildCachedCollectionPersisterNonStrictReadWrite() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = $em->getClassMetadata($entityName); + $mapping = $metadata->associationMappings['cities']; + $persister = new OneToManyPersister($em); + $region = new ConcurrentRegionMock(new DefaultRegion('regionName', $this->getSharedSecondLevelCacheDriverImpl())); + + $mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE; + + $this->factory->expects($this->once()) + ->method('createRegion') + ->with($this->equalTo($mapping['cache'])) + ->will($this->returnValue($region)); + + $cachedPersister = $this->factory->buildCachedCollectionPersister($em, $persister, $mapping); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedCollectionPersister', $cachedPersister); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedCollectionPersister', $cachedPersister); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Unrecognized access strategy type [-1] + */ + public function testBuildCachedEntityPersisterNonStrictException() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = clone $em->getClassMetadata($entityName); + $persister = new BasicEntityPersister($em, $metadata); + + $metadata->cache['usage'] = -1; + + $this->factory->buildCachedEntityPersister($em, $persister, $metadata); + } + + /** + * @expectedException InvalidArgumentException + * @expectedExceptionMessage Unrecognized access strategy type [-1] + */ + public function testBuildCachedCollectionPersisterException() + { + $em = $this->em; + $entityName = 'Doctrine\Tests\Models\Cache\State'; + $metadata = $em->getClassMetadata($entityName); + $mapping = $metadata->associationMappings['cities']; + $persister = new OneToManyPersister($em); + + $mapping['cache']['usage'] = -1; + + $this->factory->buildCachedCollectionPersister($em, $persister, $mapping); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php index ede4176c2be..b30ee9ccd98 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php @@ -519,7 +519,7 @@ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) return $this->queryCache; } - protected function createRegion($regionName) + protected function createRegion(array $cache) { return $this->region; } diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractCollectionPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractCollectionPersisterTest.php index 461e90fe000..663e0dc08ca 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractCollectionPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractCollectionPersisterTest.php @@ -24,7 +24,7 @@ abstract class AbstractCollectionPersisterTest extends OrmTestCase /** * @var \Doctrine\ORM\Persisters\CollectionPersister */ - private $collectionPersister; + protected $collectionPersister; /** * @var \Doctrine\ORM\EntityManager diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php index c3d550a7377..753a79f0817 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php @@ -151,10 +151,10 @@ public function testInvokeGetSelectSQL() $this->entityPersister->expects($this->once()) ->method('getSelectSQL') - ->with($this->equalTo(array('name'=>'Foo'))) + ->with($this->equalTo(array('name'=>'Foo')), $this->equalTo(array(0)), $this->equalTo(1), $this->equalTo(2), $this->equalTo(3), $this->equalTo(array(4))) ->will($this->returnValue('SELECT * FROM foo WERE name = ?')); - $this->assertEquals('SELECT * FROM foo WERE name = ?', $persister->getSelectSQL(array('name'=>'Foo'))); + $this->assertEquals('SELECT * FROM foo WERE name = ?', $persister->getSelectSQL(array('name'=>'Foo'), array(0), 1, 2, 3, array(4))); } public function testInvokeGetInsertSQL() @@ -186,10 +186,10 @@ public function testInvokeSelectConditionStatementSQL() $this->entityPersister->expects($this->once()) ->method('getSelectConditionStatementSQL') - ->with($this->equalTo('id'), $this->equalTo(1)) + ->with($this->equalTo('id'), $this->equalTo(1), $this->equalTo(array()), $this->equalTo('=')) ->will($this->returnValue('name = 1')); - $this->assertEquals('name = 1', $persister->getSelectConditionStatementSQL('id', 1)); + $this->assertEquals('name = 1', $persister->getSelectConditionStatementSQL('id', 1, array(), '=')); } public function testInvokeExecuteInserts() @@ -250,10 +250,10 @@ public function testInvokeLoad() $this->entityPersister->expects($this->once()) ->method('load') - ->with($this->equalTo(array('id' => 1)), $this->equalTo($entity)) + ->with($this->equalTo(array('id' => 1)), $this->equalTo($entity), $this->equalTo(array(0)), $this->equalTo(array(1)), $this->equalTo(2), $this->equalTo(3), $this->equalTo(array(4))) ->will($this->returnValue($entity)); - $this->assertEquals($entity, $persister->load(array('id' => 1), $entity)); + $this->assertEquals($entity, $persister->load(array('id' => 1), $entity, array(0), array(1), 2, 3, array(4))); } public function testInvokeLoadAll() @@ -268,14 +268,14 @@ public function testInvokeLoadAll() $this->entityPersister->expects($this->once()) ->method('loadAll') - ->with($this->equalTo(array('id' => 1))) + ->with($this->equalTo(array('id' => 1)), $this->equalTo(array(0)), $this->equalTo(1), $this->equalTo(2)) ->will($this->returnValue(array($entity))); $this->entityPersister->expects($this->once()) ->method('getResultSetMapping') ->will($this->returnValue($rsm)); - $this->assertEquals(array($entity), $persister->loadAll(array('id' => 1))); + $this->assertEquals(array($entity), $persister->loadAll(array('id' => 1), array(0), 1, 2)); } public function testInvokeLoadById() @@ -311,10 +311,10 @@ public function testInvokeRefresh() $this->entityPersister->expects($this->once()) ->method('refresh') - ->with($this->equalTo(array('id' => 1)), $this->equalTo($entity)) + ->with($this->equalTo(array('id' => 1)), $this->equalTo($entity), $this->equalTo(0)) ->will($this->returnValue($entity)); - $this->assertNull($persister->refresh(array('id' => 1), $entity)); + $this->assertNull($persister->refresh(array('id' => 1), $entity), 0); } public function testInvokeLoadCriteria() diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersisterTest.php index b825af002d9..13d7b0f3141 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/NonStrictReadWriteCachedEntityPersisterTest.php @@ -4,6 +4,9 @@ use Doctrine\ORM\Cache\Region; use Doctrine\ORM\EntityManager; +use Doctrine\Tests\Models\Cache\Country; +use Doctrine\ORM\Cache\EntityCacheKey; +use Doctrine\ORM\Cache\EntityCacheEntry; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Persisters\EntityPersister; use Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedEntityPersister; @@ -21,4 +24,117 @@ protected function createPersister(EntityManager $em, EntityPersister $persister return new NonStrictReadWriteCachedEntityPersister($persister, $region, $em, $metadata); } + public function testTransactionRollBackShouldClearQueue() + { + $entity = new Country("Foo"); + $persister = $this->createPersisterDefault(); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($entity); + $persister->delete($entity); + + $this->assertCount(2, $property->getValue($persister)); + + $persister->afterTransactionRolledBack(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testInsertTransactionCommitShouldPutCache() + { + $entity = new Country("Foo"); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + $entry = new EntityCacheEntry(Country::CLASSNAME, array('id'=>1, 'name'=>'Foo')); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('put') + ->with($this->equalTo($key), $this->equalTo($entry)); + + $this->entityPersister->expects($this->once()) + ->method('addInsert') + ->with($this->equalTo($entity)); + + $this->entityPersister->expects($this->once()) + ->method('getInserts') + ->will($this->returnValue(array($entity))); + + $this->entityPersister->expects($this->once()) + ->method('executeInserts'); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->addInsert($entity); + $persister->executeInserts(); + + $this->assertCount(1, $property->getValue($persister)); + + $persister->afterTransactionComplete(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testUpdateTransactionCommitShouldPutCache() + { + $entity = new Country("Foo"); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + $entry = new EntityCacheEntry(Country::CLASSNAME, array('id'=>1, 'name'=>'Foo')); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('put') + ->with($this->equalTo($key), $this->equalTo($entry)); + + $this->entityPersister->expects($this->once()) + ->method('update') + ->with($this->equalTo($entity)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($entity); + + $this->assertCount(1, $property->getValue($persister)); + + $persister->afterTransactionComplete(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testDeleteTransactionCommitShouldEvictCache() + { + $entity = new Country("Foo"); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)); + + $this->entityPersister->expects($this->once()) + ->method('delete') + ->with($this->equalTo($entity)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($entity); + + $this->assertCount(1, $property->getValue($persister)); + + $persister->afterTransactionComplete(); + + $this->assertCount(0, $property->getValue($persister)); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php index be4d13d537a..06051880220 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php @@ -2,8 +2,11 @@ namespace Doctrine\Tests\ORM\Cache\Persister; +use Doctrine\ORM\Cache\Lock; use Doctrine\ORM\Cache\Region; use Doctrine\ORM\EntityManager; +use Doctrine\Tests\Models\Cache\State; +use Doctrine\ORM\Cache\CollectionCacheKey; use Doctrine\ORM\Persisters\CollectionPersister; use Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister; @@ -38,4 +41,261 @@ protected function createRegion() { return $this->getMock('Doctrine\ORM\Cache\ConcurrentRegion', $this->regionMockMethods); } + + public function testDeleteShouldLockItem() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($collection); + } + + public function testUpdateShouldLockItem() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($collection); + } + + public function testUpdateTransactionRollBackShouldEvictItem() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($collection); + $persister->afterTransactionRolledBack(); + } + + public function testDeleteTransactionRollBackShouldEvictItem() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($collection); + $persister->afterTransactionRolledBack(); + } + + public function testTransactionRollBackDeleteShouldClearQueue() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($collection); + + $this->assertCount(1, $property->getValue($persister)); + + $persister->afterTransactionRolledBack(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testTransactionRollBackUpdateShouldClearQueue() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($collection); + + $this->assertCount(1, $property->getValue($persister)); + + $persister->afterTransactionRolledBack(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testTransactionRollCommitDeleteShouldClearQueue() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($collection); + + $this->assertCount(1, $property->getValue($persister)); + + $persister->afterTransactionComplete(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testTransactionRollCommitUpdateShouldClearQueue() + { + $entity = new State("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($collection); + + $this->assertCount(1, $property->getValue($persister)); + + $persister->afterTransactionComplete(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testDeleteLockFailureShouldIgnoreQueue() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue(null)); + + $this->collectionPersister->expects($this->once()) + ->method('delete') + ->with($this->equalTo($collection)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($collection); + $this->assertCount(0, $property->getValue($persister)); + } + + public function testUpdateLockFailureShouldIgnoreQueue() + { + $entity = new State("Foo"); + $persister = $this->createPersisterDefault(); + $collection = $this->createCollection($entity); + $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue(null)); + + $this->collectionPersister->expects($this->once()) + ->method('update') + ->with($this->equalTo($collection)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($collection); + $this->assertCount(0, $property->getValue($persister)); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php index ff1133649c9..e2728a92869 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php @@ -4,7 +4,10 @@ use Doctrine\ORM\Cache\Region; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Cache\Lock; +use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\Tests\Models\Cache\Country; use Doctrine\ORM\Persisters\EntityPersister; use Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister; @@ -39,4 +42,193 @@ protected function createRegion() { return $this->getMock('Doctrine\ORM\Cache\ConcurrentRegion', $this->regionMockMethods); } + + public function testDeleteShouldLockItem() + { + $entity = new Country("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($entity); + } + + public function testUpdateShouldLockItem() + { + $entity = new Country("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($entity); + } + + public function testUpdateTransactionRollBackShouldEvictItem() + { + $entity = new Country("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($entity); + $persister->afterTransactionRolledBack(); + } + + public function testDeleteTransactionRollBackShouldEvictItem() + { + $entity = new Country("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->once()) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($entity); + $persister->afterTransactionRolledBack(); + } + + public function testTransactionRollBackShouldClearQueue() + { + $entity = new Country("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->exactly(2)) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->exactly(2)) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($entity); + $persister->delete($entity); + + $this->assertCount(2, $property->getValue($persister)); + + $persister->afterTransactionRolledBack(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testTransactionlCommitShouldClearQueue() + { + $entity = new Country("Foo"); + $lock = Lock::createLockRead(); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->exactly(2)) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue($lock)); + + $this->region->expects($this->exactly(2)) + ->method('evict') + ->with($this->equalTo($key)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($entity); + $persister->delete($entity); + + $this->assertCount(2, $property->getValue($persister)); + + $persister->afterTransactionComplete(); + + $this->assertCount(0, $property->getValue($persister)); + } + + public function testDeleteLockFailureShouldIgnoreQueue() + { + $entity = new Country("Foo"); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue(null)); + + $this->entityPersister->expects($this->once()) + ->method('delete') + ->with($this->equalTo($entity)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->delete($entity); + $this->assertCount(0, $property->getValue($persister)); + } + + public function testUpdateLockFailureShouldIgnoreQueue() + { + $entity = new Country("Foo"); + $persister = $this->createPersisterDefault(); + $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); + $property = new \ReflectionProperty('Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister', 'queuedCache'); + + $property->setAccessible(true); + + $this->region->expects($this->once()) + ->method('readLock') + ->with($this->equalTo($key)) + ->will($this->returnValue(null)); + + $this->entityPersister->expects($this->once()) + ->method('update') + ->with($this->equalTo($entity)); + + $this->em->getUnitOfWork()->registerManaged($entity, array('id'=>1), array('id'=>1, 'name'=>'Foo')); + + $persister->update($entity); + $this->assertCount(0, $property->getValue($persister)); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php index c3f08d83fe2..5485471d5d4 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php @@ -129,9 +129,9 @@ public function __construct(Cache $cache) $this->cache = $cache; } - protected function createRegion($regionName) + protected function createRegion(array $cache) { - $region = new DefaultRegion($regionName, $this->cache); + $region = new DefaultRegion($cache['region'], $this->cache); $mock = new ConcurrentRegionMock($region); return $mock; diff --git a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php index c00e77ec9b5..a30845777b5 100644 --- a/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php +++ b/tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php @@ -885,21 +885,21 @@ public function testSecondLevelCacheMapping() $this->assertArrayHasKey('usage', $class->cache); $this->assertArrayHasKey('region', $class->cache); $this->assertEquals(ClassMetadata::CACHE_USAGE_READ_ONLY, $class->cache['usage']); - $this->assertEquals('doctrine.tests.models.cache.city', $class->cache['region']); + $this->assertEquals('doctrine_tests_models_cache_city', $class->cache['region']); $this->assertArrayHasKey('state', $class->associationMappings); $this->assertArrayHasKey('cache', $class->associationMappings['state']); $this->assertArrayHasKey('usage', $class->associationMappings['state']['cache']); $this->assertArrayHasKey('region', $class->associationMappings['state']['cache']); $this->assertEquals(ClassMetadata::CACHE_USAGE_READ_ONLY, $class->associationMappings['state']['cache']['usage']); - $this->assertEquals('doctrine.tests.models.cache.city::state', $class->associationMappings['state']['cache']['region']); + $this->assertEquals('doctrine_tests_models_cache_city__state', $class->associationMappings['state']['cache']['region']); $this->assertArrayHasKey('attractions', $class->associationMappings); $this->assertArrayHasKey('cache', $class->associationMappings['attractions']); $this->assertArrayHasKey('usage', $class->associationMappings['attractions']['cache']); $this->assertArrayHasKey('region', $class->associationMappings['attractions']['cache']); $this->assertEquals(ClassMetadata::CACHE_USAGE_READ_ONLY, $class->associationMappings['attractions']['cache']['usage']); - $this->assertEquals('doctrine.tests.models.cache.city::attractions', $class->associationMappings['attractions']['cache']['region']); + $this->assertEquals('doctrine_tests_models_cache_city__attractions', $class->associationMappings['attractions']['cache']['region']); } } diff --git a/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php index 02accbaebcf..72eed788714 100644 --- a/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php @@ -52,7 +52,7 @@ public function testFindEntityWithoutCache() $this->findEntity($em, __FUNCTION__); - $this->assertEquals(1002, $this->countQuery($em)); + $this->assertEquals(5502, $this->countQuery($em)); } public function testFindEntityWithCache() @@ -72,7 +72,7 @@ public function testFindAllEntityWithoutCache() $this->findAllEntity($em, __FUNCTION__); - $this->assertEquals(152, $this->countQuery($em)); + $this->assertEquals(153, $this->countQuery($em)); } public function testFindAllEntityWithCache() @@ -83,7 +83,7 @@ public function testFindAllEntityWithCache() $this->findAllEntity($em, __FUNCTION__); - $this->assertEquals(103, $this->countQuery($em)); + $this->assertEquals(53, $this->countQuery($em)); } public function testFindEntityOneToManyWithoutCache() @@ -218,7 +218,7 @@ public function findEntityOneToMany(EntityManagerInterface $em, $label) private function findEntity(EntityManagerInterface $em, $label) { - $times = 50; + $times = 10; $size = 500; $countries = array(); $startPersist = microtime(true); @@ -240,9 +240,10 @@ private function findEntity(EntityManagerInterface $em, $label) $startFind = microtime(true); - for ($i = 0; $i < $times; $i++) { + for ($i = 0; $i <= $times; $i++) { foreach ($countries as $country) { $em->find(Country::CLASSNAME, $country->getId()); + $em->clear(); } } @@ -252,8 +253,8 @@ private function findEntity(EntityManagerInterface $em, $label) private function findAllEntity(EntityManagerInterface $em, $label) { - $times = 50; - $size = 100; + $times = 100; + $size = 50; $startPersist = microtime(true); $rep = $em->getRepository(Country::CLASSNAME); @@ -269,8 +270,9 @@ private function findAllEntity(EntityManagerInterface $em, $label) $startFind = microtime(true); - for ($i = 0; $i < $times; $i++) { + for ($i = 0; $i <= $times; $i++) { $rep->findAll(); + $em->clear(); } printf("\n[%s] find %s countries (%s times)", number_format(microtime(true) - $startFind, 6), $size, $times); diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php index 2e7ccc4a844..dba7bd09f33 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php @@ -448,7 +448,7 @@ protected function _getEntityManager($config = null, $eventManager = null) { if ($this->isSecondLevelCacheEnabled || $enableSecondLevelCache) { - $cache = self::getSharedSecondLevelCacheDriverImpl(); + $cache = $this->getSharedSecondLevelCacheDriverImpl(); $factory = new DefaultCacheFactory($config, $cache); $this->secondLevelCacheFactory = $factory; diff --git a/tests/Doctrine/Tests/OrmTestCase.php b/tests/Doctrine/Tests/OrmTestCase.php index 143bc6d54f7..541e094a31f 100644 --- a/tests/Doctrine/Tests/OrmTestCase.php +++ b/tests/Doctrine/Tests/OrmTestCase.php @@ -127,7 +127,7 @@ protected function _getTestEntityManager($conn = null, $conf = null, $eventManag ), true)); if ($this->isSecondLevelCacheEnabled) { - $cache = self::getSharedSecondLevelCacheDriverImpl(); + $cache = $this->getSharedSecondLevelCacheDriverImpl(); $factory = new DefaultCacheFactory($config, $cache); $this->secondLevelCacheFactory = $factory; From 814b7949404d34c5dc08fbc9ce4772b5c2d3335e Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 26 Sep 2013 12:56:12 -0400 Subject: [PATCH 67/71] Update DefaultRegion to use namespaces --- .../ORM/Cache/DefaultCacheFactory.php | 37 ++++++++--- .../ORM/Cache/Region/DefaultRegion.php | 63 ++++--------------- .../ORM/Cache/DefaultCacheFactoryTest.php | 40 +++++++++++- .../Tests/ORM/Cache/DefaultRegionTest.php | 26 ++++++++ 4 files changed, 106 insertions(+), 60 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index fc1c90f14d7..237e54b4339 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -52,6 +52,11 @@ class DefaultCacheFactory implements CacheFactory */ private $configuration; + /** + * @var array + */ + private $regions; + /** * @param \Doctrine\ORM\Configuration $configuration * @param \Doctrine\Common\Cache\Cache $cache @@ -67,8 +72,14 @@ public function __construct(Configuration $configuration, CacheDriver $cache) */ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata) { + $role = $metadata->rootEntityName; + + if ( ! isset($this->regions[$role])) { + $this->regions[$role] = $this->createRegion($metadata->cache); + } + + $region = $this->regions[$role]; $usage = $metadata->cache['usage']; - $region = $this->createRegion($metadata->cache); if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { return new ReadOnlyCachedEntityPersister($persister, $region, $em, $metadata); @@ -90,8 +101,14 @@ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPer */ public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, $mapping) { + $role = $mapping['sourceEntity'] . '::' . $mapping['fieldName']; + + if ( ! isset($this->regions[$role])) { + $this->regions[$role] = $this->createRegion($mapping['cache']); + } + $usage = $mapping['cache']['usage']; - $region = $this->createRegion($mapping['cache']); + $region = $this->regions[$role]; if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping); @@ -113,10 +130,16 @@ public function buildCachedCollectionPersister(EntityManagerInterface $em, Colle */ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { - return new DefaultQueryCache($em, $this->createRegion(array( - 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME, - 'usage' => ClassMetadata::CACHE_USAGE_READ_ONLY, - ))); + $regionName = $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME; + + if ( ! isset($this->regions[$regionName])) { + $this->regions[$regionName] = $this->createRegion(array( + 'region' => $regionName, + 'usage' => ClassMetadata::CACHE_USAGE_READ_ONLY, + )); + } + + return new DefaultQueryCache($em, $this->regions[$regionName]); } /** @@ -142,7 +165,7 @@ public function buildEntityHydrator(EntityManagerInterface $em) */ protected function createRegion(array $cache) { - return new DefaultRegion($cache['region'], $this->cache, array( + return new DefaultRegion($cache['region'], clone $this->cache, array( 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($cache['region']) )); } diff --git a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php index 9e1841bd6bb..0b62c5b8124 100644 --- a/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php +++ b/lib/Doctrine/ORM/Cache/Region/DefaultRegion.php @@ -22,9 +22,9 @@ use Doctrine\ORM\Cache\Lock; use Doctrine\ORM\Cache\Region; -use Doctrine\Common\Cache\Cache; use Doctrine\ORM\Cache\CacheKey; use Doctrine\ORM\Cache\CacheEntry; +use Doctrine\Common\Cache\CacheProvider; /** * The simplest cache region compatible with all doctrine-cache drivers. @@ -37,7 +37,7 @@ class DefaultRegion implements Region const ENTRY_KEY = '_entry_'; /** - * @var \Doctrine\Common\Cache\Cache + * @var \Doctrine\Common\Cache\CacheProvider */ private $cache; @@ -46,26 +46,22 @@ class DefaultRegion implements Region */ private $name; - /** - * @var string - */ - private $mapKey; - /** * @var integer */ private $lifetime = 0; /** - * @param string $name - * @param \Doctrine\Common\Cache\Cache $cache - * @param array $configuration + * @param string $name + * @param \Doctrine\Common\Cache\CacheProvider $cache + * @param array $configuration */ - public function __construct($name, Cache $cache, array $configuration = array()) + public function __construct($name, CacheProvider $cache, array $configuration = array()) { $this->name = $name; $this->cache = $cache; - $this->mapKey = "{$this->name}_map"; + + $this->cache->setNamespace($this->name); if (isset($configuration['lifetime']) && $configuration['lifetime'] > 0) { $this->lifetime = (integer) $configuration['lifetime']; @@ -109,18 +105,7 @@ public function get(CacheKey $key) */ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) { - $entryKey = $this->name . self::ENTRY_KEY . $key->hash; - $entries = $this->cache->fetch($this->mapKey); - - $entries[$entryKey] = 1; - - if ($this->cache->save($entryKey, $entry, $this->lifetime)) { - $this->cache->save($this->mapKey, $entries); - - return true; - } - - return false; + return $this->cache->save($this->name . self::ENTRY_KEY . $key->hash, $entry, $this->lifetime); } /** @@ -128,19 +113,7 @@ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) */ public function evict(CacheKey $key) { - $entryKey = $this->name . self::ENTRY_KEY . $key->hash; - $entries = $this->cache->fetch($this->mapKey); - - if ($this->cache->delete($entryKey)) { - - unset($entries[$entryKey]); - - $this->cache->save($this->mapKey, $entries); - - return true; - } - - return false; + return $this->cache->delete($this->name . self::ENTRY_KEY . $key->hash); } /** @@ -148,20 +121,6 @@ public function evict(CacheKey $key) */ public function evictAll() { - $entries = $this->cache->fetch($this->mapKey); - - if ( ! is_array($entries) || empty($entries)) { - return true; - } - - foreach ($entries as $entryKey => $value) { - if ($this->cache->delete($entryKey)) { - unset($entries[$entryKey]); - } - } - - $this->cache->save($this->mapKey, $entries); - - return empty($entries); + return $this->cache->deleteAll(); } } diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php index 42d68ced926..daf40424ecf 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php @@ -4,12 +4,12 @@ use \Doctrine\Tests\OrmTestCase; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Cache\DefaultCacheFactory; use Doctrine\ORM\Cache\Region\DefaultRegion; use Doctrine\Tests\Mocks\ConcurrentRegionMock; use Doctrine\ORM\Persisters\BasicEntityPersister; use Doctrine\ORM\Persisters\OneToManyPersister; - /** * @group DDC-2183 */ @@ -175,6 +175,44 @@ public function testBuildCachedCollectionPersisterNonStrictReadWrite() $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedCollectionPersister', $cachedPersister); } + public function testInheritedEntityCacheRegion() + { + $em = $this->em; + $metadata1 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\AttractionContactInfo'); + $metadata2 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\AttractionLocationInfo'); + $persister1 = new BasicEntityPersister($em, $metadata1); + $persister2 = new BasicEntityPersister($em, $metadata2); + $factory = new DefaultCacheFactory($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl()); + + $cachedPersister1 = $factory->buildCachedEntityPersister($em, $persister1, $metadata1); + $cachedPersister2 = $factory->buildCachedEntityPersister($em, $persister2, $metadata2); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister1); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister2); + + $this->assertNotSame($cachedPersister1, $cachedPersister2); + $this->assertSame($cachedPersister1->getCacheRegion(), $cachedPersister2->getCacheRegion()); + } + + public function testCreateNewCacheDriver() + { + $em = $this->em; + $metadata1 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\State'); + $metadata2 = clone $em->getClassMetadata('Doctrine\Tests\Models\Cache\City'); + $persister1 = new BasicEntityPersister($em, $metadata1); + $persister2 = new BasicEntityPersister($em, $metadata2); + $factory = new DefaultCacheFactory($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl()); + + $cachedPersister1 = $factory->buildCachedEntityPersister($em, $persister1, $metadata1); + $cachedPersister2 = $factory->buildCachedEntityPersister($em, $persister2, $metadata2); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister1); + $this->assertInstanceOf('Doctrine\ORM\Cache\Persister\CachedEntityPersister', $cachedPersister2); + + $this->assertNotSame($cachedPersister1, $cachedPersister2); + $this->assertNotSame($cachedPersister1->getCacheRegion(), $cachedPersister2->getCacheRegion()); + } + /** * @expectedException InvalidArgumentException * @expectedExceptionMessage Unrecognized access strategy type [-1] diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php index aa218023e14..842db676e52 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -84,4 +84,30 @@ public function testEvictAll() $this->assertFalse($this->region->contains($key1)); $this->assertFalse($this->region->contains($key2)); } + + public function testSharedRegion() + { + if ( ! extension_loaded('apc') || false === @apc_cache_info()) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of APC'); + } + + $key = new CacheKeyMock('key'); + $entry = new CacheEntryMock(array('value' => 'foo')); + $region1 = new DefaultRegion('region1', new \Doctrine\Common\Cache\ApcCache()); + $region2 = new DefaultRegion('region2', new \Doctrine\Common\Cache\ApcCache()); + + $this->assertFalse($region1->contains($key)); + $this->assertFalse($region2->contains($key)); + + $region1->put($key, $entry); + $region2->put($key, $entry); + + $this->assertTrue($region1->contains($key)); + $this->assertTrue($region2->contains($key)); + + $region1->evictAll(); + + $this->assertFalse($region1->contains($key)); + $this->assertTrue($region2->contains($key)); + } } \ No newline at end of file From a1d5f96e0bc61019f0e7b208e60ceb16e87257c8 Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 26 Sep 2013 15:56:39 -0400 Subject: [PATCH 68/71] Small performance improvements --- lib/Doctrine/ORM/Cache/CollectionCacheEntry.php | 8 ++++++++ lib/Doctrine/ORM/Cache/CollectionCacheKey.php | 2 +- lib/Doctrine/ORM/Cache/EntityCacheEntry.php | 8 ++++++++ lib/Doctrine/ORM/Cache/EntityCacheKey.php | 2 +- lib/Doctrine/ORM/Cache/QueryCacheEntry.php | 13 +++++++++++-- .../Tests/ORM/Performance/SecondLevelCacheTest.php | 2 +- 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php index 5f77ac30236..4bb329a7e62 100644 --- a/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/CollectionCacheEntry.php @@ -40,4 +40,12 @@ public function __construct(array $identifiers) { $this->identifiers = $identifiers; } + + /** + * @param array $values + */ + public static function __set_state(array $values) + { + return new self($values['identifiers']); + } } diff --git a/lib/Doctrine/ORM/Cache/CollectionCacheKey.php b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php index e2c74355520..184fa9bf2a5 100644 --- a/lib/Doctrine/ORM/Cache/CollectionCacheKey.php +++ b/lib/Doctrine/ORM/Cache/CollectionCacheKey.php @@ -55,6 +55,6 @@ public function __construct($entityClass, $association, array $ownerIdentifier) $this->entityClass = $entityClass; $this->association = $association; $this->ownerIdentifier = $ownerIdentifier; - $this->hash = sprintf('%s[%s].%s', str_replace('\\', '.', strtolower($entityClass)), implode(' ', $ownerIdentifier), $association); + $this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association; } } diff --git a/lib/Doctrine/ORM/Cache/EntityCacheEntry.php b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php index 9693ebe23b9..73ce222b3c9 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheEntry.php @@ -47,4 +47,12 @@ public function __construct($class, array $data) $this->class = $class; $this->data = $data; } + + /** + * @param array $values + */ + public static function __set_state(array $values) + { + return new self($values['class'], $values['data']); + } } diff --git a/lib/Doctrine/ORM/Cache/EntityCacheKey.php b/lib/Doctrine/ORM/Cache/EntityCacheKey.php index 86b8855ee9f..7729691a6b0 100644 --- a/lib/Doctrine/ORM/Cache/EntityCacheKey.php +++ b/lib/Doctrine/ORM/Cache/EntityCacheKey.php @@ -48,6 +48,6 @@ public function __construct($entityClass, array $identifier) $this->identifier = $identifier; $this->entityClass = $entityClass; - $this->hash = sprintf('%s[%s]', str_replace('\\', '.', strtolower($entityClass)) , implode(' ', $identifier)); + $this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier)); } } diff --git a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php index 672af24e549..46e0d603f0b 100644 --- a/lib/Doctrine/ORM/Cache/QueryCacheEntry.php +++ b/lib/Doctrine/ORM/Cache/QueryCacheEntry.php @@ -40,10 +40,19 @@ class QueryCacheEntry implements CacheEntry /** * @param array $result + * @param integer $time */ - public function __construct($result) + public function __construct($result, $time = null) { $this->result = $result; - $this->time = time(); + $this->time = $time ?: time(); + } + + /** + * @param array $values + */ + public static function __set_state(array $values) + { + return new self($values['result'], $values['time']); } } diff --git a/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php index 72eed788714..cc250d5e43e 100644 --- a/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php @@ -52,7 +52,7 @@ public function testFindEntityWithoutCache() $this->findEntity($em, __FUNCTION__); - $this->assertEquals(5502, $this->countQuery($em)); + $this->assertEquals(6002, $this->countQuery($em)); } public function testFindEntityWithCache() From 4fe697af182f526d0f0791cdd62d9db0dbb04f7b Mon Sep 17 00:00:00 2001 From: fabios Date: Thu, 26 Sep 2013 17:23:36 -0400 Subject: [PATCH 69/71] Fix doc layout --- docs/en/reference/second-level-cache.rst | 62 +++++++++---------- lib/Doctrine/ORM/Cache/CacheFactory.php | 2 +- .../AbstractCollectionPersisterTest.php | 1 + .../Persister/AbstractEntityPersisterTest.php | 1 + .../ORM/Performance/SecondLevelCacheTest.php | 5 +- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/docs/en/reference/second-level-cache.rst b/docs/en/reference/second-level-cache.rst index bfe8ad28b81..0863bbb42ad 100644 --- a/docs/en/reference/second-level-cache.rst +++ b/docs/en/reference/second-level-cache.rst @@ -186,25 +186,25 @@ Caching mode Built-in cached persisters -~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~ Cached persisters are responsible to access cache regions. -+-----------------------+---------------------------------------------------------------------------+ -| Cache Usage | Persister | -+=======================+===========================================================================+ -| READ_ONLY | Doctrine\ORM\Cache\Persister\ReadOnlyCachedEntityPersister | -+-----------------------+---------------------------------------------------------------------------+ -| READ_WRITE | Doctrine\ORM\Cache\Persister\ReadWriteCachedEntityPersister | -+-----------------------+---------------------------------------------------------------------------+ -| NONSTRICT_READ_WRITE | Doctrine\ORM\Cache\Persister\NonStrictReadWriteCachedEntityPersister | -+-----------------------+---------------------------------------------------------------------------+ -| READ_ONLY | Doctrine\ORM\Cache\Persister\ReadOnlyCachedCollectionPersister | -+-----------------------+---------------------------------------------------------------------------+ -| READ_WRITE | Doctrine\ORM\Cache\Persister\ReadWriteCachedCollectionPersister | -+-----------------------+---------------------------------------------------------------------------+ -| NONSTRICT_READ_WRITE | Doctrine\ORM\Cache\Persister\NonStrictReadWriteCacheCollectionPersister | -+-----------------------+---------------------------------------------------------------------------+ + +-----------------------+-------------------------------------------------------------------------------+ + | Cache Usage | Persister | + +=======================+===============================================================================+ + | READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedEntityPersister | + +-----------------------+-------------------------------------------------------------------------------+ + | READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedEntityPersister | + +-----------------------+-------------------------------------------------------------------------------+ + | NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCachedEntityPersister | + +-----------------------+-------------------------------------------------------------------------------+ + | READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedCollectionPersister | + +-----------------------+-------------------------------------------------------------------------------+ + | READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedCollectionPersister | + +-----------------------+-------------------------------------------------------------------------------+ + | NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCacheCollectionPersister | + +-----------------------+-------------------------------------------------------------------------------+ Configuration ------------- @@ -221,8 +221,8 @@ To Enable the cache second-level-cache you should provide a cache factory getSharedSecondLevelCacheDriverImpl()->flushAll(); $this->enableSecondLevelCache(); parent::setUp(); diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php index 753a79f0817..ed4093a88af 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/AbstractEntityPersisterTest.php @@ -88,6 +88,7 @@ abstract protected function createPersister(EntityManager $em, EntityPersister $ protected function setUp() { + $this->getSharedSecondLevelCacheDriverImpl()->flushAll(); $this->enableSecondLevelCache(); parent::setUp(); diff --git a/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php index cc250d5e43e..3e628acbd80 100644 --- a/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Performance/SecondLevelCacheTest.php @@ -265,14 +265,17 @@ private function findAllEntity(EntityManagerInterface $em, $label) } $em->flush(); + $em->clear(); printf("\n[%s] persist %s countries", number_format(microtime(true) - $startPersist, 6), $size); $startFind = microtime(true); for ($i = 0; $i <= $times; $i++) { - $rep->findAll(); + $list = $rep->findAll(); $em->clear(); + + $this->assertCount($size, $list); } printf("\n[%s] find %s countries (%s times)", number_format(microtime(true) - $startFind, 6), $size, $times); From c7267f054615d485495dde239d7b05a6a324138d Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 30 Sep 2013 12:54:06 -0400 Subject: [PATCH 70/71] Change CacheFactory interface --- docs/en/reference/second-level-cache.rst | 25 +++++++--- lib/Doctrine/ORM/Cache/CacheFactory.php | 21 +++++++-- .../ORM/Cache/DefaultCacheFactory.php | 47 ++++++------------- .../Persister/AbstractCollectionPersister.php | 2 +- .../Persister/AbstractEntityPersister.php | 2 +- .../ORM/Cache/DefaultCacheFactoryTest.php | 14 +++--- .../Tests/ORM/Cache/DefaultQueryCacheTest.php | 2 +- .../SecondLevelCacheConcurrentTest.php | 2 +- 8 files changed, 60 insertions(+), 55 deletions(-) diff --git a/docs/en/reference/second-level-cache.rst b/docs/en/reference/second-level-cache.rst index 0863bbb42ad..50ce9409afa 100644 --- a/docs/en/reference/second-level-cache.rst +++ b/docs/en/reference/second-level-cache.rst @@ -215,7 +215,7 @@ Enable Second Level Cache Enabled ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To Enable the cache second-level-cache you should provide a cache factory -``\Doctrine\ORM\Cache\DefaultCacheFactory`` its the default implementation. +``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation. .. code-block:: php @@ -287,20 +287,31 @@ It allows you to provide a specific implementation of the following components : /** * Build an entity hidrator * - * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * - * @return \Doctrine\ORM\Cache\EntityHydrator The built entity hidrator + * @return \Doctrine\ORM\Cache\EntityHydrator The built entity hidrator. */ - public function buildEntityHydrator(EntityManagerInterface $em); + public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata); /** * Build a collection hidrator * - * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param array $mapping The association mapping. * - * @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hidrator + * @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hidrator. */ - public function buildCollectionHydrator(EntityManagerInterface $em); + public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping); + + /** + * Gets a cache region based on its name. + * + * @param array $cache The cache configuration. + * + * @return \Doctrine\ORM\Cache\Region The cache region. + */ + public function getRegion(array $cache); } Region Lifetime diff --git a/lib/Doctrine/ORM/Cache/CacheFactory.php b/lib/Doctrine/ORM/Cache/CacheFactory.php index bf05a0c120b..89d54d43f3f 100644 --- a/lib/Doctrine/ORM/Cache/CacheFactory.php +++ b/lib/Doctrine/ORM/Cache/CacheFactory.php @@ -52,7 +52,7 @@ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPer * * @return \Doctrine\ORM\Cache\Persister\CachedCollectionPersister */ - public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, $mapping); + public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping); /** * Build a query cache based on the given region name @@ -67,18 +67,29 @@ public function buildQueryCache(EntityManagerInterface $em, $regionName = null); /** * Build an entity hidrator * - * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata. * * @return \Doctrine\ORM\Cache\EntityHydrator The built entity hidrator. */ - public function buildEntityHydrator(EntityManagerInterface $em); + public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata); /** * Build a collection hidrator * - * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param \Doctrine\ORM\EntityManagerInterface $em The Entity manager. + * @param array $mapping The association mapping. * * @return \Doctrine\ORM\Cache\CollectionHydrator The built collection hidrator. */ - public function buildCollectionHydrator(EntityManagerInterface $em); + public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping); + + /** + * Build a cache region + * + * @param array $cache The cache configuration. + * + * @return \Doctrine\ORM\Cache\Region The cache region. + */ + public function getRegion(array $cache); } diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index 237e54b4339..7f421b251bd 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -72,13 +72,7 @@ public function __construct(Configuration $configuration, CacheDriver $cache) */ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata) { - $role = $metadata->rootEntityName; - - if ( ! isset($this->regions[$role])) { - $this->regions[$role] = $this->createRegion($metadata->cache); - } - - $region = $this->regions[$role]; + $region = $this->getRegion($metadata->cache); $usage = $metadata->cache['usage']; if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { @@ -99,16 +93,10 @@ public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPer /** * {@inheritdoc} */ - public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, $mapping) + public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping) { - $role = $mapping['sourceEntity'] . '::' . $mapping['fieldName']; - - if ( ! isset($this->regions[$role])) { - $this->regions[$role] = $this->createRegion($mapping['cache']); - } - $usage = $mapping['cache']['usage']; - $region = $this->regions[$role]; + $region = $this->getRegion($mapping['cache']); if ($usage === ClassMetadata::CACHE_USAGE_READ_ONLY) { return new ReadOnlyCachedCollectionPersister($persister, $region, $em, $mapping); @@ -130,22 +118,15 @@ public function buildCachedCollectionPersister(EntityManagerInterface $em, Colle */ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { - $regionName = $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME; - - if ( ! isset($this->regions[$regionName])) { - $this->regions[$regionName] = $this->createRegion(array( - 'region' => $regionName, - 'usage' => ClassMetadata::CACHE_USAGE_READ_ONLY, - )); - } - - return new DefaultQueryCache($em, $this->regions[$regionName]); + return new DefaultQueryCache($em, $this->getRegion(array( + 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME + ))); } /** * {@inheritdoc} */ - public function buildCollectionHydrator(EntityManagerInterface $em) + public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping) { return new DefaultCollectionHydrator($em); } @@ -153,19 +134,21 @@ public function buildCollectionHydrator(EntityManagerInterface $em) /** * {@inheritdoc} */ - public function buildEntityHydrator(EntityManagerInterface $em) + public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata) { return new DefaultEntityHydrator($em); } /** - * @param array $cache - * - * @return \Doctrine\ORM\Cache\Region\DefaultRegion + * {@inheritdoc} */ - protected function createRegion(array $cache) + public function getRegion(array $cache) { - return new DefaultRegion($cache['region'], clone $this->cache, array( + if (isset($this->regions[$cache['region']])) { + return $this->regions[$cache['region']]; + } + + return $this->regions[$cache['region']] = new DefaultRegion($cache['region'], clone $this->cache, array( 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($cache['region']) )); } diff --git a/lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php index cd85779a8dd..23a24994035 100644 --- a/lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/AbstractCollectionPersister.php @@ -107,7 +107,7 @@ public function __construct(CollectionPersister $persister, Region $region, Enti $this->uow = $em->getUnitOfWork(); $this->metadataFactory = $em->getMetadataFactory(); $this->cacheLogger = $configuration->getSecondLevelCacheLogger(); - $this->hidrator = $cacheFactory->buildCollectionHydrator($em); + $this->hidrator = $cacheFactory->buildCollectionHydrator($em, $association); $this->sourceEntity = $em->getClassMetadata($association['sourceEntity']); $this->targetEntity = $em->getClassMetadata($association['targetEntity']); } diff --git a/lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php index 7360e88b8cb..25e4bd55901 100644 --- a/lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/AbstractEntityPersister.php @@ -108,7 +108,7 @@ public function __construct(EntityPersister $persister, Region $region, EntityMa $this->uow = $em->getUnitOfWork(); $this->metadataFactory = $em->getMetadataFactory(); $this->cacheLogger = $config->getSecondLevelCacheLogger(); - $this->hidrator = $factory->buildEntityHydrator($em); + $this->hidrator = $factory->buildEntityHydrator($em, $class); } /** diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php index daf40424ecf..88157e0120a 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php @@ -35,7 +35,7 @@ protected function setUp() $arguments = array($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl()); $this->factory = $this->getMock('\Doctrine\ORM\Cache\DefaultCacheFactory', array( - 'createRegion' + 'getRegion' ), $arguments); } @@ -55,7 +55,7 @@ public function testBuildCachedEntityPersisterReadOnly() $metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_READ_ONLY; $this->factory->expects($this->once()) - ->method('createRegion') + ->method('getRegion') ->with($this->equalTo($metadata->cache)) ->will($this->returnValue($region)); @@ -77,7 +77,7 @@ public function testBuildCachedEntityPersisterReadWrite() $metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_READ_WRITE; $this->factory->expects($this->once()) - ->method('createRegion') + ->method('getRegion') ->with($this->equalTo($metadata->cache)) ->will($this->returnValue($region)); @@ -98,7 +98,7 @@ public function testBuildCachedEntityPersisterNonStrictReadWrite() $metadata->cache['usage'] = ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE; $this->factory->expects($this->once()) - ->method('createRegion') + ->method('getRegion') ->with($this->equalTo($metadata->cache)) ->will($this->returnValue($region)); @@ -120,7 +120,7 @@ public function testBuildCachedCollectionPersisterReadOnly() $mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_READ_ONLY; $this->factory->expects($this->once()) - ->method('createRegion') + ->method('getRegion') ->with($this->equalTo($mapping['cache'])) ->will($this->returnValue($region)); @@ -143,7 +143,7 @@ public function testBuildCachedCollectionPersisterReadWrite() $mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_READ_WRITE; $this->factory->expects($this->once()) - ->method('createRegion') + ->method('getRegion') ->with($this->equalTo($mapping['cache'])) ->will($this->returnValue($region)); @@ -165,7 +165,7 @@ public function testBuildCachedCollectionPersisterNonStrictReadWrite() $mapping['cache']['usage'] = ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE; $this->factory->expects($this->once()) - ->method('createRegion') + ->method('getRegion') ->with($this->equalTo($mapping['cache'])) ->will($this->returnValue($region)); diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php index b30ee9ccd98..8cc1c54ef40 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultQueryCacheTest.php @@ -519,7 +519,7 @@ public function buildQueryCache(EntityManagerInterface $em, $regionName = null) return $this->queryCache; } - protected function createRegion(array $cache) + public function getRegion(array $cache) { return $this->region; } diff --git a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php index 5485471d5d4..ed35992c8eb 100644 --- a/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/SecondLevelCacheConcurrentTest.php @@ -129,7 +129,7 @@ public function __construct(Cache $cache) $this->cache = $cache; } - protected function createRegion(array $cache) + public function getRegion(array $cache) { $region = new DefaultRegion($cache['region'], $this->cache); $mock = new ConcurrentRegionMock($region); From 0cc72cd54cc034763bdd10c0868618ebf61efb53 Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 30 Sep 2013 18:01:09 -0400 Subject: [PATCH 71/71] FileLockRegion and some docs update --- docs/en/reference/second-level-cache.rst | 56 +++- lib/Doctrine/ORM/Cache/ConcurrentRegion.php | 8 +- .../ORM/Cache/DefaultCacheFactory.php | 42 ++- lib/Doctrine/ORM/Cache/Lock.php | 5 - .../ReadWriteCachedCollectionPersister.php | 4 +- .../ReadWriteCachedEntityPersister.php | 4 +- .../ORM/Cache/Region/FileLockRegion.php | 245 +++++++++++++++++ lib/Doctrine/ORM/Configuration.php | 18 ++ .../Tests/Mocks/ConcurrentRegionMock.php | 4 +- .../Tests/ORM/Cache/AbstractRegionTest.php | 85 ++++++ .../ORM/Cache/DefaultCacheFactoryTest.php | 14 + .../Tests/ORM/Cache/DefaultRegionTest.php | 69 +---- .../Tests/ORM/Cache/FileLockRegionTest.php | 251 ++++++++++++++++++ ...ReadWriteCachedCollectionPersisterTest.php | 24 +- .../ReadWriteCachedEntityPersisterTest.php | 20 +- 15 files changed, 745 insertions(+), 104 deletions(-) create mode 100644 lib/Doctrine/ORM/Cache/Region/FileLockRegion.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/AbstractRegionTest.php create mode 100644 tests/Doctrine/Tests/ORM/Cache/FileLockRegionTest.php diff --git a/docs/en/reference/second-level-cache.rst b/docs/en/reference/second-level-cache.rst index 50ce9409afa..de930e6e100 100644 --- a/docs/en/reference/second-level-cache.rst +++ b/docs/en/reference/second-level-cache.rst @@ -64,6 +64,11 @@ A query region might be something like : The entity values will be stored in its own region +.. _reference-second-level-cache-regions: + +Cache Regions +------------- + ``Doctrine\ORM\Cache\Region\DefaultRegion`` Its the default implementation. A simplest cache region compatible with all doctrine-cache drivers but does not support locking. @@ -745,4 +750,53 @@ however, you can use the cache API to check / invalidate cache entries. $cache->evictCollectionRegion('State', 'cities'); // Remove all collections from cache Limitations ------------ \ No newline at end of file +----------- + +Composite primary key +~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + Composite primary key are supported by second level cache, + However when one of the keys is an association + the cached entity should always be retrieved using the association identifier. + +.. code-block:: php + + 1, 'target' => 2); + $reference = $this->_em->find("Reference", $id); + + // NOT Supported + $id = array('source' => new Article(1), 'target' => new Article(2)); + $reference = $this->_em->find("Reference", $id); + + +Concurrent cache region +~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Doctrine\\ORM\\Cache\\ConcurrentRegion`` is designed to store concurrently managed data region. +However doctrine provide an very simple implementation based on file locks ``Doctrine\\ORM\\Cache\\Region\\FileLockRegion``. + +If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region. +for more details about how to implement a cache region please see :ref:`reference-second-level-cache-regions` \ No newline at end of file diff --git a/lib/Doctrine/ORM/Cache/ConcurrentRegion.php b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php index a3c558686ed..7bb50086ba5 100644 --- a/lib/Doctrine/ORM/Cache/ConcurrentRegion.php +++ b/lib/Doctrine/ORM/Cache/ConcurrentRegion.php @@ -24,6 +24,10 @@ /** * Defines contract for concurrently managed data region. + * It should be able to lock an specific cache entry in an atomic operation. + * + * When a entry is locked another process should not be able to read or write the entry. + * All evict operation should not consider locks, even though an entry is locked evict should be able to delete the entry and its lock. * * @since 2.5 * @author Fabio B. Silva @@ -39,7 +43,7 @@ interface ConcurrentRegion extends Region * * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. */ - public function readLock(CacheKey $key); + public function lock(CacheKey $key); /** * Attempts to read unlock the mapping for the given key. @@ -51,5 +55,5 @@ public function readLock(CacheKey $key); * * @throws \Doctrine\ORM\Cache\LockException Indicates a problem accessing the region. */ - public function readUnlock(CacheKey $key, Lock $lock); + public function unlock(CacheKey $key, Lock $lock); } diff --git a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php index 7f421b251bd..bb6a90901c8 100644 --- a/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php +++ b/lib/Doctrine/ORM/Cache/DefaultCacheFactory.php @@ -25,6 +25,7 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Cache\Region\DefaultRegion; +use Doctrine\ORM\Cache\Region\FileLockRegion; use Doctrine\Common\Cache\Cache as CacheDriver; use Doctrine\ORM\Persisters\EntityPersister; @@ -57,6 +58,11 @@ class DefaultCacheFactory implements CacheFactory */ private $regions; + /** + * @var string + */ + private $fileLockRegionDirectory; + /** * @param \Doctrine\ORM\Configuration $configuration * @param \Doctrine\Common\Cache\Cache $cache @@ -67,6 +73,22 @@ public function __construct(Configuration $configuration, CacheDriver $cache) $this->configuration = $configuration; } + /** + * @param string $fileLockRegionDirectory + */ + public function setFileLockRegionDirectory($fileLockRegionDirectory) + { + $this->fileLockRegionDirectory = $fileLockRegionDirectory; + } + + /** + * @return string + */ + public function getFileLockRegionDirectory() + { + return $this->fileLockRegionDirectory; + } + /** * {@inheritdoc} */ @@ -119,7 +141,8 @@ public function buildCachedCollectionPersister(EntityManagerInterface $em, Colle public function buildQueryCache(EntityManagerInterface $em, $regionName = null) { return new DefaultQueryCache($em, $this->getRegion(array( - 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME + 'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME, + 'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE ))); } @@ -148,8 +171,23 @@ public function getRegion(array $cache) return $this->regions[$cache['region']]; } - return $this->regions[$cache['region']] = new DefaultRegion($cache['region'], clone $this->cache, array( + $region = new DefaultRegion($cache['region'], clone $this->cache, array( 'lifetime' => $this->configuration->getSecondLevelCacheRegionLifetime($cache['region']) )); + + if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) { + + if ( ! $this->fileLockRegionDirectory) { + throw new \RuntimeException( + 'To use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' . + 'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you what to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). ' + ); + } + + $directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region']; + $region = new FileLockRegion($region, $directory, $this->configuration->getSecondLevelCacheLockLifetime()); + } + + return $this->regions[$cache['region']] = $region; } } diff --git a/lib/Doctrine/ORM/Cache/Lock.php b/lib/Doctrine/ORM/Cache/Lock.php index 490b04d4a09..5346aa32371 100644 --- a/lib/Doctrine/ORM/Cache/Lock.php +++ b/lib/Doctrine/ORM/Cache/Lock.php @@ -38,11 +38,6 @@ class Lock */ public $time; - /** - * @var integer - */ - public $type; - /** * @param string $value * @param integer $time diff --git a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php index 74f9ba44e64..573257bdd59 100644 --- a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedCollectionPersister.php @@ -96,7 +96,7 @@ public function delete(PersistentCollection $collection) { $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - $lock = $this->region->readLock($key); + $lock = $this->region->lock($key); $this->persister->delete($collection); @@ -126,7 +126,7 @@ public function update(PersistentCollection $collection) $ownerId = $this->uow->getEntityIdentifier($collection->getOwner()); $key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association['fieldName'], $ownerId); - $lock = $this->region->readLock($key); + $lock = $this->region->lock($key); if ($lock === null) { return; diff --git a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php index c4031fff078..e49c225411a 100644 --- a/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php +++ b/lib/Doctrine/ORM/Cache/Persister/ReadWriteCachedEntityPersister.php @@ -97,7 +97,7 @@ public function afterTransactionRolledBack() public function delete($entity) { $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); - $lock = $this->region->readLock($key); + $lock = $this->region->lock($key); $this->persister->delete($entity); @@ -117,7 +117,7 @@ public function delete($entity) public function update($entity) { $key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity)); - $lock = $this->region->readLock($key); + $lock = $this->region->lock($key); $this->persister->update($entity); diff --git a/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php b/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php new file mode 100644 index 00000000000..680f1940be6 --- /dev/null +++ b/lib/Doctrine/ORM/Cache/Region/FileLockRegion.php @@ -0,0 +1,245 @@ +. + */ + +namespace Doctrine\ORM\Cache\Region; + +use Doctrine\ORM\Cache\Lock; +use Doctrine\ORM\Cache\Region; +use Doctrine\ORM\Cache\CacheKey; +use Doctrine\ORM\Cache\CacheEntry; +use Doctrine\ORM\Cache\ConcurrentRegion; + +/** + * Very naive concurrent region, based on file locks. + * + * since 2.5 + * author Fabio B. Silva + */ +class FileLockRegion implements ConcurrentRegion +{ + const LOCK_EXTENSION = 'lock'; + + /** + * var \Doctrine\ORM\Cache\Region + */ + private $region; + + /** + * var string + */ + private $directory; + + /** + * var integer + */ + private $lockLifetime; + + /** + * @param \Doctrine\ORM\Cache\Region $region + * @param string $directory + * @param string $lockLifetime + * + * @throws \InvalidArgumentException + */ + public function __construct(Region $region, $directory, $lockLifetime) + { + if ( ! is_dir($directory) && ! @mkdir($directory, 0777, true)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $directory)); + } + + if ( ! is_writable($directory)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable.', $directory)); + } + + $this->region = $region; + $this->directory = $directory; + $this->lockLifetime = $lockLifetime; + } + + /** + * param \Doctrine\ORM\Cache\CacheKey $key + * param \Doctrine\ORM\Cache\Lock $lock + * + * return boolean + */ + private function isLoked(CacheKey $key, Lock $lock = null) + { + $filename = $this->getLockFileName($key); + + if ( ! is_file($filename)) { + return false; + } + + $time = $this->getLockTime($filename); + $content = $this->getLockContent($filename); + + if ( ! $content || ! $time) { + @unlink($filename); + + return false; + } + + if ($lock && $content === $lock->value) { + return false; + } + + // outdated lock + if (($time + $this->lockLifetime) <= time()) { + @unlink($filename); + + return false; + } + + return true; + } + + /** + * @param \Doctrine\ORM\Cache\CacheKey $key + * + * return string + */ + private function getLockFileName(CacheKey $key) + { + return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION; + } + + /** + * @param string $filename + * + * return string + */ + private function getLockContent($filename) + { + return @file_get_contents($filename); + } + + /** + * @param string $filename + * + * return integer + */ + private function getLockTime($filename) + { + return @fileatime($filename); + } + + /** + * {inheritdoc} + */ + public function getName() + { + return $this->region->getName(); + } + + /** + * {inheritdoc} + */ + public function contains(CacheKey $key) + { + if ($this->isLoked($key)) { + return false; + } + + return $this->region->contains($key); + } + + /** + * {inheritdoc} + */ + public function get(CacheKey $key) + { + if ($this->isLoked($key)) { + return null; + } + + return $this->region->get($key); + } + + /** + * {inheritdoc} + */ + public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) + { + if ($this->isLoked($key, $lock)) { + return false; + } + + return $this->region->put($key, $entry); + } + + /** + * {inheritdoc} + */ + public function evict(CacheKey $key) + { + if ($this->isLoked($key)) { + @unlink($this->getLockFileName($key)); + } + + return $this->region->evict($key); + } + + /** + * {inheritdoc} + */ + public function evictAll() + { + foreach (glob(sprintf("%s/*.%s" , $this->directory, self::LOCK_EXTENSION)) as $filename) { + @unlink($filename); + } + + return $this->region->evictAll(); + } + + /** + * {inheritdoc} + */ + public function lock(CacheKey $key) + { + if ($this->isLoked($key)) { + return null; + } + + $lock = Lock::createLockRead(); + $filename = $this->getLockFileName($key); + + if ( ! @file_put_contents($filename, $lock->value, LOCK_EX)) { + return null; + } + + return $lock; + } + + /** + * {inheritdoc} + */ + public function unlock(CacheKey $key, Lock $lock) + { + if ($this->isLoked($key, $lock)) { + return false; + } + + if ( ! @unlink($this->getLockFileName($key))) { + return false; + } + + return true; + } +} diff --git a/lib/Doctrine/ORM/Configuration.php b/lib/Doctrine/ORM/Configuration.php index 1e1f45d5689..6496957a3b6 100644 --- a/lib/Doctrine/ORM/Configuration.php +++ b/lib/Doctrine/ORM/Configuration.php @@ -319,6 +319,24 @@ public function setSecondLevelCacheDefaultRegionLifetime($lifetime) $this->_attributes['secondLevelCacheDefaultRegionLifetime'] = (integer) $lifetime; } + /** + * @param integer $lifetime + */ + public function setSecondLevelCacheLockLifetime($lifetime) + { + $this->_attributes['secondLevelCacheLockLifetime'] = (integer) $lifetime; + } + + /** + * @return integer + */ + public function getSecondLevelCacheLockLifetime() + { + return isset($this->_attributes['secondLevelCacheLockLifetime']) + ? $this->_attributes['secondLevelCacheLockLifetime'] + : 60; + } + /** * @return \Doctrine\ORM\Cache\Logging\CacheLogger|null */ diff --git a/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php index 2cd7da55af9..a84672bea7a 100644 --- a/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php +++ b/tests/Doctrine/Tests/Mocks/ConcurrentRegionMock.php @@ -118,7 +118,7 @@ public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null) return $this->region->put($key, $entry); } - public function readLock(CacheKey $key) + public function lock(CacheKey $key) { $this->calls[__FUNCTION__][] = array('key' => $key); @@ -131,7 +131,7 @@ public function readLock(CacheKey $key) return $this->locks[$key->hash] = Lock::createLockRead(); } - public function readUnlock(CacheKey $key, Lock $lock) + public function unlock(CacheKey $key, Lock $lock) { $this->calls[__FUNCTION__][] = array('key' => $key, 'lock' => $lock); diff --git a/tests/Doctrine/Tests/ORM/Cache/AbstractRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionTest.php new file mode 100644 index 00000000000..ca01b069d46 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/AbstractRegionTest.php @@ -0,0 +1,85 @@ +cache = new ArrayCache(); + $this->region = $this->createRegion(); + } + + /** + * @return \Doctrine\ORM\Cache\Region + */ + protected abstract function createRegion(); + + static public function dataProviderCacheValues() + { + return array( + array(new CacheKeyMock('key.1'), new CacheEntryMock(array('id'=>1, 'name' => 'bar'))), + array(new CacheKeyMock('key.2'), new CacheEntryMock(array('id'=>2, 'name' => 'foo'))), + ); + } + + /** + * @dataProvider dataProviderCacheValues + */ + public function testPutGetContainsEvict($key, $value) + { + $this->assertFalse($this->region->contains($key)); + + $this->region->put($key, $value); + + $this->assertTrue($this->region->contains($key)); + + $actual = $this->region->get($key); + + $this->assertEquals($value, $actual); + + $this->region->evict($key); + + $this->assertFalse($this->region->contains($key)); + } + + public function testEvictAll() + { + $key1 = new CacheKeyMock('key.1'); + $key2 = new CacheKeyMock('key.2'); + + $this->assertFalse($this->region->contains($key1)); + $this->assertFalse($this->region->contains($key2)); + + $this->region->put($key1, new CacheEntryMock(array('value' => 'foo'))); + $this->region->put($key2, new CacheEntryMock(array('value' => 'bar'))); + + $this->assertTrue($this->region->contains($key1)); + $this->assertTrue($this->region->contains($key2)); + + $this->region->evictAll(); + + $this->assertFalse($this->region->contains($key1)); + $this->assertFalse($this->region->contains($key2)); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php index 88157e0120a..d52196d4473 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultCacheFactoryTest.php @@ -245,4 +245,18 @@ public function testBuildCachedCollectionPersisterException() $this->factory->buildCachedCollectionPersister($em, $persister, $mapping); } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage To use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you what to use it please provide a valid directory + */ + public function testInvalidFileLockRegionDirectoryException() + { + $factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($this->em->getConfiguration(), $this->getSharedSecondLevelCacheDriverImpl()); + + $factory->getRegion(array( + 'usage' => ClassMetadata::CACHE_USAGE_READ_WRITE, + 'region' => 'foo' + )); + } } \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php index 842db676e52..e8f7764b59f 100644 --- a/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/DefaultRegionTest.php @@ -3,32 +3,17 @@ namespace Doctrine\Tests\ORM\Cache; use Doctrine\ORM\Cache\Region\DefaultRegion; -use Doctrine\Tests\OrmFunctionalTestCase; use Doctrine\Tests\Mocks\CacheEntryMock; use Doctrine\Tests\Mocks\CacheKeyMock; -use Doctrine\Common\Cache\ArrayCache; /** * @group DDC-2183 */ -class DefaultRegionTest extends OrmFunctionalTestCase +class DefaultRegionTest extends AbstractRegionTest { - /** - * @var \Doctrine\ORM\Cache\Region\DefaultRegion - */ - private $region; - - /** - * @var \Doctrine\Common\Cache\ArrayCache - */ - private $cache; - - protected function setUp() + protected function createRegion() { - parent::setUp(); - - $this->cache = new ArrayCache(); - $this->region = new DefaultRegion('default.region.test', $this->cache); + return new DefaultRegion('default.region.test', $this->cache); } public function testGetters() @@ -37,54 +22,6 @@ public function testGetters() $this->assertSame($this->cache, $this->region->getCache()); } - static public function dataProviderCacheValues() - { - return array( - array(new CacheKeyMock('key.1'), new CacheEntryMock(array('id'=>1, 'name' => 'bar'))), - array(new CacheKeyMock('key.2'), new CacheEntryMock(array('id'=>2, 'name' => 'foo'))), - ); - } - - /** - * @dataProvider dataProviderCacheValues - */ - public function testPutGetContainsEvict($key, $value) - { - $this->assertFalse($this->region->contains($key)); - - $this->region->put($key, $value); - - $this->assertTrue($this->region->contains($key)); - - $actual = $this->region->get($key); - - $this->assertEquals($value, $actual); - - $this->region->evict($key); - - $this->assertFalse($this->region->contains($key)); - } - - public function testEvictAll() - { - $key1 = new CacheKeyMock('key.1'); - $key2 = new CacheKeyMock('key.2'); - - $this->assertFalse($this->region->contains($key1)); - $this->assertFalse($this->region->contains($key2)); - - $this->region->put($key1, new CacheEntryMock(array('value' => 'foo'))); - $this->region->put($key2, new CacheEntryMock(array('value' => 'bar'))); - - $this->assertTrue($this->region->contains($key1)); - $this->assertTrue($this->region->contains($key2)); - - $this->region->evictAll(); - - $this->assertFalse($this->region->contains($key1)); - $this->assertFalse($this->region->contains($key2)); - } - public function testSharedRegion() { if ( ! extension_loaded('apc') || false === @apc_cache_info()) { diff --git a/tests/Doctrine/Tests/ORM/Cache/FileLockRegionTest.php b/tests/Doctrine/Tests/ORM/Cache/FileLockRegionTest.php new file mode 100644 index 00000000000..94cd3985d11 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Cache/FileLockRegionTest.php @@ -0,0 +1,251 @@ +directory)) { + return; + } + + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->directory), RecursiveIteratorIterator::CHILD_FIRST) as $file) { + $file->isFile() + ? @unlink($file->getRealPath()) + : @rmdir($file->getRealPath()); + } + } + + /** + * @param \Doctrine\ORM\Cache\ConcurrentRegion $region + * @param \Doctrine\ORM\Cache\CacheKey $key + * + * @return string + */ + private function getFileName(ConcurrentRegion $region, CacheKey $key) + { + $reflection = new \ReflectionMethod($region, 'getLockFileName'); + + $reflection->setAccessible(true); + + return $reflection->invoke($region, $key); + } + + protected function createRegion() + { + $this->directory = sys_get_temp_dir() . '/doctrine_lock_'. uniqid(); + + $region = new DefaultRegion('concurren_region_test', $this->cache); + + return new FileLockRegion($region, $this->directory, 60); + } + + public function testGetRegionName() + { + $this->assertEquals('concurren_region_test', $this->region->getName()); + } + + public function testLockAndUnlock() + { + $key = new CacheKeyMock('key'); + $entry = new CacheEntryMock(array('foo' => 'bar')); + $file = $this->getFileName($this->region, $key); + + $this->assertFalse($this->region->contains($key)); + $this->assertTrue($this->region->put($key, $entry)); + $this->assertTrue($this->region->contains($key)); + + $lock = $this->region->lock($key); + + $this->assertFileExists($file); + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock); + $this->assertEquals($lock->value, file_get_contents($file)); + + // should be not available after lock + $this->assertFalse($this->region->contains($key)); + $this->assertNull($this->region->get($key)); + + $this->assertTrue($this->region->unlock($key, $lock)); + $this->assertFileNotExists($file); + } + + public function testLockWithExistingLock() + { + $key = new CacheKeyMock('key'); + $entry = new CacheEntryMock(array('foo' => 'bar')); + $file = $this->getFileName($this->region, $key); + + $this->assertFalse($this->region->contains($key)); + $this->assertTrue($this->region->put($key, $entry)); + $this->assertTrue($this->region->contains($key)); + + file_put_contents($file, 'foo'); + $this->assertFileExists($file); + $this->assertEquals('foo' , file_get_contents($file)); + + $this->assertNull($this->region->lock($key)); + $this->assertEquals('foo' , file_get_contents($file)); + $this->assertFileExists($file); + + // should be not available + $this->assertFalse($this->region->contains($key)); + $this->assertNull($this->region->get($key)); + } + + public function testUnlockWithExistingLock() + { + $key = new CacheKeyMock('key'); + $entry = new CacheEntryMock(array('foo' => 'bar')); + $file = $this->getFileName($this->region, $key); + + $this->assertFalse($this->region->contains($key)); + $this->assertTrue($this->region->put($key, $entry)); + $this->assertTrue($this->region->contains($key)); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock = $this->region->lock($key)); + $this->assertEquals($lock->value, file_get_contents($file)); + $this->assertFileExists($file); + + // change the lock + file_put_contents($file, 'foo'); + $this->assertFileExists($file); + $this->assertEquals('foo' , file_get_contents($file)); + + //try to unlock + $this->assertFalse($this->region->unlock($key, $lock)); + $this->assertEquals('foo' , file_get_contents($file)); + $this->assertFileExists($file); + + // should be not available + $this->assertFalse($this->region->contains($key)); + $this->assertNull($this->region->get($key)); + } + + public function testPutWithExistingLock() + { + $key = new CacheKeyMock('key'); + $entry = new CacheEntryMock(array('foo' => 'bar')); + $file = $this->getFileName($this->region, $key); + + $this->assertFalse($this->region->contains($key)); + $this->assertTrue($this->region->put($key, $entry)); + $this->assertTrue($this->region->contains($key)); + + // create lock + file_put_contents($file, 'foo'); + $this->assertFileExists($file); + $this->assertEquals('foo' , file_get_contents($file)); + + $this->assertFalse($this->region->contains($key)); + $this->assertFalse($this->region->put($key, $entry)); + $this->assertFalse($this->region->contains($key)); + + $this->assertFileExists($file); + $this->assertEquals('foo' , file_get_contents($file)); + } + + public function testLockedEvict() + { + $key = new CacheKeyMock('key'); + $entry = new CacheEntryMock(array('foo' => 'bar')); + $file = $this->getFileName($this->region, $key); + + $this->assertFalse($this->region->contains($key)); + $this->assertTrue($this->region->put($key, $entry)); + $this->assertTrue($this->region->contains($key)); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock = $this->region->lock($key)); + $this->assertEquals($lock->value, file_get_contents($file)); + $this->assertFileExists($file); + + $this->assertFalse($this->region->contains($key)); + $this->assertTrue($this->region->evict($key)); + $this->assertFalse($this->region->contains($key)); + $this->assertFileNotExists($file); + } + + public function testLockedEvictAll() + { + $key1 = new CacheKeyMock('key1'); + $entry1 = new CacheEntryMock(array('foo1' => 'bar1')); + $file1 = $this->getFileName($this->region, $key1); + + $key2 = new CacheKeyMock('key2'); + $entry2 = new CacheEntryMock(array('foo2' => 'bar2')); + $file2 = $this->getFileName($this->region, $key2); + + $this->assertFalse($this->region->contains($key1)); + $this->assertTrue($this->region->put($key1, $entry1)); + $this->assertTrue($this->region->contains($key1)); + + $this->assertFalse($this->region->contains($key2)); + $this->assertTrue($this->region->put($key2, $entry2)); + $this->assertTrue($this->region->contains($key2)); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock1 = $this->region->lock($key1)); + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock2 = $this->region->lock($key2)); + + $this->assertEquals($lock2->value, file_get_contents($file2)); + $this->assertEquals($lock1->value, file_get_contents($file1)); + + $this->assertFileExists($file1); + $this->assertFileExists($file2); + + $this->assertTrue($this->region->evictAll()); + + $this->assertFileNotExists($file1); + $this->assertFileNotExists($file2); + + $this->assertFalse($this->region->contains($key1)); + $this->assertFalse($this->region->contains($key2)); + } + + public function testLockLifetime() + { + $key = new CacheKeyMock('key'); + $entry = new CacheEntryMock(array('foo' => 'bar')); + $file = $this->getFileName($this->region, $key); + $property = new \ReflectionProperty($this->region, 'lockLifetime'); + + $property->setAccessible(true); + $property->setValue($this->region, -10); + + $this->assertFalse($this->region->contains($key)); + $this->assertTrue($this->region->put($key, $entry)); + $this->assertTrue($this->region->contains($key)); + + $this->assertInstanceOf('Doctrine\ORM\Cache\Lock', $lock = $this->region->lock($key)); + $this->assertEquals($lock->value, file_get_contents($file)); + $this->assertFileExists($file); + + // outdated lock should be removed + $this->assertTrue($this->region->contains($key)); + $this->assertNotNull($this->region->get($key)); + $this->assertFileNotExists($file); + } +} \ No newline at end of file diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php index 06051880220..4c70140661a 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedCollectionPersisterTest.php @@ -22,8 +22,8 @@ class ReadWriteCachedCollectionPersisterTest extends AbstractCollectionPersister 'put', 'evict', 'evictAll', - 'readLock', - 'readUnlock', + 'lock', + 'unlock', ); /** @@ -51,7 +51,7 @@ public function testDeleteShouldLockItem() $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -69,7 +69,7 @@ public function testUpdateShouldLockItem() $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -87,7 +87,7 @@ public function testUpdateTransactionRollBackShouldEvictItem() $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -111,7 +111,7 @@ public function testDeleteTransactionRollBackShouldEvictItem() $key = new CollectionCacheKey(State::CLASSNAME, 'cities', array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -137,7 +137,7 @@ public function testTransactionRollBackDeleteShouldClearQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -168,7 +168,7 @@ public function testTransactionRollBackUpdateShouldClearQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -199,7 +199,7 @@ public function testTransactionRollCommitDeleteShouldClearQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -230,7 +230,7 @@ public function testTransactionRollCommitUpdateShouldClearQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -260,7 +260,7 @@ public function testDeleteLockFailureShouldIgnoreQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue(null)); @@ -285,7 +285,7 @@ public function testUpdateLockFailureShouldIgnoreQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue(null)); diff --git a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php index e2728a92869..1b6ed6183b8 100644 --- a/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php +++ b/tests/Doctrine/Tests/ORM/Cache/Persister/ReadWriteCachedEntityPersisterTest.php @@ -23,8 +23,8 @@ class ReadWriteCachedEntityPersisterTest extends AbstractEntityPersisterTest 'put', 'evict', 'evictAll', - 'readLock', - 'readUnlock', + 'lock', + 'unlock', ); /** @@ -51,7 +51,7 @@ public function testDeleteShouldLockItem() $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -68,7 +68,7 @@ public function testUpdateShouldLockItem() $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -85,7 +85,7 @@ public function testUpdateTransactionRollBackShouldEvictItem() $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -108,7 +108,7 @@ public function testDeleteTransactionRollBackShouldEvictItem() $key = new EntityCacheKey(Country::CLASSNAME, array('id'=>1)); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -133,7 +133,7 @@ public function testTransactionRollBackShouldClearQueue() $property->setAccessible(true); $this->region->expects($this->exactly(2)) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -164,7 +164,7 @@ public function testTransactionlCommitShouldClearQueue() $property->setAccessible(true); $this->region->expects($this->exactly(2)) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue($lock)); @@ -194,7 +194,7 @@ public function testDeleteLockFailureShouldIgnoreQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue(null)); @@ -218,7 +218,7 @@ public function testUpdateLockFailureShouldIgnoreQueue() $property->setAccessible(true); $this->region->expects($this->once()) - ->method('readLock') + ->method('lock') ->with($this->equalTo($key)) ->will($this->returnValue(null));