Failed Conditions
Pull Request — master (#7214)
by
unknown
14:26
created

AbstractQuery::setResultCacheDriver()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.0466

Importance

Changes 0
Metric Value
cc 4
eloc 6
nc 3
nop 1
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
ccs 6
cts 7
cp 0.8571
crap 4.0466
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\Common\Collections\Collection;
9
use Doctrine\DBAL\Cache\QueryCacheProfile;
10
use Doctrine\DBAL\Driver\Statement;
11
use Doctrine\ORM\Cache\Exception\InvalidResultCacheDriver;
12
use Doctrine\ORM\Cache\Logging\CacheLogger;
13
use Doctrine\ORM\Cache\QueryCacheKey;
14
use Doctrine\ORM\Cache\TimestampCacheKey;
15
use Doctrine\ORM\Internal\Hydration\IterableResult;
16
use Doctrine\ORM\Query\Parameter;
17
use Doctrine\ORM\Query\ResultSetMapping;
18
use Doctrine\ORM\Utility\StaticClassNameConverter;
19
use function array_map;
20
use function array_shift;
21
use function count;
22
use function is_array;
23
use function is_numeric;
24
use function is_object;
25
use function is_scalar;
26
use function ksort;
27
use function reset;
28
use function serialize;
29
use function sha1;
30
31
/**
32
 * Base contract for ORM queries. Base class for Query and NativeQuery.
33
 */
34
abstract class AbstractQuery
35
{
36
    /* Hydration mode constants */
37
38
    /**
39
     * Hydrates an object graph. This is the default behavior.
40
     */
41
    public const HYDRATE_OBJECT = 1;
42
43
    /**
44
     * Hydrates an array graph.
45
     */
46
    public const HYDRATE_ARRAY = 2;
47
48
    /**
49
     * Hydrates a flat, rectangular result set with scalar values.
50
     */
51
    public const HYDRATE_SCALAR = 3;
52
53
    /**
54
     * Hydrates a single scalar value.
55
     */
56
    public const HYDRATE_SINGLE_SCALAR = 4;
57
58
    /**
59
     * Very simple object hydrator (optimized for performance).
60
     */
61
    public const HYDRATE_SIMPLEOBJECT = 5;
62
63
    /**
64
     * The parameter map of this query.
65
     *
66
     * @var ArrayCollection
67
     */
68
    protected $parameters;
69
70
    /**
71
     * The user-specified ResultSetMapping to use.
72
     *
73
     * @var ResultSetMapping
74
     */
75
    protected $resultSetMapping;
76
77
    /**
78
     * The entity manager used by this query object.
79
     *
80
     * @var EntityManagerInterface
81
     */
82
    protected $em;
83
84
    /**
85
     * The map of query hints.
86
     *
87
     * @var mixed[]
88
     */
89
    protected $hints = [];
90
91
    /**
92
     * The hydration mode.
93
     *
94
     * @var int
95
     */
96
    protected $hydrationMode = self::HYDRATE_OBJECT;
97
98
    /** @var QueryCacheProfile */
99
    protected $queryCacheProfile;
100
101
    /**
102
     * Whether or not expire the result cache.
103
     *
104
     * @var bool
105
     */
106
    protected $expireResultCache = false;
107
108
    /** @var QueryCacheProfile */
109
    protected $hydrationCacheProfile;
110
111
    /**
112
     * Whether to use second level cache, if available.
113
     *
114
     * @var bool
115
     */
116
    protected $cacheable = false;
117
118
    /** @var bool */
119
    protected $hasCache = false;
120
121
    /**
122
     * Second level cache region name.
123
     *
124
     * @var string|null
125
     */
126
    protected $cacheRegion;
127
128
    /**
129
     * Second level query cache mode.
130
     *
131
     * @var int|null
132
     */
133
    protected $cacheMode;
134
135
    /** @var CacheLogger|null */
136
    protected $cacheLogger;
137
138
    /** @var int */
139
    protected $lifetime = 0;
140
141
    /**
142
     * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
143
     */
144 968
    public function __construct(EntityManagerInterface $em)
145
    {
146 968
        $this->em         = $em;
147 968
        $this->parameters = new ArrayCollection();
148 968
        $this->hints      = $em->getConfiguration()->getDefaultQueryHints();
149 968
        $this->hasCache   = $this->em->getConfiguration()->isSecondLevelCacheEnabled();
150
151 968
        if ($this->hasCache) {
152 32
            $this->cacheLogger = $em->getConfiguration()
153 32
                ->getSecondLevelCacheConfiguration()
154 32
                ->getCacheLogger();
155
        }
156 968
    }
157
158
    /**
159
     * Enable/disable second level query (result) caching for this query.
160
     *
161
     * @param bool $cacheable
162
     *
163
     * @return static This query instance.
164
     */
165 133
    public function setCacheable($cacheable)
166
    {
167 133
        $this->cacheable = (bool) $cacheable;
168
169 133
        return $this;
170
    }
171
172
    /**
173
     * @return bool TRUE if the query results are enable for second level cache, FALSE otherwise.
174
     */
175 90
    public function isCacheable()
176
    {
177 90
        return $this->cacheable;
178
    }
179
180
    /**
181
     * @param string $cacheRegion
182
     *
183
     * @return static This query instance.
184
     */
185 2
    public function setCacheRegion($cacheRegion)
186
    {
187 2
        $this->cacheRegion = (string) $cacheRegion;
188
189 2
        return $this;
190
    }
191
192
    /**
193
     * Obtain the name of the second level query cache region in which query results will be stored
194
     *
195
     * @return string|null The cache region name; NULL indicates the default region.
196
     */
197 1
    public function getCacheRegion()
198
    {
199 1
        return $this->cacheRegion;
200
    }
201
202
    /**
203
     * @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise.
204
     */
205 466
    protected function isCacheEnabled()
206
    {
207 466
        return $this->cacheable && $this->hasCache;
208
    }
209
210
    /**
211
     * @return int
212
     */
213 1
    public function getLifetime()
214
    {
215 1
        return $this->lifetime;
216
    }
217
218
    /**
219
     * Sets the life-time for this query into second level cache.
220
     *
221
     * @param int $lifetime
222
     *
223
     * @return \Doctrine\ORM\AbstractQuery This query instance.
224
     */
225 2
    public function setLifetime($lifetime)
226
    {
227 2
        $this->lifetime = (int) $lifetime;
228
229 2
        return $this;
230
    }
231
232
    /**
233
     * @return int
234
     */
235 1
    public function getCacheMode()
236
    {
237 1
        return $this->cacheMode;
238
    }
239
240
    /**
241
     * @param int $cacheMode
242
     *
243
     * @return \Doctrine\ORM\AbstractQuery This query instance.
244
     */
245 4
    public function setCacheMode($cacheMode)
246
    {
247 4
        $this->cacheMode = (int) $cacheMode;
248
249 4
        return $this;
250
    }
251
252
    /**
253
     * Gets the SQL query that corresponds to this query object.
254
     * The returned SQL syntax depends on the connection driver that is used
255
     * by this query object at the time of this method call.
256
     *
257
     * @return string SQL query
258
     */
259
    abstract public function getSQL();
260
261
    /**
262
     * Retrieves the associated EntityManager of this Query instance.
263
     *
264
     * @return EntityManagerInterface
265
     */
266 891
    public function getEntityManager()
267
    {
268 891
        return $this->em;
269
    }
270
271
    /**
272
     * Frees the resources used by the query object.
273
     *
274
     * Resets Parameters, Parameter Types and Query Hints.
275
     */
276 220
    public function free()
277
    {
278 220
        $this->parameters = new ArrayCollection();
279
280 220
        $this->hints = $this->em->getConfiguration()->getDefaultQueryHints();
281 220
    }
282
283
    /**
284
     * Get all defined parameters.
285
     *
286
     * @return ArrayCollection The defined query parameters.
287
     */
288 135
    public function getParameters()
289
    {
290 135
        return $this->parameters;
291
    }
292
293
    /**
294
     * Gets a query parameter.
295
     *
296
     * @param mixed $key The key (index or name) of the bound parameter.
297
     *
298
     * @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
299
     */
300 266
    public function getParameter($key)
301
    {
302 266
        $filteredParameters = $this->parameters->filter(
303
            function (Query\Parameter $parameter) use ($key) : bool {
304 150
                $parameterName = $parameter->getName();
305
306 150
                return $key === $parameterName || (string) $key === (string) $parameterName;
307 266
            }
308
        );
309
310 266
        return $filteredParameters->isEmpty() ? null : $filteredParameters->first();
311
    }
312
313
    /**
314
     * Sets a collection of query parameters.
315
     *
316
     * @param ArrayCollection|array|Parameter[]|mixed[] $parameters
317
     *
318
     * @return static This query instance.
319
     */
320 190
    public function setParameters($parameters)
321
    {
322
        // BC compatibility with 2.3-
323 190
        if (is_array($parameters)) {
324 1
            $parameterCollection = new ArrayCollection();
325
326 1
            foreach ($parameters as $key => $value) {
327 1
                $parameterCollection->add(new Parameter($key, $value));
328
            }
329
330 1
            $parameters = $parameterCollection;
331
        }
332
333 190
        $this->parameters = $parameters;
334
335 190
        return $this;
336
    }
337
338
    /**
339
     * Sets a query parameter.
340
     *
341
     * @param string|int  $key   The parameter position or name.
342
     * @param mixed       $value The parameter value.
343
     * @param string|null $type  The parameter type. If specified, the given value will be run through
344
     *                           the type conversion of this type. This is usually not needed for
345
     *                           strings and numeric types.
346
     *
347
     * @return static This query instance.
348
     */
349 176
    public function setParameter($key, $value, $type = null)
350
    {
351 176
        $existingParameter = $this->getParameter($key);
352
353 176
        if ($existingParameter !== null) {
354 5
            $existingParameter->setValue($value, $type);
355
356 5
            return $this;
357
        }
358
359 174
        $this->parameters->add(new Parameter($key, $value, $type));
360
361 174
        return $this;
362
    }
363
364
    /**
365
     * Processes an individual parameter value.
366
     *
367
     * @param mixed $value
368
     *
369
     * @return string|mixed[]
370
     *
371
     * @throws ORMInvalidArgumentException
372
     */
373 173
    public function processParameterValue($value)
374
    {
375 173
        if (is_scalar($value)) {
376 161
            return $value;
377
        }
378
379 88
        if ($value instanceof Mapping\ClassMetadata) {
380 1
            return $value->discriminatorValue ?: $value->getClassName();
381
        }
382
383 87
        if ($value instanceof Collection) {
384 1
            $value = $value->toArray();
385
        }
386
387 87
        if (is_array($value)) {
388 72
            foreach ($value as $key => $paramValue) {
389 72
                $paramValue  = $this->processParameterValue($paramValue);
390 72
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
391
            }
392
393 72
            return $value;
394
        }
395
396 18
        if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(StaticClassNameConverter::getClass($value))) {
397 13
            $value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
398
399 13
            if ($value === null) {
400
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
401
            }
402
        }
403
404 18
        return $value;
405
    }
406
407
    /**
408
     * Sets the ResultSetMapping that should be used for hydration.
409
     *
410
     * @return static This query instance.
411
     */
412 22
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
413
    {
414 22
        $this->resultSetMapping = $rsm;
415
416 22
        return $this;
417
    }
418
419
    /**
420
     * Gets the ResultSetMapping used for hydration.
421
     *
422
     * @return ResultSetMapping
423
     */
424 14
    protected function getResultSetMapping()
425
    {
426 14
        return $this->resultSetMapping;
427
    }
428
429
    /**
430
     * Set a cache profile for hydration caching.
431
     *
432
     * If no result cache driver is set in the QueryCacheProfile, the default
433
     * result cache driver is used from the configuration.
434
     *
435
     * Important: Hydration caching does NOT register entities in the
436
     * UnitOfWork when retrieved from the cache. Never use result cached
437
     * entities for requests that also flush the EntityManager. If you want
438
     * some form of caching with UnitOfWork registration you should use
439
     * {@see AbstractQuery::setResultCacheProfile()}.
440
     *
441
     * @example
442
     * $lifetime = 100;
443
     * $resultKey = "abc";
444
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
445
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
446
     *
447
     * @return static This query instance.
448
     */
449 3
    public function setHydrationCacheProfile(?QueryCacheProfile $profile = null)
450
    {
451 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
452
            $resultCacheDriver = $this->em->getConfiguration()->getHydrationCacheImpl();
453
            $profile           = $profile->setResultCacheDriver($resultCacheDriver);
454
        }
455
456 3
        $this->hydrationCacheProfile = $profile;
457
458 3
        return $this;
459
    }
460
461
    /**
462
     * @return QueryCacheProfile
463
     */
464 3
    public function getHydrationCacheProfile()
465
    {
466 3
        return $this->hydrationCacheProfile;
467
    }
468
469
    /**
470
     * Set a cache profile for the result cache.
471
     *
472
     * If no result cache driver is set in the QueryCacheProfile, the default
473
     * result cache driver is used from the configuration.
474
     *
475
     * @return static This query instance.
476
     */
477 1
    public function setResultCacheProfile(?QueryCacheProfile $profile = null)
478
    {
479 1
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
480
            $resultCacheDriver = $this->em->getConfiguration()->getResultCacheImpl();
481
            $profile           = $profile->setResultCacheDriver($resultCacheDriver);
482
        }
483
484 1
        $this->queryCacheProfile = $profile;
485
486 1
        return $this;
487
    }
488
489
    /**
490
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
491
     *
492
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
493
     *
494
     * @return static This query instance.
495
     *
496
     * @throws ORMException
497
     */
498 9
    public function setResultCacheDriver($resultCacheDriver = null)
499
    {
500 9
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
0 ignored issues
show
introduced by
$resultCacheDriver is always a sub-type of Doctrine\Common\Cache\Cache.
Loading history...
501
            throw InvalidResultCacheDriver::create();
502
        }
503
504 9
        $this->queryCacheProfile = $this->queryCacheProfile
505 1
            ? $this->queryCacheProfile->setResultCacheDriver($resultCacheDriver)
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver can also be of type null; however, parameter $cache of Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does only seem to accept Doctrine\Common\Cache\Cache, maybe add an additional type check? ( Ignorable by Annotation )

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

505
            ? $this->queryCacheProfile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver)
Loading history...
506 8
            : new QueryCacheProfile(0, null, $resultCacheDriver);
507
508 9
        return $this;
509
    }
510
511
    /**
512
     * Returns the cache driver used for caching result sets.
513
     *
514
     * @deprecated
515
     *
516
     * @return \Doctrine\Common\Cache\Cache Cache driver
517
     */
518 3
    public function getResultCacheDriver()
519
    {
520 3
        if ($this->queryCacheProfile && $this->queryCacheProfile->getResultCacheDriver()) {
521 3
            return $this->queryCacheProfile->getResultCacheDriver();
522
        }
523
524
        return $this->em->getConfiguration()->getResultCacheImpl();
525
    }
526
527
    /**
528
     * Enables caching of the results of this query, for given or default amount of seconds
529
     * and optionally specifies which ID to use for the cache entry.
530
     *
531
     * @param int|null    $lifetime      How long the cache entry is valid, in seconds.
532
     * @param string|null $resultCacheId ID to use for the cache entry.
533
     *
534
     * @return static This query instance.
535
     */
536 9
    public function enableResultCache($lifetime = null, $resultCacheId = null)
537
    {
538 9
        $this->setResultCacheLifetime($lifetime);
539 9
        $this->setResultCacheId($resultCacheId);
540
541 9
        return $this;
542
    }
543
544
    /**
545
     * Disables caching of the results of this query.
546
     *
547
     * @return static This query instance.
548
     */
549 2
    public function disableResultCache()
550
    {
551 2
        $this->queryCacheProfile = null;
552
553 2
        return $this;
554
    }
555
556
    /**
557
     * Defines how long the result cache will be active before expire.
558
     *
559
     * @param int $lifetime How long the cache entry is valid, in seconds.
560
     *
561
     * @return static This query instance.
562
     */
563 10
    public function setResultCacheLifetime($lifetime)
564
    {
565 10
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
0 ignored issues
show
introduced by
The condition $lifetime !== null is always true.
Loading history...
566
567 10
        $this->queryCacheProfile = $this->queryCacheProfile
568 4
            ? $this->queryCacheProfile->setLifetime($lifetime)
569 6
            : new QueryCacheProfile($lifetime, null, $this->em->getConfiguration()->getResultCacheImpl());
570
571 10
        return $this;
572
    }
573
574
    /**
575
     * Retrieves the lifetime of resultset cache.
576
     *
577
     * @deprecated
578
     *
579
     * @return int
580
     */
581
    public function getResultCacheLifetime()
582
    {
583
        return $this->queryCacheProfile ? $this->queryCacheProfile->getLifetime() : 0;
584
    }
585
586
    /**
587
     * Defines if the result cache is active or not.
588
     *
589
     * @param bool $expire Whether or not to force resultset cache expiration.
590
     *
591
     * @return static This query instance.
592
     */
593 4
    public function expireResultCache($expire = true)
594
    {
595 4
        $this->expireResultCache = $expire;
596
597 4
        return $this;
598
    }
599
600
    /**
601
     * Retrieves if the resultset cache is active or not.
602
     *
603
     * @return bool
604
     */
605 8
    public function getExpireResultCache()
606
    {
607 8
        return $this->expireResultCache;
608
    }
609
610
    /**
611
     * @return QueryCacheProfile
612
     */
613 1
    public function getQueryCacheProfile()
614
    {
615 1
        return $this->queryCacheProfile;
616
    }
617
618
    /**
619
     * Change the default fetch mode of an association for this query.
620
     *
621
     * $fetchMode can be one of FetchMode::EAGER, FetchMode::LAZY or FetchMode::EXTRA_LAZY
622
     *
623
     * @param string $class
624
     * @param string $assocName
625
     * @param int    $fetchMode
626
     *
627
     * @return static This query instance.
628
     */
629 3
    public function setFetchMode($class, $assocName, $fetchMode)
630
    {
631 3
        if ($fetchMode !== Mapping\FetchMode::EAGER) {
0 ignored issues
show
introduced by
The condition $fetchMode !== Doctrine\...apping\FetchMode::EAGER is always true.
Loading history...
632
            $fetchMode = Mapping\FetchMode::LAZY;
633
        }
634
635 3
        $this->hints['fetchMode'][$class][$assocName] = $fetchMode;
636
637 3
        return $this;
638
    }
639
640
    /**
641
     * Defines the processing mode to be used during hydration / result set transformation.
642
     *
643
     * @param int $hydrationMode Doctrine processing mode to be used during hydration process.
644
     *                           One of the Query::HYDRATE_* constants.
645
     *
646
     * @return static This query instance.
647
     */
648 373
    public function setHydrationMode($hydrationMode)
649
    {
650 373
        $this->hydrationMode = $hydrationMode;
651
652 373
        return $this;
653
    }
654
655
    /**
656
     * Gets the hydration mode currently used by the query.
657
     *
658
     * @return int
659
     */
660 653
    public function getHydrationMode()
661
    {
662 653
        return $this->hydrationMode;
663
    }
664
665
    /**
666
     * Gets the list of results for the query.
667
     *
668
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
669
     *
670
     * @param int $hydrationMode
671
     *
672
     * @return mixed
673
     */
674 294
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
675
    {
676 294
        return $this->execute(null, $hydrationMode);
677
    }
678
679
    /**
680
     * Gets the array of results for the query.
681
     *
682
     * Alias for execute(null, HYDRATE_ARRAY).
683
     *
684
     * @return mixed[]
685
     */
686 24
    public function getArrayResult()
687
    {
688 24
        return $this->execute(null, self::HYDRATE_ARRAY);
689
    }
690
691
    /**
692
     * Gets the scalar results for the query.
693
     *
694
     * Alias for execute(null, HYDRATE_SCALAR).
695
     *
696
     * @return mixed[]
697
     */
698 87
    public function getScalarResult()
699
    {
700 87
        return $this->execute(null, self::HYDRATE_SCALAR);
701
    }
702
703
    /**
704
     * Get exactly one result or null.
705
     *
706
     * @param int $hydrationMode
707
     *
708
     * @return mixed
709
     *
710
     * @throws NonUniqueResultException
711
     */
712 16
    public function getOneOrNullResult($hydrationMode = null)
713
    {
714
        try {
715 16
            $result = $this->execute(null, $hydrationMode);
716 1
        } catch (NoResultException $e) {
717 1
            return null;
718
        }
719
720 15
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
721 1
            return null;
722
        }
723
724 14
        if (! is_array($result)) {
725 1
            return $result;
726
        }
727
728 14
        if (count($result) > 1) {
729 1
            throw new NonUniqueResultException();
730
        }
731
732 13
        return array_shift($result);
733
    }
734
735
    /**
736
     * Gets the single result of the query.
737
     *
738
     * Enforces the presence as well as the uniqueness of the result.
739
     *
740
     * If the result is not unique, a NonUniqueResultException is thrown.
741
     * If there is no result, a NoResultException is thrown.
742
     *
743
     * @param int $hydrationMode
744
     *
745
     * @return mixed
746
     *
747
     * @throws NonUniqueResultException If the query result is not unique.
748
     * @throws NoResultException        If the query returned no result.
749
     */
750 102
    public function getSingleResult($hydrationMode = null)
751
    {
752 102
        $result = $this->execute(null, $hydrationMode);
753
754 96
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
755 2
            throw new NoResultException();
756
        }
757
758 95
        if (! is_array($result)) {
759 9
            return $result;
760
        }
761
762 87
        if (count($result) > 1) {
763 1
            throw new NonUniqueResultException();
764
        }
765
766 86
        return array_shift($result);
767
    }
768
769
    /**
770
     * Gets the single scalar result of the query.
771
     *
772
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
773
     *
774
     * @return mixed The scalar result, or NULL if the query returned no result.
775
     *
776
     * @throws NonUniqueResultException If the query result is not unique.
777
     * @throws NoResultException        If the query returned no result.
778
     */
779 11
    public function getSingleScalarResult()
780
    {
781 11
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
782
    }
783
784
    /**
785
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
786
     *
787
     * @param string $name  The name of the hint.
788
     * @param mixed  $value The value of the hint.
789
     *
790
     * @return static This query instance.
791
     */
792 464
    public function setHint($name, $value)
793
    {
794 464
        $this->hints[$name] = $value;
795
796 464
        return $this;
797
    }
798
799
    /**
800
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
801
     *
802
     * @param string $name The name of the hint.
803
     *
804
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
805
     */
806 797
    public function getHint($name)
807
    {
808 797
        return $this->hints[$name] ?? false;
809
    }
810
811
    /**
812
     * Check if the query has a hint
813
     *
814
     * @param string $name The name of the hint
815
     *
816
     * @return bool False if the query does not have any hint
817
     */
818 17
    public function hasHint($name)
819
    {
820 17
        return isset($this->hints[$name]);
821
    }
822
823
    /**
824
     * Return the key value map of query hints that are currently set.
825
     *
826
     * @return mixed[]
827
     */
828 136
    public function getHints()
829
    {
830 136
        return $this->hints;
831
    }
832
833
    /**
834
     * Executes the query and returns an IterableResult that can be used to incrementally
835
     * iterate over the result.
836
     *
837
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters    The query parameters.
838
     * @param int|null                                       $hydrationMode The hydration mode to use.
839
     *
840
     * @return IterableResult
841
     */
842 10
    public function iterate($parameters = null, $hydrationMode = null)
843
    {
844 10
        if ($hydrationMode !== null) {
845 10
            $this->setHydrationMode($hydrationMode);
846
        }
847
848 10
        if (! empty($parameters)) {
849 1
            $this->setParameters($parameters);
850
        }
851
852 10
        $rsm  = $this->getResultSetMapping();
853 7
        $stmt = $this->doExecute();
854
855 7
        return $this->em->newHydrator($this->hydrationMode)->iterate($stmt, $rsm, $this->hints);
856
    }
857
858
    /**
859
     * Executes the query.
860
     *
861
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters    Query parameters.
862
     * @param int|null                                       $hydrationMode Processing mode to be used during the hydration process.
863
     *
864
     * @return mixed
865
     */
866 466
    public function execute($parameters = null, $hydrationMode = null)
867
    {
868 466
        return $this->isCacheEnabled()
869 29
            ? $this->executeUsingQueryCache($parameters, $hydrationMode)
870 463
            : $this->executeIgnoreQueryCache($parameters, $hydrationMode);
871
    }
872
873
    /**
874
     * Execute query ignoring second level cache.
875
     *
876
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters
877
     * @param int|null                                       $hydrationMode
878
     *
879
     * @return mixed
880
     */
881 466
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
882
    {
883 466
        if ($hydrationMode !== null) {
884 363
            $this->setHydrationMode($hydrationMode);
885
        }
886
887 466
        if (! empty($parameters)) {
888
            $this->setParameters($parameters);
889
        }
890
891
        $setCacheEntry = function () {
892 466
        };
893
894 466
        if ($this->hydrationCacheProfile !== null) {
895 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
896
897 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
898 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
899 2
            $result            = $cache->fetch($cacheKey);
900
901 2
            if (isset($result[$realCacheKey])) {
902 2
                return $result[$realCacheKey];
903
            }
904
905 2
            if (! $result) {
906 2
                $result = [];
907
            }
908
909
            $setCacheEntry = function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
910 2
                $result[$realCacheKey] = $data;
911
912 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
913 2
            };
914
        }
915
916 466
        $stmt = $this->doExecute();
917
918 456
        if (is_numeric($stmt)) {
0 ignored issues
show
introduced by
The condition is_numeric($stmt) is always false.
Loading history...
919 27
            $setCacheEntry($stmt);
920
921 27
            return $stmt;
922
        }
923
924 441
        $rsm  = $this->getResultSetMapping();
925 441
        $data = $this->em->newHydrator($this->hydrationMode)->hydrateAll($stmt, $rsm, $this->hints);
926
927 437
        $setCacheEntry($data);
928
929 437
        return $data;
930
    }
931
932
    /**
933
     * Load from second level cache or executes the query and put into cache.
934
     *
935
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters
936
     * @param int|null                                       $hydrationMode
937
     *
938
     * @return mixed
939
     */
940 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
941
    {
942 29
        $rsm        = $this->getResultSetMapping();
943 29
        $queryCache = $this->em->getCache()->getQueryCache($this->cacheRegion);
944 29
        $queryKey   = new QueryCacheKey(
945 29
            $this->getHash(),
946 29
            $this->lifetime,
947 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
948 29
            $this->getTimestampKey()
949
        );
950
951 29
        $result = $queryCache->get($queryKey, $rsm, $this->hints);
952
953 29
        if ($result !== null) {
954 16
            if ($this->cacheLogger) {
955 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
956
            }
957
958 16
            return $result;
959
        }
960
961 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
962 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->hints);
963
964 26
        if ($this->cacheLogger) {
965 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
966
967 26
            if ($cached) {
968 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
969
            }
970
        }
971
972 26
        return $result;
973
    }
974
975
    /**
976
     * @return TimestampCacheKey|null
977
     */
978 29
    private function getTimestampKey()
979
    {
980 29
        $entityName = reset($this->resultSetMapping->aliasMap);
981
982 29
        if (empty($entityName)) {
983 2
            return null;
984
        }
985
986 27
        $metadata = $this->em->getClassMetadata($entityName);
987
988 27
        return new Cache\TimestampCacheKey($metadata->getRootClassName());
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

988
        return new Cache\TimestampCacheKey($metadata->/** @scrutinizer ignore-call */ getRootClassName());

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...
989
    }
990
991
    /**
992
     * Get the result cache id to use to store the result set cache entry.
993
     * Will return the configured id if it exists otherwise a hash will be
994
     * automatically generated for you.
995
     *
996
     * @return string[] ($key, $hash)
997
     */
998 2
    protected function getHydrationCacheId()
999
    {
1000 2
        $parameters = [];
1001
1002 2
        foreach ($this->getParameters() as $parameter) {
1003 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1004
        }
1005
1006 2
        $sql                    = $this->getSQL();
1007 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1008 2
        $hints                  = $this->getHints();
1009 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1010
1011 2
        ksort($hints);
1012
1013 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1014
    }
1015
1016
    /**
1017
     * Set the result cache id to use to store the result set cache entry.
1018
     * If this is not explicitly set by the developer then a hash is automatically
1019
     * generated for you.
1020
     *
1021
     * @param string $id
1022
     *
1023
     * @return static This query instance.
1024
     */
1025 13
    public function setResultCacheId($id)
1026
    {
1027 13
        $this->queryCacheProfile = $this->queryCacheProfile
1028 13
            ? $this->queryCacheProfile->setCacheKey($id)
1029
            : new QueryCacheProfile(0, $id, $this->em->getConfiguration()->getResultCacheImpl());
1030
1031 13
        return $this;
1032
    }
1033
1034
    /**
1035
     * Get the result cache id to use to store the result set cache entry if set.
1036
     *
1037
     * @deprecated
1038
     *
1039
     * @return string
1040
     */
1041
    public function getResultCacheId()
1042
    {
1043
        return $this->queryCacheProfile ? $this->queryCacheProfile->getCacheKey() : null;
1044
    }
1045
1046
    /**
1047
     * Executes the query and returns a the resulting Statement object.
1048
     *
1049
     * @return Statement The executed database statement that holds the results.
1050
     */
1051
    abstract protected function doExecute();
1052
1053
    /**
1054
     * Cleanup Query resource when clone is called.
1055
     */
1056 139
    public function __clone()
1057
    {
1058 139
        $this->parameters = new ArrayCollection();
1059 139
        $this->hints      = $this->em->getConfiguration()->getDefaultQueryHints();
1060 139
    }
1061
1062
    /**
1063
     * Generates a string of currently query to use for the cache second level cache.
1064
     *
1065
     * @return string
1066
     */
1067 29
    protected function getHash()
1068
    {
1069 29
        $query  = $this->getSQL();
1070 29
        $hints  = $this->getHints();
1071
        $params = array_map(function (Parameter $parameter) {
1072 5
            $value = $parameter->getValue();
1073
1074
            // Small optimization
1075
            // Does not invoke processParameterValue for scalar values
1076 5
            if (is_scalar($value)) {
1077 4
                return $value;
1078
            }
1079
1080 1
            return $this->processParameterValue($value);
1081 29
        }, $this->parameters->getValues());
1082
1083 29
        ksort($hints);
1084
1085 29
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1086
    }
1087
}
1088