Failed Conditions
Pull Request — 2.8.x (#8009)
by
unknown
08:59
created

DefaultQueryCache::get()   D

Complexity

Conditions 24
Paths 106

Size

Total Lines 153
Code Lines 75

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 75
CRAP Score 24.0013

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 24
eloc 75
c 3
b 0
f 0
nop 3
dl 0
loc 153
ccs 75
cts 76
cp 0.9868
crap 24.0013
rs 4.1166
nc 106

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the MIT license. For more information, see
18
 * <http://www.doctrine-project.org>.
19
 */
20
21
namespace Doctrine\ORM\Cache;
22
23
use Doctrine\Common\Collections\ArrayCollection;
24
use Doctrine\ORM\Cache\Persister\CachedPersister;
25
use Doctrine\ORM\Cache\Persister\Entity\CachedEntityPersister;
26
use Doctrine\ORM\EntityManagerInterface;
27
use Doctrine\ORM\Query\ResultSetMapping;
28
use Doctrine\ORM\Mapping\ClassMetadata;
29
use Doctrine\ORM\PersistentCollection;
30
use Doctrine\Common\Proxy\Proxy;
31
use Doctrine\ORM\Cache;
32
use Doctrine\ORM\Query;
33
use function assert;
34
35
/**
36
 * Default query cache implementation.
37
 *
38
 * @since   2.5
39
 * @author  Fabio B. Silva <[email protected]>
40
 */
41
class DefaultQueryCache implements QueryCache
42
{
43
     /**
44
     * @var \Doctrine\ORM\EntityManagerInterface
45
     */
46
    private $em;
47
48
    /**
49
     * @var \Doctrine\ORM\UnitOfWork
50
     */
51
    private $uow;
52
53
    /**
54
     * @var \Doctrine\ORM\Cache\Region
55
     */
56
    private $region;
57
58
    /**
59
     * @var \Doctrine\ORM\Cache\QueryCacheValidator
60
     */
61
    private $validator;
62
63
    /**
64
     * @var \Doctrine\ORM\Cache\Logging\CacheLogger
65
     */
66
    protected $cacheLogger;
67
68
    /**
69
     * @var array
70
     */
71
    private static $hints = [Query::HINT_CACHE_ENABLED => true];
72
73
    /**
74
     * @param \Doctrine\ORM\EntityManagerInterface $em     The entity manager.
75
     * @param \Doctrine\ORM\Cache\Region           $region The query region.
76
     */
77 88
    public function __construct(EntityManagerInterface $em, Region $region)
78
    {
79 88
        $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
80
81 88
        $this->em           = $em;
82 88
        $this->region       = $region;
83 88
        $this->uow          = $em->getUnitOfWork();
84 88
        $this->cacheLogger  = $cacheConfig->getCacheLogger();
85 88
        $this->validator    = $cacheConfig->getQueryValidator();
86 88
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 54
    public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
92
    {
93 54
        if ( ! ($key->cacheMode & Cache::MODE_GET)) {
94 3
            return null;
95
        }
96
97 53
        $cacheEntry = $this->region->get($key);
98
99 53
        if ( ! $cacheEntry instanceof QueryCacheEntry) {
100 48
            return null;
101
        }
102
103 40
        if ( ! $this->validator->isValid($key, $cacheEntry)) {
104 8
            $this->region->evict($key);
105
106 8
            return null;
107
        }
108
109 35
        $result      = [];
110 35
        $entityName  = reset($rsm->aliasMap);
111 35
        $hasRelation = ! empty($rsm->relationMap);
112 35
        $persister   = $this->uow->getEntityPersister($entityName);
113 35
        assert($persister instanceof CachedEntityPersister);
114
115 35
        $region     = $persister->getCacheRegion();
116 35
        $regionName = $region->getName();
117
118 35
        $cm = $this->em->getClassMetadata($entityName);
119
120
        $generateKeys = static function (array $entry) use ($cm) : EntityCacheKey {
121 35
            return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
122 35
        };
123
124 35
        $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
125 35
        $entries   = $region->getMultiple($cacheKeys) ?? [];
126
127
        // @TODO - move to cache hydration component
128 35
        foreach ($cacheEntry->result as $index => $entry) {
129 35
            $entityEntry = $entries[$index] ?? null;
130
131 35
            if (! $entityEntry instanceof EntityCacheEntry) {
132 3
                if ($this->cacheLogger !== null) {
133 1
                    $this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
134
                }
135
136 3
                return null;
137
            }
138
139 33
            if ($this->cacheLogger !== null) {
140 30
                $this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
141
            }
142
143 33
            if ( ! $hasRelation) {
144 24
                $result[$index]  = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
145
146 24
                continue;
147
            }
148
149 9
            $data = $entityEntry->data;
150
151 9
            foreach ($entry['associations'] as $name => $assoc) {
152 9
                $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
153 9
                assert($assocPersister instanceof CachedEntityPersister);
154
155 9
                $assocRegion   = $assocPersister->getCacheRegion();
156 9
                $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
157
158 9
                if ($assoc['type'] & ClassMetadata::TO_ONE) {
159
160 5
                    if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
161
162 1
                        if ($this->cacheLogger !== null) {
163 1
                            $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
164
                        }
165
166 1
                        $this->uow->hydrationComplete();
167
168 1
                        return null;
169
                    }
170
171 4
                    $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
0 ignored issues
show
Bug introduced by
Accessing class on the interface Doctrine\ORM\Cache\CacheEntry suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
Bug introduced by
The method resolveAssociationEntries() does not exist on Doctrine\ORM\Cache\CacheEntry. It seems like you code against a sub-type of Doctrine\ORM\Cache\CacheEntry such as Doctrine\ORM\Cache\EntityCacheEntry. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

171
                    $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->/** @scrutinizer ignore-call */ resolveAssociationEntries($this->em), self::$hints);
Loading history...
172
173 4
                    if ($this->cacheLogger !== null) {
174 4
                        $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
175
                    }
176
177 4
                    continue;
178
                }
179
180 4
                if ( ! isset($assoc['list']) || empty($assoc['list'])) {
181
                    continue;
182
                }
183
184
                $generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
185 4
                    return new EntityCacheKey($assocMetadata->rootEntityName, $id);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
186 4
                };
187
188 4
                $collection   = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
189 4
                $assocKeys    = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
190 4
                $assocEntries = $assocRegion->getMultiple($assocKeys);
191
192 4
                foreach ($assoc['list'] as $assocIndex => $assocId) {
193 4
                    $assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
194
195 4
                    if ($assocEntry === null) {
196 1
                        if ($this->cacheLogger !== null) {
197 1
                            $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
198
                        }
199
200 1
                        $this->uow->hydrationComplete();
201
202 1
                        return null;
203
                    }
204
205 3
                    $element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
206
207 3
                    $collection->hydrateSet($assocIndex, $element);
208
209 3
                    if ($this->cacheLogger !== null) {
210 3
                        $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
211
                    }
212
                }
213
214 3
                $data[$name] = $collection;
215
216 3
                $collection->setInitialized(true);
217
            }
218
219 7
            foreach ($data as $fieldName => $unCachedAssociationData) {
220
                // In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the
221
                // cache key information in `$cacheEntry` will not contain details
222
                // for fields that are associations.
223
                //
224
                // This means that `$data` keys for some associations that may
225
                // actually not be cached will not be converted to actual association
226
                // data, yet they contain L2 cache AssociationCacheEntry objects.
227
                //
228
                // We need to unwrap those associations into proxy references,
229
                // since we don't have actual data for them except for identifiers.
230 7
                if ($unCachedAssociationData instanceof AssociationCacheEntry) {
231 4
                    $data[$fieldName] = $this->em->getReference(
232 4
                        $unCachedAssociationData->class,
233 7
                        $unCachedAssociationData->identifier
234
                    );
235
                }
236
            }
237
238 7
            $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
239
        }
240
241 30
        $this->uow->hydrationComplete();
242
243 30
        return $result;
244
    }
245
246
    /**
247
     * {@inheritdoc}
248
     */
249 59
    public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
250
    {
251 59
        if ($rsm->scalarMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rsm->scalarMappings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
252 1
            throw new CacheException("Second level cache does not support scalar results.");
253
        }
254
255 58
        if (count($rsm->entityMappings) > 1) {
256 1
            throw new CacheException("Second level cache does not support multiple root entities.");
257
        }
258
259 57
        if ( ! $rsm->isSelect) {
260 2
            throw new CacheException("Second-level cache query supports only select statements.");
261
        }
262
263 55
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) {
264 1
            throw new CacheException("Second level cache does not support partial entities.");
265
        }
266
267 54
        if ( ! ($key->cacheMode & Cache::MODE_PUT)) {
268 3
            return false;
269
        }
270
271 53
        $data        = [];
272 53
        $entityName  = reset($rsm->aliasMap);
273 53
        $rootAlias   = key($rsm->aliasMap);
274 53
        $persister   = $this->uow->getEntityPersister($entityName);
275
276 53
        if (! $persister instanceof CachedEntityPersister) {
277 1
            throw CacheException::nonCacheableEntity($entityName);
278
        }
279
280 52
        $region = $persister->getCacheRegion();
281
282 52
        $cm = $this->em->getClassMetadata($entityName);
283
284 52
        foreach ($result as $index => $entity) {
285 52
            $identifier                     = $this->uow->getEntityIdentifier($entity);
286 52
            $entityKey  = new EntityCacheKey($cm->rootEntityName, $identifier);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
287
288 52
            if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
289
                // Cancel put result if entity put fail
290 36
                if ( ! $persister->storeEntityCache($entity, $entityKey)) {
291 1
                    return false;
292
                }
293
            }
294
295 51
            $data[$index]['identifier']   = $identifier;
296 51
            $data[$index]['associations'] = [];
297
298
            // @TODO - move to cache hydration components
299 51
            foreach ($rsm->relationMap as $alias => $name) {
300 15
                $parentAlias  = $rsm->parentAliasMap[$alias];
301 15
                $parentClass  = $rsm->aliasMap[$parentAlias];
302 15
                $metadata     = $this->em->getClassMetadata($parentClass);
303 15
                $assoc        = $metadata->associationMappings[$name];
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
304 15
                $assocValue   = $this->getAssociationValue($rsm, $alias, $entity);
305
306 15
                if ($assocValue === null) {
307 1
                    continue;
308
                }
309
310
                // root entity association
311 14
                if ($rootAlias === $parentAlias) {
312
                    // Cancel put result if association put fail
313 14
                    if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
314 2
                        return false;
315
                    }
316
317 12
                    $data[$index]['associations'][$name] = $assocInfo;
318
319 12
                    continue;
320
                }
321
322
                // store single nested association
323 2
                if ( ! is_array($assocValue)) {
324
                    // Cancel put result if association put fail
325 1
                    if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
326
                        return false;
327
                    }
328
329 1
                    continue;
330
                }
331
332
                // store array of nested association
333 1
                foreach ($assocValue as $aVal) {
334
                    // Cancel put result if association put fail
335 1
                    if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
336 49
                        return false;
337
                    }
338
                }
339
            }
340
        }
341
342 49
        return $this->region->put($key, new QueryCacheEntry($data));
343
    }
344
345
    /**
346
     * @param \Doctrine\ORM\Cache\QueryCacheKey $key
347
     * @param array                             $assoc
348
     * @param mixed                             $assocValue
349
     *
350
     * @return array|null
351
     */
352 14
    private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
353
    {
354 14
        $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
355 14
        $assocMetadata  = $assocPersister->getClassMetadata();
356 14
        $assocRegion    = $assocPersister->getCacheRegion();
0 ignored issues
show
Bug introduced by
The method getCacheRegion() does not exist on Doctrine\ORM\Persisters\Entity\EntityPersister. It seems like you code against a sub-type of Doctrine\ORM\Persisters\Entity\EntityPersister such as Doctrine\ORM\Cache\Persi...y\CachedEntityPersister. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

356
        /** @scrutinizer ignore-call */ 
357
        $assocRegion    = $assocPersister->getCacheRegion();
Loading history...
357
358
        // Handle *-to-one associations
359 14
        if ($assoc['type'] & ClassMetadata::TO_ONE) {
360 8
            $assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
361 8
            $entityKey       = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
362
363 8
            if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! $assocValue instanceo...n->contains($entityKey), Probably Intended Meaning: ! $assocValue instanceof...->contains($entityKey))
Loading history...
364
                // Entity put fail
365 7
                if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
0 ignored issues
show
Bug introduced by
The method storeEntityCache() does not exist on Doctrine\ORM\Persisters\Entity\EntityPersister. It seems like you code against a sub-type of Doctrine\ORM\Persisters\Entity\EntityPersister such as Doctrine\ORM\Cache\Persi...y\CachedEntityPersister. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

365
                if ( ! $assocPersister->/** @scrutinizer ignore-call */ storeEntityCache($assocValue, $entityKey)) {
Loading history...
366 1
                    return null;
367
                }
368
            }
369
370
            return [
371 7
                'targetEntity'  => $assocMetadata->rootEntityName,
372 7
                'identifier'    => $assocIdentifier,
373 7
                'type'          => $assoc['type']
374
            ];
375
        }
376
377
        // Handle *-to-many associations
378 6
        $list = [];
379
380 6
        foreach ($assocValue as $assocItemIndex => $assocItem) {
381 6
            $assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
382 6
            $entityKey       = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
383
384 6
            if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
385
                // Entity put fail
386 6
                if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
387 1
                    return null;
388
                }
389
            }
390
391 5
            $list[$assocItemIndex] = $assocIdentifier;
392
        }
393
394
        return [
395 5
            'targetEntity'  => $assocMetadata->rootEntityName,
396 5
            'type'          => $assoc['type'],
397 5
            'list'          => $list,
398
        ];
399
    }
400
401
    /**
402
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
403
     * @param string                               $assocAlias
404
     * @param object                               $entity
405
     *
406
     * @return array|object
407
     */
408 16
    private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
409
    {
410 16
        $path  = [];
411 16
        $alias = $assocAlias;
412
413 16
        while (isset($rsm->parentAliasMap[$alias])) {
414 16
            $parent = $rsm->parentAliasMap[$alias];
415 16
            $field  = $rsm->relationMap[$alias];
416 16
            $class  = $rsm->aliasMap[$parent];
417
418 16
            array_unshift($path, [
419 16
                'field'  => $field,
420 16
                'class'  => $class
421
            ]
422
            );
423
424 16
            $alias = $parent;
425
        }
426
427 16
        return $this->getAssociationPathValue($entity, $path);
428
    }
429
430
    /**
431
     * @param mixed $value
432
     * @param array $path
433
     *
434
     * @return array|object|null
435
     */
436 16
    private function getAssociationPathValue($value, array $path)
437
    {
438 16
        $mapping  = array_shift($path);
439 16
        $metadata = $this->em->getClassMetadata($mapping['class']);
440 16
        $assoc    = $metadata->associationMappings[$mapping['field']];
0 ignored issues
show
Bug introduced by
Accessing associationMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
441 16
        $value    = $metadata->getFieldValue($value, $mapping['field']);
0 ignored issues
show
Bug introduced by
The method getFieldValue() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getFieldNames()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

441
        /** @scrutinizer ignore-call */ 
442
        $value    = $metadata->getFieldValue($value, $mapping['field']);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
442
443 16
        if ($value === null) {
444 1
            return null;
445
        }
446
447 15
        if (empty($path)) {
448 15
            return $value;
449
        }
450
451
        // Handle *-to-one associations
452 3
        if ($assoc['type'] & ClassMetadata::TO_ONE) {
453 1
            return $this->getAssociationPathValue($value, $path);
454
        }
455
456 2
        $values = [];
457
458 2
        foreach ($value as $item) {
459 2
            $values[] = $this->getAssociationPathValue($item, $path);
460
        }
461
462 2
        return $values;
463
    }
464
465
    /**
466
     * {@inheritdoc}
467
     */
468 48
    public function clear()
469
    {
470 48
        return $this->region->evictAll();
471
    }
472
473
    /**
474
     * {@inheritdoc}
475
     */
476 28
    public function getRegion()
477
    {
478 28
        return $this->region;
479
    }
480
}
481