Failed Conditions
Pull Request — master (#7885)
by Šimon
09:39
created

AbstractQuery::getIterable()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

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

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

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

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

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

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