From 07b397f3419e37f4ac0bf1d0a55572af7cb9d575 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Wed, 23 Aug 2017 00:19:49 +0200 Subject: [PATCH] #6284 fixing actual issue, which comes from an extremely tricky L2 caching issue. We are not hydrating some of the cached association data into entities due to keys missing in the cache association definition. Since this is an extreme edge case that is just a mismatch between db and cache, a detailed explanation was provided in the fix snippet as well --- lib/Doctrine/ORM/Cache/DefaultQueryCache.php | 37 ++++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php index 3ac206547..d431313ef 100644 --- a/lib/Doctrine/ORM/Cache/DefaultQueryCache.php +++ b/lib/Doctrine/ORM/Cache/DefaultQueryCache.php @@ -92,13 +92,13 @@ class DefaultQueryCache implements QueryCache return null; } - $entry = $this->region->get($key); + $cacheEntry = $this->region->get($key); - if ( ! $entry instanceof QueryCacheEntry) { + if ( ! $cacheEntry instanceof QueryCacheEntry) { return null; } - if ( ! $this->validator->isValid($key, $entry)) { + if ( ! $this->validator->isValid($key, $cacheEntry)) { $this->region->evict($key); return null; @@ -117,11 +117,11 @@ class DefaultQueryCache implements QueryCache return new EntityCacheKey($cm->rootEntityName, $entry['identifier']); }; - $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $entry->result)); + $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result)); $entries = $region->getMultiple($cacheKeys); // @TODO - move to cache hydration component - foreach ($entry->result as $index => $entry) { + foreach ($cacheEntry->result as $index => $entry) { $entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null; if ($entityEntry === null) { @@ -210,6 +210,25 @@ class DefaultQueryCache implements QueryCache $collection->setInitialized(true); } + foreach ($data as $fieldName => $unCachedAssociationData) { + // In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the + // cache key information in `$cacheEntry` will not contain details + // for fields that are associations. + // + // This means that `$data` keys for some associations that may + // actually not be cached will not be converted to actual association + // data, yet they contain L2 cache AssociationCacheEntry objects. + // + // We need to unwrap those associations into proxy references, + // since we don't have actual data for them except for identifiers. + if ($unCachedAssociationData instanceof AssociationCacheEntry) { + $data[$fieldName] = $this->em->getReference( + $unCachedAssociationData->class, + $unCachedAssociationData->identifier + ); + } + } + $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints); } @@ -246,7 +265,6 @@ class DefaultQueryCache implements QueryCache $data = []; $entityName = reset($rsm->aliasMap); $rootAlias = key($rsm->aliasMap); - $hasRelation = ( ! empty($rsm->relationMap)); $persister = $this->uow->getEntityPersister($entityName); if ( ! ($persister instanceof CachedPersister)) { @@ -258,8 +276,6 @@ class DefaultQueryCache implements QueryCache foreach ($result as $index => $entity) { $identifier = $this->uow->getEntityIdentifier($entity); $entityKey = new EntityCacheKey($entityName, $identifier); - $data[$index]['identifier'] = $identifier; - $data[$index]['associations'] = []; if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) { // Cancel put result if entity put fail @@ -268,9 +284,8 @@ class DefaultQueryCache implements QueryCache } } - if ( ! $hasRelation) { - continue; - } + $data[$index]['identifier'] = $identifier; + $data[$index]['associations'] = []; // @TODO - move to cache hydration components foreach ($rsm->relationMap as $alias => $name) {