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

DefaultQueryCache::put()   D

Complexity

Conditions 20
Paths 24

Size

Total Lines 94
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 47
CRAP Score 20.0035

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 47
c 1
b 0
f 0
dl 0
loc 94
ccs 47
cts 48
cp 0.9792
rs 4.1666
cc 20
nc 24
nop 4
crap 20.0035

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