AbstractQuery   F
last analyzed

Complexity

Total Complexity 112

Size/Duplication

Total Lines 1055
Duplicated Lines 0 %

Test Coverage

Coverage 95.26%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 240
c 5
b 0
f 0
dl 0
loc 1055
ccs 261
cts 274
cp 0.9526
rs 2
wmc 112

53 Methods

Rating   Name   Duplication   Size   Complexity  
A getHint() 0 3 1
A hasHint() 0 3 1
A getHints() 0 3 1
A getScalarResult() 0 3 1
A getQueryCacheProfile() 0 3 1
A getArrayResult() 0 3 1
A getResultCacheLifetime() 0 3 2
A getResult() 0 3 1
A getResultSetMapping() 0 3 1
A getExpireResultCache() 0 3 1
A setParameter() 0 13 2
A expireResultCache() 0 5 1
A setHint() 0 5 1
A setResultCacheLifetime() 0 9 3
A getCacheMode() 0 3 1
A getParameters() 0 3 1
A setHydrationMode() 0 5 1
A getOneOrNullResult() 0 21 6
A getResultCacheDriver() 0 7 3
A setLifetime() 0 5 1
A getResultCacheId() 0 3 2
A __clone() 0 4 1
A useResultCache() 0 12 2
A free() 0 5 1
A getSingleResult() 0 17 5
A setCacheMode() 0 5 1
A setResultSetMapping() 0 5 1
A getEntityManager() 0 3 1
A setResultCacheId() 0 7 2
A isCacheEnabled() 0 3 2
A getParameter() 0 11 3
A getHydrationCacheId() 0 16 2
A setResultCacheDriver() 0 11 4
A iterate() 0 14 3
A getCacheRegion() 0 3 1
A getTimestampKey() 0 11 2
A setFetchMode() 0 9 2
A setCacheRegion() 0 5 1
A __construct() 0 11 2
A setParameters() 0 16 3
A isCacheable() 0 3 1
A getLifetime() 0 3 1
A getSingleScalarResult() 0 3 1
A setHydrationCacheProfile() 0 10 3
A getHydrationMode() 0 3 1
A getHydrationCacheProfile() 0 3 1
A executeUsingQueryCache() 0 33 6
A setResultCacheProfile() 0 10 3
A getHash() 0 19 2
A execute() 0 5 2
A setCacheable() 0 5 1
B processParameterValue() 0 40 11
B executeIgnoreQueryCache() 0 49 7

How to fix   Complexity   

Complex Class

Complex classes like AbstractQuery often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractQuery, and based on these observations, apply Extract Interface, too.

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

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

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

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

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