Failed Conditions
Pull Request — master (#7885)
by Šimon
06:26
created

AbstractQuery::hasHint()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

466
            $profile           = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
467
        }
468
469 3
        $this->hydrationCacheProfile = $profile;
470
471 3
        return $this;
472
    }
473
474
    /**
475
     * @return QueryCacheProfile
476
     */
477 3
    public function getHydrationCacheProfile()
478
    {
479 3
        return $this->hydrationCacheProfile;
480
    }
481
482
    /**
483
     * Set a cache profile for the result cache.
484
     *
485
     * If no result cache driver is set in the QueryCacheProfile, the default
486
     * result cache driver is used from the configuration.
487
     *
488
     * @return static This query instance.
489
     */
490 1
    public function setResultCacheProfile(?QueryCacheProfile $profile = null)
491
    {
492 1
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
493
            $resultCacheDriver = $this->em->getConfiguration()->getResultCacheImpl();
494
            $profile           = $profile->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

494
            $profile           = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
495
        }
496
497 1
        $this->queryCacheProfile = $profile;
498
499 1
        return $this;
500
    }
501
502
    /**
503
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
504
     *
505
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
506
     *
507
     * @return static This query instance.
508
     *
509
     * @throws ORMException
510
     */
511 8
    public function setResultCacheDriver($resultCacheDriver = null)
512
    {
513 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...
514
            throw InvalidResultCacheDriver::create();
515
        }
516
517 8
        $this->queryCacheProfile = $this->queryCacheProfile
518 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

518
            ? $this->queryCacheProfile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver)
Loading history...
519 7
            : new QueryCacheProfile(0, null, $resultCacheDriver);
520
521 8
        return $this;
522
    }
523
524
    /**
525
     * Returns the cache driver used for caching result sets.
526
     *
527
     * @deprecated
528
     *
529
     * @return \Doctrine\Common\Cache\Cache Cache driver
530
     */
531 3
    public function getResultCacheDriver()
532
    {
533 3
        if ($this->queryCacheProfile && $this->queryCacheProfile->getResultCacheDriver()) {
534 3
            return $this->queryCacheProfile->getResultCacheDriver();
535
        }
536
537
        return $this->em->getConfiguration()->getResultCacheImpl();
538
    }
539
540
    /**
541
     * Set whether or not to cache the results of this query and if so, for
542
     * how long and which ID to use for the cache entry.
543
     *
544
     * @param bool   $useCache      Whether or not to cache the results of this query.
545
     * @param int    $lifetime      How long the cache entry is valid, in seconds.
546
     * @param string $resultCacheId ID to use for the cache entry.
547
     *
548
     * @return static This query instance.
549
     */
550 9
    public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
551
    {
552 9
        if ($useCache) {
553 9
            $this->setResultCacheLifetime($lifetime);
554 9
            $this->setResultCacheId($resultCacheId);
555
556 9
            return $this;
557
        }
558
559 1
        $this->queryCacheProfile = null;
560
561 1
        return $this;
562
    }
563
564
    /**
565
     * Defines how long the result cache will be active before expire.
566
     *
567
     * @param int $lifetime How long the cache entry is valid, in seconds.
568
     *
569
     * @return static This query instance.
570
     */
571 10
    public function setResultCacheLifetime($lifetime)
572
    {
573 10
        $lifetime = $lifetime !== null ? (int) $lifetime : 0;
0 ignored issues
show
introduced by
The condition $lifetime !== null is always true.
Loading history...
574
575 10
        $this->queryCacheProfile = $this->queryCacheProfile
576 4
            ? $this->queryCacheProfile->setLifetime($lifetime)
577 6
            : new QueryCacheProfile($lifetime, null, $this->em->getConfiguration()->getResultCacheImpl());
578
579 10
        return $this;
580
    }
581
582
    /**
583
     * Retrieves the lifetime of resultset cache.
584
     *
585
     * @deprecated
586
     *
587
     * @return int
588
     */
589
    public function getResultCacheLifetime()
590
    {
591
        return $this->queryCacheProfile ? $this->queryCacheProfile->getLifetime() : 0;
592
    }
593
594
    /**
595
     * Defines if the result cache is active or not.
596
     *
597
     * @param bool $expire Whether or not to force resultset cache expiration.
598
     *
599
     * @return static This query instance.
600
     */
601 4
    public function expireResultCache($expire = true)
602
    {
603 4
        $this->expireResultCache = $expire;
604
605 4
        return $this;
606
    }
607
608
    /**
609
     * Retrieves if the resultset cache is active or not.
610
     *
611
     * @return bool
612
     */
613 8
    public function getExpireResultCache()
614
    {
615 8
        return $this->expireResultCache;
616
    }
617
618
    /**
619
     * @return QueryCacheProfile
620
     */
621 1
    public function getQueryCacheProfile()
622
    {
623 1
        return $this->queryCacheProfile;
624
    }
625
626
    /**
627
     * Change the default fetch mode of an association for this query.
628
     *
629
     * $fetchMode can be one of FetchMode::EAGER, FetchMode::LAZY or FetchMode::EXTRA_LAZY
630
     *
631
     * @param string $class
632
     * @param string $assocName
633
     * @param int    $fetchMode
634
     *
635
     * @return static This query instance.
636
     */
637 3
    public function setFetchMode($class, $assocName, $fetchMode)
638
    {
639 3
        if ($fetchMode !== Mapping\FetchMode::EAGER) {
0 ignored issues
show
introduced by
The condition $fetchMode !== Doctrine\...apping\FetchMode::EAGER is always true.
Loading history...
640
            $fetchMode = Mapping\FetchMode::LAZY;
641
        }
642
643 3
        $this->hints['fetchMode'][$class][$assocName] = $fetchMode;
644
645 3
        return $this;
646
    }
647
648
    /**
649
     * Defines the processing mode to be used during hydration / result set transformation.
650
     *
651
     * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
652
     *                                  One of the Query::HYDRATE_* constants.
653
     *
654
     * @return static This query instance.
655
     */
656 383
    public function setHydrationMode($hydrationMode)
657
    {
658 383
        $this->hydrationMode = $hydrationMode;
659
660 383
        return $this;
661
    }
662
663
    /**
664
     * Gets the hydration mode currently used by the query.
665
     *
666
     * @return string|int
667
     */
668 667
    public function getHydrationMode()
669
    {
670 667
        return $this->hydrationMode;
671
    }
672
673
    /**
674
     * Gets the list of results for the query.
675
     *
676
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
677
     *
678
     * @param string|int $hydrationMode
679
     *
680
     * @return mixed
681
     */
682 302
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
683
    {
684 302
        return $this->execute(null, $hydrationMode);
685
    }
686
687
    /**
688
     * Gets the array of results for the query.
689
     *
690
     * Alias for execute(null, HYDRATE_ARRAY).
691
     *
692
     * @return mixed[]
693
     */
694 26
    public function getArrayResult()
695
    {
696 26
        return $this->execute(null, self::HYDRATE_ARRAY);
697
    }
698
699
    /**
700
     * Gets the scalar results for the query.
701
     *
702
     * Alias for execute(null, HYDRATE_SCALAR).
703
     *
704
     * @return mixed[]
705
     */
706 87
    public function getScalarResult()
707
    {
708 87
        return $this->execute(null, self::HYDRATE_SCALAR);
709
    }
710
711
    /**
712
     * Get exactly one result or null.
713
     *
714
     * @param string|int $hydrationMode
715
     *
716
     * @return mixed
717
     *
718
     * @throws NonUniqueResultException
719
     */
720 16
    public function getOneOrNullResult($hydrationMode = null)
721
    {
722
        try {
723 16
            $result = $this->execute(null, $hydrationMode);
724 1
        } catch (NoResultException $e) {
725 1
            return null;
726
        }
727
728 15
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
729 1
            return null;
730
        }
731
732 14
        if (! is_array($result)) {
733 1
            return $result;
734
        }
735
736 14
        if (count($result) > 1) {
737 1
            throw new NonUniqueResultException();
738
        }
739
740 13
        return array_shift($result);
741
    }
742
743
    /**
744
     * Gets the single result of the query.
745
     *
746
     * Enforces the presence as well as the uniqueness of the result.
747
     *
748
     * If the result is not unique, a NonUniqueResultException is thrown.
749
     * If there is no result, a NoResultException is thrown.
750
     *
751
     * @param string|int $hydrationMode
752
     *
753
     * @return mixed
754
     *
755
     * @throws NonUniqueResultException If the query result is not unique.
756
     * @throws NoResultException        If the query returned no result.
757
     */
758 103
    public function getSingleResult($hydrationMode = null)
759
    {
760 103
        $result = $this->execute(null, $hydrationMode);
761
762 97
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
763 2
            throw new NoResultException();
764
        }
765
766 96
        if (! is_array($result)) {
767 9
            return $result;
768
        }
769
770 88
        if (count($result) > 1) {
771 1
            throw new NonUniqueResultException();
772
        }
773
774 87
        return array_shift($result);
775
    }
776
777
    /**
778
     * Gets the single scalar result of the query.
779
     *
780
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
781
     *
782
     * @return mixed
783
     *
784
     * @throws NonUniqueResultException If the query result is not unique.
785
     * @throws NoResultException        If the query returned no result.
786
     */
787 11
    public function getSingleScalarResult()
788
    {
789 11
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
790
    }
791
792
    /**
793
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
794
     *
795
     * @param string $name  The name of the hint.
796
     * @param mixed  $value The value of the hint.
797
     *
798
     * @return static This query instance.
799
     */
800 489
    public function setHint($name, $value)
801
    {
802 489
        $this->hints[$name] = $value;
803
804 489
        return $this;
805
    }
806
807
    /**
808
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
809
     *
810
     * @param string $name The name of the hint.
811
     *
812
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
813
     */
814 812
    public function getHint($name)
815
    {
816 812
        return $this->hints[$name] ?? false;
817
    }
818
819
    /**
820
     * Check if the query has a hint
821
     *
822
     * @param string $name The name of the hint
823
     *
824
     * @return bool False if the query does not have any hint
825
     */
826 19
    public function hasHint($name)
827
    {
828 19
        return isset($this->hints[$name]);
829
    }
830
831
    /**
832
     * Return the key value map of query hints that are currently set.
833
     *
834
     * @return mixed[]
835
     */
836 139
    public function getHints()
837
    {
838 139
        return $this->hints;
839
    }
840
841
    /**
842
     * Executes the query and returns an IterableResult that can be used to incrementally
843
     * iterate over the result.
844
     *
845
     * @param ArrayCollection|mixed[]|null $parameters    The query parameters.
846
     * @param string|int|null              $hydrationMode The hydration mode to use.
847
     *
848
     * @return IterableResult
849
     */
850 10
    public function iterate($parameters = null, $hydrationMode = null)
851
    {
852 10
        if ($hydrationMode !== null) {
853 10
            $this->setHydrationMode($hydrationMode);
854
        }
855
856 10
        if (! empty($parameters)) {
857 1
            $this->setParameters($parameters);
858
        }
859
860 10
        $rsm  = $this->getResultSetMapping();
861 7
        $stmt = $this->doExecute();
862
863 7
        return $this->em->newHydrator($this->hydrationMode)->iterate($stmt, $rsm, $this->hints);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Internal\Hy...ractHydrator::iterate() has been deprecated. ( Ignorable by Annotation )

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

863
        return /** @scrutinizer ignore-deprecated */ $this->em->newHydrator($this->hydrationMode)->iterate($stmt, $rsm, $this->hints);
Loading history...
864
    }
865
866
    /**
867
     * Executes the query and returns an iterable that can be used to incrementally
868
     * iterate over the result.
869
     *
870
     * @param ArrayCollection|mixed[]|null $parameters    The query parameters.
871
     * @param string|int|null              $hydrationMode The hydration mode to use.
872
     *
873
     * @return iterable<mixed>
874
     */
875 26
    public function getIterable(?iterable $parameters = null, $hydrationMode = null) : iterable
876
    {
877 26
        if ($hydrationMode !== null) {
878 26
            $this->setHydrationMode($hydrationMode);
879
        }
880
881 26
        if ($parameters !== null
882
            && (
883 1
                ($this->isCountable($parameters) && count($parameters) !== 0)
884 26
                || iterator_count($parameters) !== 0
885
            )
886
        ) {
887 1
            $this->setParameters($parameters);
888
        }
889
890 26
        $rsm  = $this->getResultSetMapping();
891 23
        $stmt = $this->doExecute();
892
893 23
        return $this->em->newHydrator($this->hydrationMode)->getIterable($stmt, $rsm, $this->hints);
894
    }
895
896
    /**
897
     * Executes the query.
898
     *
899
     * @param ArrayCollection|mixed[]|null $parameters    Query parameters.
900
     * @param string|int|null              $hydrationMode Processing mode to be used during the hydration process.
901
     *
902
     * @return mixed
903
     */
904 474
    public function execute($parameters = null, $hydrationMode = null)
905
    {
906 474
        return $this->isCacheEnabled()
907 29
            ? $this->executeUsingQueryCache($parameters, $hydrationMode)
908 471
            : $this->executeIgnoreQueryCache($parameters, $hydrationMode);
909
    }
910
911
    /**
912
     * Execute query ignoring second level cache.
913
     *
914
     * @param ArrayCollection|mixed[]|null $parameters
915
     * @param string|int|null              $hydrationMode
916
     *
917
     * @return mixed
918
     */
919 474
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
920
    {
921 474
        if ($hydrationMode !== null) {
922 369
            $this->setHydrationMode($hydrationMode);
923
        }
924
925 474
        if (! empty($parameters)) {
926
            $this->setParameters($parameters);
927
        }
928
929
        $setCacheEntry = static function () {
930 474
        };
931
932 474
        if ($this->hydrationCacheProfile !== null) {
933 2
            [$cacheKey, $realCacheKey] = $this->getHydrationCacheId();
934
935 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
936 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
937 2
            $result            = $cache->fetch($cacheKey);
938
939 2
            if (isset($result[$realCacheKey])) {
940 2
                return $result[$realCacheKey];
941
            }
942
943 2
            if (! $result) {
944 2
                $result = [];
945
            }
946
947
            $setCacheEntry = static function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
948 2
                $result[$realCacheKey] = $data;
949
950 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
951 2
            };
952
        }
953
954 474
        $stmt = $this->doExecute();
955
956 466
        if (is_numeric($stmt)) {
0 ignored issues
show
introduced by
The condition is_numeric($stmt) is always false.
Loading history...
957 27
            $setCacheEntry($stmt);
958
959 27
            return $stmt;
960
        }
961
962 451
        $rsm  = $this->getResultSetMapping();
963 451
        $data = $this->em->newHydrator($this->hydrationMode)->hydrateAll($stmt, $rsm, $this->hints);
964
965 447
        $setCacheEntry($data);
966
967 447
        return $data;
968
    }
969
970
    /**
971
     * Load from second level cache or executes the query and put into cache.
972
     *
973
     * @param ArrayCollection|mixed[]|null $parameters
974
     * @param string|int|null              $hydrationMode
975
     *
976
     * @return mixed
977
     */
978 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
979
    {
980 29
        $rsm        = $this->getResultSetMapping();
981 29
        $queryCache = $this->em->getCache()->getQueryCache($this->cacheRegion);
982 29
        $queryKey   = new QueryCacheKey(
983 29
            $this->getHash(),
984 29
            $this->lifetime,
985 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
986 29
            $this->getTimestampKey()
987
        );
988
989 29
        $result = $queryCache->get($queryKey, $rsm, $this->hints);
990
991 29
        if ($result !== null) {
992 16
            if ($this->cacheLogger) {
993 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
994
            }
995
996 16
            return $result;
997
        }
998
999 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
1000 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->hints);
1001
1002 26
        if ($this->cacheLogger) {
1003 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
1004
1005 26
            if ($cached) {
1006 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
1007
            }
1008
        }
1009
1010 26
        return $result;
1011
    }
1012
1013
    /**
1014
     * @return TimestampCacheKey|null
1015
     */
1016 29
    private function getTimestampKey()
1017
    {
1018 29
        $entityName = reset($this->resultSetMapping->aliasMap);
1019
1020 29
        if (empty($entityName)) {
1021 2
            return null;
1022
        }
1023
1024 27
        $metadata = $this->em->getClassMetadata($entityName);
1025
1026 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

1026
        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...
1027
    }
1028
1029
    /**
1030
     * Get the result cache id to use to store the result set cache entry.
1031
     * Will return the configured id if it exists otherwise a hash will be
1032
     * automatically generated for you.
1033
     *
1034
     * @return string[] ($key, $hash)
1035
     */
1036 2
    protected function getHydrationCacheId()
1037
    {
1038 2
        $parameters = [];
1039
1040 2
        foreach ($this->getParameters() as $parameter) {
1041 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1042
        }
1043
1044 2
        $sql                    = $this->getSQL();
1045 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1046 2
        $hints                  = $this->getHints();
1047 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1048
1049 2
        ksort($hints);
1050
1051 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1052
    }
1053
1054
    /**
1055
     * Set the result cache id to use to store the result set cache entry.
1056
     * If this is not explicitly set by the developer then a hash is automatically
1057
     * generated for you.
1058
     *
1059
     * @param string $id
1060
     *
1061
     * @return static This query instance.
1062
     */
1063 12
    public function setResultCacheId($id)
1064
    {
1065 12
        $this->queryCacheProfile = $this->queryCacheProfile
1066 12
            ? $this->queryCacheProfile->setCacheKey($id)
1067
            : new QueryCacheProfile(0, $id, $this->em->getConfiguration()->getResultCacheImpl());
1068
1069 12
        return $this;
1070
    }
1071
1072
    /**
1073
     * Get the result cache id to use to store the result set cache entry if set.
1074
     *
1075
     * @deprecated
1076
     *
1077
     * @return string
1078
     */
1079
    public function getResultCacheId()
1080
    {
1081
        return $this->queryCacheProfile ? $this->queryCacheProfile->getCacheKey() : null;
1082
    }
1083
1084
    /**
1085
     * Executes the query and returns a the resulting Statement object.
1086
     *
1087
     * @return Statement The executed database statement that holds the results.
1088
     */
1089
    abstract protected function doExecute();
1090
1091
    /**
1092
     * Cleanup Query resource when clone is called.
1093
     */
1094 142
    public function __clone()
1095
    {
1096 142
        $this->parameters = new ArrayCollection();
1097 142
        $this->hints      = $this->em->getConfiguration()->getDefaultQueryHints();
1098 142
    }
1099
1100
    /**
1101
     * Generates a string of currently query to use for the cache second level cache.
1102
     *
1103
     * @return string
1104
     */
1105 29
    protected function getHash()
1106
    {
1107 29
        $query  = $this->getSQL();
1108 29
        $hints  = $this->getHints();
1109
        $params = array_map(function (Parameter $parameter) {
1110 5
            $value = $parameter->getValue();
1111
1112
            // Small optimization
1113
            // Does not invoke processParameterValue for scalar values
1114 5
            if (is_scalar($value)) {
1115 4
                return $value;
1116
            }
1117
1118 1
            return $this->processParameterValue($value);
1119 29
        }, $this->parameters->getValues());
1120
1121 29
        ksort($hints);
1122
1123 29
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1124
    }
1125
1126
    /** @param iterable<mixed> $subject */
1127 1
    private function isCountable(iterable $subject) : bool
1128
    {
1129 1
        return $subject instanceof Countable || is_array($subject);
1130
    }
1131
}
1132