Failed Conditions
Pull Request — master (#7210)
by Michael
12:25
created

AbstractQuery::getOneOrNullResult()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 6

Importance

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

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

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