Failed Conditions
Pull Request — master (#6959)
by Matthew
19:32
created

AbstractQuery::setResultSetMapping()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

430
            return $this->em->getClassMetadata($alias)->/** @scrutinizer ignore-call */ getClassName();

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...
431 31
        };
432
433 31
        $rsm->aliasMap         = array_map($translate, $rsm->aliasMap);
434 31
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
435 31
    }
436
437
    /**
438
     * Set a cache profile for hydration caching.
439
     *
440
     * If no result cache driver is set in the QueryCacheProfile, the default
441
     * result cache driver is used from the configuration.
442
     *
443
     * Important: Hydration caching does NOT register entities in the
444
     * UnitOfWork when retrieved from the cache. Never use result cached
445
     * entities for requests that also flush the EntityManager. If you want
446
     * some form of caching with UnitOfWork registration you should use
447
     * {@see AbstractQuery::setResultCacheProfile()}.
448
     *
449
     * @example
450
     * $lifetime = 100;
451
     * $resultKey = "abc";
452
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
453
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
454
     *
455
     *
456
     * @return static This query instance.
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);
463
        }
464
465 3
        $this->hydrationCacheProfile = $profile;
466
467 3
        return $this;
468
    }
469
470
    /**
471
     * @return \Doctrine\DBAL\Cache\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
     *
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);
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)) {
511
            throw ORMException::invalidResultCacheDriver();
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   $bool
542
     * @param int    $lifetime
543
     * @param string $resultCacheId
544
     *
545
     * @return static This query instance.
546
     */
547 9
    public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
548
    {
549 9
        if ($bool) {
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.
565
     *
566
     * @return static This query instance.
567
     */
568 10
    public function setResultCacheLifetime($lifetime)
569
    {
570 10
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
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 3
    public function expireResultCache($expire = true)
599
    {
600 3
        $this->expireResultCache = $expire;
601
602 3
        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) {
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 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 371
    public function setHydrationMode($hydrationMode)
654
    {
655 371
        $this->hydrationMode = $hydrationMode;
656
657 371
        return $this;
658
    }
659
660
    /**
661
     * Gets the hydration mode currently used by the query.
662
     *
663
     * @return int
664
     */
665 646
    public function getHydrationMode()
666
    {
667 646
        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 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 25
    public function getArrayResult()
692
    {
693 25
        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 83
    public function getScalarResult()
704
    {
705 83
        return $this->execute(null, self::HYDRATE_SCALAR);
706
    }
707
708
    /**
709
     * Get exactly one result or null.
710
     *
711
     * @param int $hydrationMode
712
     *
713
     * @return mixed
714
     *
715
     * @throws NonUniqueResultException
716
     */
717 14
    public function getOneOrNullResult($hydrationMode = null)
718
    {
719
        try {
720 14
            $result = $this->execute(null, $hydrationMode);
721 1
        } catch (NoResultException $e) {
722 1
            return null;
723
        }
724
725 13
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
726 1
            return null;
727
        }
728
729 12
        if (! is_array($result)) {
730 1
            return $result;
731
        }
732
733 12
        if (count($result) > 1) {
734 1
            throw new NonUniqueResultException;
735
        }
736
737 11
        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 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 and hydration mode is not HYDRATE_SINGLE_SCALAR.
754
     */
755 95
    public function getSingleResult($hydrationMode = null)
756
    {
757 95
        $result = $this->execute(null, $hydrationMode);
758
759 89
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
760 2
            throw new NoResultException;
761
        }
762
763 88
        if (! is_array($result)) {
764 7
            return $result;
765
        }
766
767 82
        if (count($result) > 1) {
768 1
            throw new NonUniqueResultException;
769
        }
770
771 81
        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 The scalar result, or NULL if the query returned no result.
780
     *
781
     * @throws NonUniqueResultException If the query result is not unique.
782
     */
783 9
    public function getSingleScalarResult()
784
    {
785 9
        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 462
    public function setHint($name, $value)
797
    {
798 462
        $this->hints[$name] = $value;
799
800 462
        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 725
    public function getHint($name)
811
    {
812 725
        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 15
    public function hasHint($name)
823
    {
824 15
        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 130
    public function getHints()
833
    {
834 130
        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|array|Parameter[]|mixed[]|null $parameters    The query parameters.
842
     * @param int|null                                       $hydrationMode The hydration mode to use.
843
     *
844
     * @return \Doctrine\ORM\Internal\Hydration\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|array|Parameter[]|mixed[]|null $parameters    Query parameters.
866
     * @param int|null                                       $hydrationMode Processing mode to be used during the hydration process.
867
     *
868
     * @return mixed
869
     */
870 455
    public function execute($parameters = null, $hydrationMode = null)
871
    {
872 455
        if ($this->cacheable && $this->isCacheEnabled()) {
873 29
            return $this->executeUsingQueryCache($parameters, $hydrationMode);
874
        }
875
876 428
        return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
877
    }
878
879
    /**
880
     * Execute query ignoring second level cache.
881
     *
882
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters
883
     * @param int|null                                       $hydrationMode
884
     *
885
     * @return mixed
886
     */
887 453
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
888
    {
889 453
        if ($hydrationMode !== null) {
890 361
            $this->setHydrationMode($hydrationMode);
891
        }
892
893 453
        if (! empty($parameters)) {
894
            $this->setParameters($parameters);
895
        }
896
897 453
        $setCacheEntry = function () {
898 453
        };
899
900 453
        if ($this->hydrationCacheProfile !== null) {
901 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
902
903 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
904 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
905 2
            $result            = $cache->fetch($cacheKey);
906
907 2
            if (isset($result[$realCacheKey])) {
908 2
                return $result[$realCacheKey];
909
            }
910
911 2
            if (! $result) {
912 2
                $result = [];
913
            }
914
915 2
            $setCacheEntry = function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
916 2
                $result[$realCacheKey] = $data;
917
918 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
919 2
            };
920
        }
921
922 453
        $stmt = $this->doExecute();
923
924 440
        if (is_numeric($stmt)) {
925 1
            $setCacheEntry($stmt);
926
927 1
            return $stmt;
928
        }
929
930 439
        $rsm  = $this->getResultSetMapping();
931 439
        $data = $this->em->newHydrator($this->hydrationMode)->hydrateAll($stmt, $rsm, $this->hints);
932
933 435
        $setCacheEntry($data);
934
935 435
        return $data;
936
    }
937
938
    /**
939
     * Load from second level cache or executes the query and put into cache.
940
     *
941
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters
942
     * @param int|null                                       $hydrationMode
943
     *
944
     * @return mixed
945
     */
946 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
947
    {
948 29
        $rsm        = $this->getResultSetMapping();
949 27
        $queryCache = $this->em->getCache()->getQueryCache($this->cacheRegion);
950 27
        $queryKey   = new QueryCacheKey(
951 27
            $this->getHash(),
952 27
            $this->lifetime,
953 27
            $this->cacheMode ?: Cache::MODE_NORMAL,
954 27
            $this->getTimestampKey()
955
        );
956
957 27
        $result = $queryCache->get($queryKey, $rsm, $this->hints);
958
959 27
        if ($result !== null) {
960 16
            if ($this->cacheLogger) {
961 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
962
            }
963
964 16
            return $result;
965
        }
966
967 27
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
968 27
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->hints);
969
970 26
        if ($this->cacheLogger) {
971 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
972
973 26
            if ($cached) {
974 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
975
            }
976
        }
977
978 26
        return $result;
979
    }
980
981
    /**
982
     * @return \Doctrine\ORM\Cache\TimestampCacheKey|null
983
     */
984 27
    private function getTimestampKey()
985
    {
986 27
        $entityName = reset($this->resultSetMapping->aliasMap);
987
988 27
        if (empty($entityName)) {
989
            return null;
990
        }
991
992 27
        $metadata = $this->em->getClassMetadata($entityName);
993
994 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

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