Passed
Pull Request — master (#7885)
by Šimon
05:58
created

AbstractQuery::getResultSetMapping()   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 0
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 function array_map;
22
use function array_shift;
23
use function count;
24
use function is_array;
25
use function is_numeric;
26
use function is_object;
27
use function is_scalar;
28
use function iterator_count;
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|Parameter[]
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 string|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 982
    public function __construct(EntityManagerInterface $em)
148
    {
149 982
        $this->em         = $em;
150 982
        $this->parameters = new ArrayCollection();
151 982
        $this->hints      = $em->getConfiguration()->getDefaultQueryHints();
152 982
        $this->hasCache   = $this->em->getConfiguration()->isSecondLevelCacheEnabled();
153
154 982
        if ($this->hasCache) {
155 32
            $this->cacheLogger = $em->getConfiguration()
156 32
                ->getSecondLevelCacheConfiguration()
157 32
                ->getCacheLogger();
158
        }
159 982
    }
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 136
    public function setCacheable($cacheable)
169
    {
170 136
        $this->cacheable = (bool) $cacheable;
171
172 136
        return $this;
173
    }
174
175
    /**
176
     * @return bool TRUE if the query results are enable for second level cache, FALSE otherwise.
177
     */
178 92
    public function isCacheable()
179
    {
180 92
        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 474
    protected function isCacheEnabled()
209
    {
210 474
        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 906
    public function getEntityManager()
270
    {
271 906
        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 138
    public function getParameters()
292
    {
293 138
        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 270
    public function getParameter($key)
304
    {
305 270
        $filteredParameters = $this->parameters->filter(
306
            static function (Query\Parameter $parameter) use ($key) : bool {
307 154
                $parameterName = $parameter->getName();
308
309 154
                return $key === $parameterName || (string) $key === (string) $parameterName;
310 270
            }
311
        );
312
313 270
        return $filteredParameters->isEmpty() ? null : $filteredParameters->first();
314
    }
315
316
    /**
317
     * Sets a collection of query parameters.
318
     *
319
     * @param ArrayCollection|mixed[] $parameters
320
     *
321
     * @return static This query instance.
322
     */
323 193
    public function setParameters($parameters)
324
    {
325
        // BC compatibility with 2.3-
326 193
        if (is_array($parameters)) {
327 4
            $parameterCollection = new ArrayCollection();
328
329 4
            foreach ($parameters as $key => $value) {
330 4
                $parameterCollection->add(new Parameter($key, $value));
331
            }
332
333 4
            $parameters = $parameterCollection;
334
        }
335
336 193
        $this->parameters = $parameters;
337
338 193
        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 180
    public function setParameter($key, $value, $type = null)
353
    {
354 180
        $existingParameter = $this->getParameter($key);
355
356 180
        if ($existingParameter !== null) {
357 5
            $existingParameter->setValue($value, $type);
358
359 5
            return $this;
360
        }
361
362 178
        $this->parameters->add(new Parameter($key, $value, $type));
363
364 178
        return $this;
365
    }
366
367
    /**
368
     * Processes an individual parameter value.
369
     *
370
     * @param mixed $value
371
     *
372
     * @return string|mixed[]
373
     *
374
     * @throws ORMInvalidArgumentException
375
     */
376 174
    public function processParameterValue($value)
377
    {
378 174
        if (is_scalar($value)) {
379 163
            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 73
            foreach ($value as $key => $paramValue) {
392 73
                $paramValue  = $this->processParameterValue($paramValue);
393 73
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
394
            }
395
396 73
            return $value;
397
        }
398
399 17
        if (! is_object($value)) {
400 1
            return $value;
401
        }
402
403
        try {
404 16
            $value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
405
406 14
            if ($value === null) {
407 14
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
408
            }
409 3
        } catch (MappingException | CommonMappingException $e) {
410
            // Silence any mapping exceptions. These can occur if the object in
411
            // question is not a mapped entity, in which case we just don't do
412
            // any preparation on the value.
413
        }
414
415 16
        return $value;
416
    }
417
418
    /**
419
     * Sets the ResultSetMapping that should be used for hydration.
420
     *
421
     * @return static This query instance.
422
     */
423 22
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
424
    {
425 22
        $this->resultSetMapping = $rsm;
426
427 22
        return $this;
428
    }
429
430
    /**
431
     * Gets the ResultSetMapping used for hydration.
432
     *
433
     * @return ResultSetMapping
434
     */
435 14
    protected function getResultSetMapping()
436
    {
437 14
        return $this->resultSetMapping;
438
    }
439
440
    /**
441
     * Set a cache profile for hydration caching.
442
     *
443
     * If no result cache driver is set in the QueryCacheProfile, the default
444
     * result cache driver is used from the configuration.
445
     *
446
     * Important: Hydration caching does NOT register entities in the
447
     * UnitOfWork when retrieved from the cache. Never use result cached
448
     * entities for requests that also flush the EntityManager. If you want
449
     * some form of caching with UnitOfWork registration you should use
450
     * {@see AbstractQuery::setResultCacheProfile()}.
451
     *
452
     * @return static This query instance.
453
     *
454
     * @example
455
     * $lifetime = 100;
456
     * $resultKey = "abc";
457
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
458
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
459
     */
460 3
    public function setHydrationCacheProfile(?QueryCacheProfile $profile = null)
461
    {
462 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
463
            $resultCacheDriver = $this->em->getConfiguration()->getHydrationCacheImpl();
464
            $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

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

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

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

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

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