Failed Conditions
Pull Request — master (#7130)
by Michael
13:25 queued 18s
created

AbstractQuery::setResultSetMapping()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
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\Cache\Cache as CommonCache;
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\Common\Collections\Collection;
10
use Doctrine\DBAL\Cache\QueryCacheProfile;
11
use Doctrine\DBAL\Driver\ResultStatement;
12
use Doctrine\ORM\Cache\Logging\CacheLogger;
13
use Doctrine\ORM\Cache\QueryCacheKey;
14
use Doctrine\ORM\Cache\TimestampCacheKey;
15
use Doctrine\ORM\Internal\Hydration\IterableResult;
16
use Doctrine\ORM\Query\Parameter;
17
use Doctrine\ORM\Query\ResultSetMapping;
18
use Doctrine\ORM\Utility\StaticClassNameConverter;
19
use function array_map;
20
use function array_shift;
21
use function count;
22
use function is_array;
23
use function is_numeric;
24
use function is_object;
25
use function is_scalar;
26
use function ksort;
27
use function reset;
28
use function serialize;
29
use function sha1;
30
31
/**
32
 * Base contract for ORM queries. Base class for Query and NativeQuery.
33
 */
34
abstract class AbstractQuery
35
{
36
    /* Hydration mode constants */
37
38
    /**
39
     * Hydrates an object graph. This is the default behavior.
40
     */
41
    public const HYDRATE_OBJECT = 1;
42
43
    /**
44
     * Hydrates an array graph.
45
     */
46
    public const HYDRATE_ARRAY = 2;
47
48
    /**
49
     * Hydrates a flat, rectangular result set with scalar values.
50
     */
51
    public const HYDRATE_SCALAR = 3;
52
53
    /**
54
     * Hydrates a single scalar value.
55
     */
56
    public const HYDRATE_SINGLE_SCALAR = 4;
57
58
    /**
59
     * Very simple object hydrator (optimized for performance).
60
     */
61
    public const HYDRATE_SIMPLEOBJECT = 5;
62
63
    /**
64
     * The parameter map of this query.
65
     *
66
     * @var ArrayCollection
67
     */
68
    protected $parameters;
69
70
    /**
71
     * The user-specified ResultSetMapping to use.
72
     *
73
     * @var ResultSetMapping
74
     */
75
    protected $resultSetMapping;
76
77
    /**
78
     * The entity manager used by this query object.
79
     *
80
     * @var EntityManagerInterface
81
     */
82
    protected $em;
83
84
    /**
85
     * The map of query hints.
86
     *
87
     * @var mixed[]
88
     */
89
    protected $hints = [];
90
91
    /**
92
     * The hydration mode.
93
     *
94
     * @var int|string
95
     */
96
    protected $hydrationMode = self::HYDRATE_OBJECT;
97
98
    /** @var QueryCacheProfile */
99
    protected $queryCacheProfile;
100
101
    /**
102
     * Whether or not expire the result cache.
103
     *
104
     * @var bool
105
     */
106
    protected $expireResultCache = false;
107
108
    /** @var QueryCacheProfile */
109
    protected $hydrationCacheProfile;
110
111
    /**
112
     * Whether to use second level cache, if available.
113
     *
114
     * @var bool
115
     */
116
    protected $cacheable = false;
117
118
    /** @var bool */
119
    protected $hasCache = false;
120
121
    /**
122
     * Second level cache region name.
123
     *
124
     * @var string|null
125
     */
126
    protected $cacheRegion;
127
128
    /**
129
     * Second level query cache mode.
130
     *
131
     * @var int|null
132
     */
133
    protected $cacheMode;
134
135
    /** @var CacheLogger|null */
136
    protected $cacheLogger;
137
138
    /** @var int */
139
    protected $lifetime = 0;
140
141
    /**
142
     * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
143
     */
144 967
    public function __construct(EntityManagerInterface $em)
145
    {
146 967
        $this->em         = $em;
147 967
        $this->parameters = new ArrayCollection();
148 967
        $this->hints      = $em->getConfiguration()->getDefaultQueryHints();
149 967
        $this->hasCache   = $this->em->getConfiguration()->isSecondLevelCacheEnabled();
150
151 967
        if ($this->hasCache) {
152 32
            $this->cacheLogger = $em->getConfiguration()
153 32
                ->getSecondLevelCacheConfiguration()
154 32
                ->getCacheLogger();
155
        }
156 967
    }
157
158
    /**
159
     * Enable/disable second level query (result) caching for this query.
160
     *
161
     * @return static This query instance.
162
     */
163 133
    public function setCacheable(bool $cacheable) : self
164
    {
165 133
        $this->cacheable = (bool) $cacheable;
166
167 133
        return $this;
168
    }
169
170
    /**
171
     * @return bool TRUE if the query results are enable for second level cache, FALSE otherwise.
172
     */
173 90
    public function isCacheable() : bool
174
    {
175 90
        return $this->cacheable;
176
    }
177
178
    /**
179
     * @return static This query instance.
180
     */
181 2
    public function setCacheRegion(string $cacheRegion) : self
182
    {
183 2
        $this->cacheRegion = $cacheRegion;
184
185 2
        return $this;
186
    }
187
188
    /**
189
     * Obtain the name of the second level query cache region in which query results will be stored
190
     *
191
     * @return string|null The cache region name; NULL indicates the default region.
192
     */
193 1
    public function getCacheRegion() : ?string
194
    {
195 1
        return $this->cacheRegion;
196
    }
197
198
    /**
199
     * @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise.
200
     */
201 465
    protected function isCacheEnabled() : bool
202
    {
203 465
        return $this->cacheable && $this->hasCache;
204
    }
205
206 1
    public function getLifetime() : int
207
    {
208 1
        return $this->lifetime;
209
    }
210
211
    /**
212
     * Sets the life-time for this query into second level cache.
213
     *
214
     * @return AbstractQuery This query instance.
215
     */
216 2
    public function setLifetime(int $lifetime) : self
217
    {
218 2
        $this->lifetime = $lifetime;
219
220 2
        return $this;
221
    }
222
223 1
    public function getCacheMode() : ?int
224
    {
225 1
        return $this->cacheMode;
226
    }
227
228
    /**
229
     * @return AbstractQuery This query instance.
230
     */
231 4
    public function setCacheMode(int $cacheMode) : self
232
    {
233 4
        $this->cacheMode = (int) $cacheMode;
234
235 4
        return $this;
236
    }
237
238
    /**
239
     * Gets the SQL query that corresponds to this query object.
240
     * The returned SQL syntax depends on the connection driver that is used
241
     * by this query object at the time of this method call.
242
     *
243
     * @return string|string[] SQL query
244
     */
245
    abstract public function getSQL();
246
247
    /**
248
     * Retrieves the associated EntityManager of this Query instance.
249
     */
250 890
    public function getEntityManager() : EntityManagerInterface
251
    {
252 890
        return $this->em;
253
    }
254
255
    /**
256
     * Frees the resources used by the query object.
257
     *
258
     * Resets Parameters, Parameter Types and Query Hints.
259
     */
260 220
    public function free() : void
261
    {
262 220
        $this->parameters = new ArrayCollection();
263
264 220
        $this->hints = $this->em->getConfiguration()->getDefaultQueryHints();
265 220
    }
266
267
    /**
268
     * Get all defined parameters.
269
     *
270
     * @return ArrayCollection The defined query parameters.
271
     */
272 135
    public function getParameters() : ArrayCollection
273
    {
274 135
        return $this->parameters;
275
    }
276
277
    /**
278
     * Gets a query parameter.
279
     *
280
     * @param mixed $key The key (index or name) of the bound parameter.
281
     *
282
     * @return Parameter|null The value of the bound parameter, or NULL if not available.
283
     */
284 266
    public function getParameter($key) : ?Parameter
285
    {
286 266
        $filteredParameters = $this->parameters->filter(
287 266
            function (Parameter $parameter) use ($key) : bool {
288 150
                $parameterName = $parameter->getName();
289
290 150
                return $key === $parameterName || (string) $key === (string) $parameterName;
291 266
            }
292
        );
293
294 266
        return $filteredParameters->isEmpty() ? null : $filteredParameters->first();
295
    }
296
297
    /**
298
     * Sets a collection of query parameters.
299
     *
300
     * @param ArrayCollection|array|Parameter[]|mixed[] $parameters
301
     *
302
     * @return static This query instance.
303
     */
304 190
    public function setParameters(iterable $parameters) : self
305
    {
306
        // BC compatibility with 2.3-
307 190
        if (is_array($parameters)) {
308 1
            $parameterCollection = new ArrayCollection();
309
310 1
            foreach ($parameters as $key => $value) {
311 1
                $parameterCollection->add(new Parameter($key, $value));
312
            }
313
314 1
            $parameters = $parameterCollection;
315
        }
316
317 190
        $this->parameters = $parameters;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parameters can also be of type iterable. However, the property $parameters is declared as type Doctrine\Common\Collections\ArrayCollection. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
318
319 190
        return $this;
320
    }
321
322
    /**
323
     * Sets a query parameter.
324
     *
325
     * @param string|int      $key   The parameter position or name.
326
     * @param mixed           $value The parameter value.
327
     * @param string|int|null $type  The parameter type. If specified, the given value will be run through
328
     *                               the type conversion of this type. This is usually not needed for
329
     *                               strings and numeric types.
330
     *
331
     * @return static This query instance.
332
     */
333 176
    public function setParameter($key, $value, $type = null) : self
334
    {
335 176
        $existingParameter = $this->getParameter($key);
336
337 176
        if ($existingParameter !== null) {
338 5
            $existingParameter->setValue($value, $type);
339
340 5
            return $this;
341
        }
342
343 174
        $this->parameters->add(new Parameter($key, $value, $type));
344
345 174
        return $this;
346
    }
347
348
    /**
349
     * Processes an individual parameter value.
350
     *
351
     * @param mixed $value
352
     *
353
     * @return string|mixed[]
354
     *
355
     * @throws ORMInvalidArgumentException
356
     */
357 173
    public function processParameterValue($value)
358
    {
359 173
        if (is_scalar($value)) {
360 161
            return $value;
361
        }
362
363 88
        if ($value instanceof Mapping\ClassMetadata) {
364 1
            return $value->discriminatorValue ?: $value->getClassName();
365
        }
366
367 87
        if ($value instanceof Collection) {
368 1
            $value = $value->toArray();
369
        }
370
371 87
        if (is_array($value)) {
372 72
            foreach ($value as $key => $paramValue) {
373 72
                $paramValue  = $this->processParameterValue($paramValue);
374 72
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
375
            }
376
377 72
            return $value;
378
        }
379
380 18
        if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(StaticClassNameConverter::getClass($value))) {
381 13
            $value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
382
383 13
            if ($value === null) {
384
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
385
            }
386
        }
387
388 18
        return $value;
389
    }
390
391
    /**
392
     * Sets the ResultSetMapping that should be used for hydration.
393
     *
394
     * @return static This query instance.
395
     */
396 22
    public function setResultSetMapping(ResultSetMapping $rsm) : self
397
    {
398 22
        $this->resultSetMapping = $rsm;
399
400 22
        return $this;
401
    }
402
403
    /**
404
     * Gets the ResultSetMapping used for hydration.
405
     */
406 14
    protected function getResultSetMapping() : ResultSetMapping
407
    {
408 14
        return $this->resultSetMapping;
409
    }
410
411
    /**
412
     * Set a cache profile for hydration caching.
413
     *
414
     * If no result cache driver is set in the QueryCacheProfile, the default
415
     * result cache driver is used from the configuration.
416
     *
417
     * Important: Hydration caching does NOT register entities in the
418
     * UnitOfWork when retrieved from the cache. Never use result cached
419
     * entities for requests that also flush the EntityManager. If you want
420
     * some form of caching with UnitOfWork registration you should use
421
     * {@see AbstractQuery::setResultCacheProfile()}.
422
     *
423
     * @example
424
     * $lifetime = 100;
425
     * $resultKey = "abc";
426
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
427
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
428
     *
429
     * @return static This query instance.
430
     */
431 3
    public function setHydrationCacheProfile(?QueryCacheProfile $profile = null) : self
432
    {
433 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
434
            $resultCacheDriver = $this->em->getConfiguration()->getHydrationCacheImpl();
435
            $profile           = $profile->setResultCacheDriver($resultCacheDriver);
436
        }
437
438 3
        $this->hydrationCacheProfile = $profile;
439
440 3
        return $this;
441
    }
442
443 3
    public function getHydrationCacheProfile() : ?QueryCacheProfile
444
    {
445 3
        return $this->hydrationCacheProfile;
446
    }
447
448
    /**
449
     * Set a cache profile for the result cache.
450
     *
451
     * If no result cache driver is set in the QueryCacheProfile, the default
452
     * result cache driver is used from the configuration.
453
     *
454
     * @return static This query instance.
455
     */
456 1
    public function setResultCacheProfile(?QueryCacheProfile $profile = null) : self
457
    {
458 1
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
459
            $resultCacheDriver = $this->em->getConfiguration()->getResultCacheImpl();
460
            $profile           = $profile->setResultCacheDriver($resultCacheDriver);
461
        }
462
463 1
        $this->queryCacheProfile = $profile;
464
465 1
        return $this;
466
    }
467
468
    /**
469
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
470
     *
471
     * @param CommonCache|null $resultCacheDriver Cache driver
472
     *
473
     * @return static This query instance.
474
     *
475
     * @throws ORMException
476
     */
477 8
    public function setResultCacheDriver(?CommonCache $resultCacheDriver = null) : self
478
    {
479 8
        $this->queryCacheProfile = $this->queryCacheProfile
480 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

480
            ? $this->queryCacheProfile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver)
Loading history...
481 7
            : new QueryCacheProfile(0, null, $resultCacheDriver);
482
483 8
        return $this;
484
    }
485
486
    /**
487
     * Returns the cache driver used for caching result sets.
488
     *
489
     * @deprecated
490
     *
491
     * @return CommonCache Cache driver
492
     */
493 3
    public function getResultCacheDriver() : CommonCache
494
    {
495 3
        if ($this->queryCacheProfile && $this->queryCacheProfile->getResultCacheDriver()) {
496 3
            return $this->queryCacheProfile->getResultCacheDriver();
497
        }
498
499
        return $this->em->getConfiguration()->getResultCacheImpl();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->em->getCon...)->getResultCacheImpl() could return the type null which is incompatible with the type-hinted return Doctrine\Common\Cache\Cache. Consider adding an additional type-check to rule them out.
Loading history...
500
    }
501
502
    /**
503
     * Set whether or not to cache the results of this query and if so, for
504
     * how long and which ID to use for the cache entry.
505
     *
506
     * @return static This query instance.
507
     */
508 9
    public function useResultCache(bool $bool, ?int $lifetime = null, ?string $resultCacheId = null) : self
509
    {
510 9
        if ($bool) {
511 9
            $this->setResultCacheLifetime($lifetime);
512 9
            $this->setResultCacheId($resultCacheId);
513
514 9
            return $this;
515
        }
516
517 1
        $this->queryCacheProfile = null;
518
519 1
        return $this;
520
    }
521
522
    /**
523
     * Defines how long the result cache will be active before expire.
524
     *
525
     * @param int|null $lifetime How long the cache entry is valid.
526
     *
527
     * @return static This query instance.
528
     */
529 10
    public function setResultCacheLifetime(?int $lifetime) : self
530
    {
531 10
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
532
533 10
        $this->queryCacheProfile = $this->queryCacheProfile
534 4
            ? $this->queryCacheProfile->setLifetime($lifetime)
535 6
            : new QueryCacheProfile($lifetime, null, $this->em->getConfiguration()->getResultCacheImpl());
536
537 10
        return $this;
538
    }
539
540
    /**
541
     * Retrieves the lifetime of resultset cache.
542
     *
543
     * @deprecated
544
     */
545
    public function getResultCacheLifetime() : int
546
    {
547
        return $this->queryCacheProfile ? $this->queryCacheProfile->getLifetime() : 0;
548
    }
549
550
    /**
551
     * Defines if the result cache is active or not.
552
     *
553
     * @param bool $expire Whether or not to force resultset cache expiration.
554
     *
555
     * @return static This query instance.
556
     */
557 4
    public function expireResultCache(bool $expire = true) : self
558
    {
559 4
        $this->expireResultCache = $expire;
560
561 4
        return $this;
562
    }
563
564
    /**
565
     * Retrieves if the resultset cache is active or not.
566
     */
567 8
    public function getExpireResultCache() : bool
568
    {
569 8
        return $this->expireResultCache;
570
    }
571
572 1
    public function getQueryCacheProfile() : QueryCacheProfile
573
    {
574 1
        return $this->queryCacheProfile;
575
    }
576
577
    /**
578
     * Change the default fetch mode of an association for this query.
579
     *
580
     * @param string $fetchMode can be one of FetchMode::EAGER, FetchMode::LAZY or FetchMode::EXTRA_LAZY
581
     *
582
     * @return static This query instance.
583
     */
584 3
    public function setFetchMode(string $class, string $assocName, string $fetchMode) : self
585
    {
586 3
        if ($fetchMode !== Mapping\FetchMode::EAGER) {
587
            $fetchMode = Mapping\FetchMode::LAZY;
588
        }
589
590 3
        $this->hints['fetchMode'][$class][$assocName] = $fetchMode;
591
592 3
        return $this;
593
    }
594
595
    /**
596
     * Defines the processing mode to be used during hydration / result set transformation.
597
     *
598
     * @param int|string $hydrationMode Doctrine processing mode to be used during hydration process.
599
     *                                  One of the Query::HYDRATE_* constants.
600
     *
601
     * @return static This query instance.
602
     */
603 372
    public function setHydrationMode($hydrationMode) : self
604
    {
605 372
        $this->hydrationMode = $hydrationMode;
606
607 372
        return $this;
608
    }
609
610
    /**
611
     * Gets the hydration mode currently used by the query.
612
     *
613
     * @return int|string
614
     */
615 653
    public function getHydrationMode()
616
    {
617 653
        return $this->hydrationMode;
618
    }
619
620
    /**
621
     * Gets the list of results for the query.
622
     *
623
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
624
     *
625
     * @param int|string $hydrationMode
626
     *
627
     * @return mixed
628
     */
629 293
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
630
    {
631 293
        return $this->execute(null, $hydrationMode);
632
    }
633
634
    /**
635
     * Gets the array of results for the query.
636
     *
637
     * Alias for execute(null, HYDRATE_ARRAY).
638
     *
639
     * @return mixed[]
640
     */
641 24
    public function getArrayResult() : array
642
    {
643 24
        return $this->execute(null, self::HYDRATE_ARRAY);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute(null, self::HYDRATE_ARRAY) could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
644
    }
645
646
    /**
647
     * Gets the scalar results for the query.
648
     *
649
     * Alias for execute(null, HYDRATE_SCALAR).
650
     *
651
     * @return mixed[]
652
     */
653 87
    public function getScalarResult() : array
654
    {
655 87
        return $this->execute(null, self::HYDRATE_SCALAR);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->execute(null, self::HYDRATE_SCALAR) could return the type integer which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
656
    }
657
658
    /**
659
     * Get exactly one result or null.
660
     *
661
     * @param int|string|null $hydrationMode
662
     *
663
     * @return mixed
664
     *
665
     * @throws NonUniqueResultException
666
     */
667 16
    public function getOneOrNullResult($hydrationMode = null)
668
    {
669
        try {
670 16
            $result = $this->execute(null, $hydrationMode);
671 1
        } catch (NoResultException $e) {
672 1
            return null;
673
        }
674
675 15
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
676 1
            return null;
677
        }
678
679 14
        if (! is_array($result)) {
680 1
            return $result;
681
        }
682
683 14
        if (count($result) > 1) {
684 1
            throw new NonUniqueResultException();
685
        }
686
687 13
        return array_shift($result);
688
    }
689
690
    /**
691
     * Gets the single result of the query.
692
     *
693
     * Enforces the presence as well as the uniqueness of the result.
694
     *
695
     * If the result is not unique, a NonUniqueResultException is thrown.
696
     * If there is no result, a NoResultException is thrown.
697
     *
698
     * @param int|string|null $hydrationMode
699
     *
700
     * @return mixed
701
     *
702
     * @throws NonUniqueResultException If the query result is not unique.
703
     * @throws NoResultException        If the query returned no result.
704
     */
705 102
    public function getSingleResult($hydrationMode = null)
706
    {
707 102
        $result = $this->execute(null, $hydrationMode);
708
709 96
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
710 2
            throw new NoResultException();
711
        }
712
713 95
        if (! is_array($result)) {
714 9
            return $result;
715
        }
716
717 87
        if (count($result) > 1) {
718 1
            throw new NonUniqueResultException();
719
        }
720
721 86
        return array_shift($result);
722
    }
723
724
    /**
725
     * Gets the single scalar result of the query.
726
     *
727
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
728
     *
729
     * @return mixed The scalar result, or NULL if the query returned no result.
730
     *
731
     * @throws NonUniqueResultException If the query result is not unique.
732
     * @throws NoResultException        If the query returned no result.
733
     */
734 11
    public function getSingleScalarResult()
735
    {
736 11
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
737
    }
738
739
    /**
740
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
741
     *
742
     * @param string $name  The name of the hint.
743
     * @param mixed  $value The value of the hint.
744
     *
745
     * @return static This query instance.
746
     */
747 464
    public function setHint(string $name, $value) : self
748
    {
749 464
        $this->hints[$name] = $value;
750
751 464
        return $this;
752
    }
753
754
    /**
755
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
756
     *
757
     * @param string $name The name of the hint.
758
     *
759
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
760
     */
761 797
    public function getHint(string $name)
762
    {
763 797
        return $this->hints[$name] ?? false;
764
    }
765
766
    /**
767
     * Check if the query has a hint
768
     *
769
     * @param string $name The name of the hint
770
     *
771
     * @return bool False if the query does not have any hint
772
     */
773 17
    public function hasHint(string $name) : bool
774
    {
775 17
        return isset($this->hints[$name]);
776
    }
777
778
    /**
779
     * Return the key value map of query hints that are currently set.
780
     *
781
     * @return mixed[]
782
     */
783 136
    public function getHints() : array
784
    {
785 136
        return $this->hints;
786
    }
787
788
    /**
789
     * Executes the query and returns an IterableResult that can be used to incrementally
790
     * iterate over the result.
791
     *
792
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters    The query parameters.
793
     * @param int|string|null                                $hydrationMode The hydration mode to use.
794
     */
795 10
    public function iterate(?iterable $parameters = null, $hydrationMode = null) : IterableResult
796
    {
797 10
        if ($hydrationMode !== null) {
798 10
            $this->setHydrationMode($hydrationMode);
799
        }
800
801 10
        if (! empty($parameters)) {
802 1
            $this->setParameters($parameters);
803
        }
804
805 10
        $rsm  = $this->getResultSetMapping();
806 7
        $stmt = $this->doExecute();
807
808 7
        return $this->em->newHydrator($this->hydrationMode)->iterate($stmt, $rsm, $this->hints);
0 ignored issues
show
Bug introduced by
It seems like $stmt can also be of type integer; however, parameter $stmt of Doctrine\ORM\Internal\Hy...ractHydrator::iterate() does only seem to accept object, 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

808
        return $this->em->newHydrator($this->hydrationMode)->iterate(/** @scrutinizer ignore-type */ $stmt, $rsm, $this->hints);
Loading history...
809
    }
810
811
    /**
812
     * Executes the query.
813
     *
814
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters    Query parameters.
815
     * @param int|string|null                                $hydrationMode Processing mode to be used during the hydration process.
816
     *
817
     * @return mixed
818
     */
819 465
    public function execute(?iterable $parameters = null, $hydrationMode = null)
820
    {
821 465
        return $this->isCacheEnabled()
822 29
            ? $this->executeUsingQueryCache($parameters, $hydrationMode)
823 462
            : $this->executeIgnoreQueryCache($parameters, $hydrationMode);
824
    }
825
826
    /**
827
     * Execute query ignoring second level cache.
828
     *
829
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters
830
     * @param int|string|null                                $hydrationMode
831
     *
832
     * @return mixed
833
     */
834 465
    private function executeIgnoreQueryCache(?iterable $parameters = null, $hydrationMode = null)
835
    {
836 465
        if ($hydrationMode !== null) {
837 362
            $this->setHydrationMode($hydrationMode);
838
        }
839
840 465
        if (! empty($parameters)) {
841
            $this->setParameters($parameters);
842
        }
843
844 465
        $setCacheEntry = function () : void {
845 465
        };
846
847 465
        if ($this->hydrationCacheProfile !== null) {
848 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
849
850 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
851 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
852 2
            $result            = $cache->fetch($cacheKey);
853
854 2
            if (isset($result[$realCacheKey])) {
855 2
                return $result[$realCacheKey];
856
            }
857
858 2
            if (! $result) {
859 2
                $result = [];
860
            }
861
862 2
            $setCacheEntry = function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) : void {
863 2
                $result[$realCacheKey] = $data;
864
865 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
866 2
            };
867
        }
868
869 465
        $stmt = $this->doExecute();
870
871 455
        if (is_numeric($stmt)) {
872 27
            $setCacheEntry($stmt);
873
874 27
            return $stmt;
875
        }
876
877 440
        $rsm  = $this->getResultSetMapping();
878 440
        $data = $this->em->newHydrator($this->hydrationMode)->hydrateAll($stmt, $rsm, $this->hints);
879
880 436
        $setCacheEntry($data);
881
882 436
        return $data;
883
    }
884
885
    /**
886
     * Load from second level cache or executes the query and put into cache.
887
     *
888
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters
889
     * @param int|string|null                                $hydrationMode
890
     *
891
     * @return mixed
892
     */
893 29
    private function executeUsingQueryCache(?iterable $parameters = null, $hydrationMode = null)
894
    {
895 29
        $rsm        = $this->getResultSetMapping();
896 29
        $queryCache = $this->em->getCache()->getQueryCache($this->cacheRegion);
897 29
        $queryKey   = new QueryCacheKey(
898 29
            $this->getHash(),
899 29
            $this->lifetime,
900 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
901 29
            $this->getTimestampKey()
902
        );
903
904 29
        $result = $queryCache->get($queryKey, $rsm, $this->hints);
905
906 29
        if ($result !== null) {
907 16
            if ($this->cacheLogger) {
908 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
909
            }
910
911 16
            return $result;
912
        }
913
914 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
915 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->hints);
916
917 26
        if ($this->cacheLogger) {
918 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
919
920 26
            if ($cached) {
921 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
922
            }
923
        }
924
925 26
        return $result;
926
    }
927
928 29
    private function getTimestampKey() : ?TimestampCacheKey
929
    {
930 29
        $entityName = reset($this->resultSetMapping->aliasMap);
931
932 29
        if (empty($entityName)) {
933 2
            return null;
934
        }
935
936 27
        $metadata = $this->em->getClassMetadata($entityName);
937
938 27
        return new Cache\TimestampCacheKey($metadata->getRootClassName());
939
    }
940
941
    /**
942
     * Get the result cache id to use to store the result set cache entry.
943
     * Will return the configured id if it exists otherwise a hash will be
944
     * automatically generated for you.
945
     *
946
     * @return string[] ($key, $hash)
947
     */
948 2
    protected function getHydrationCacheId() : array
949
    {
950 2
        $parameters = [];
951
952 2
        foreach ($this->getParameters() as $parameter) {
953 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
954
        }
955
956 2
        $sql                    = $this->getSQL();
957 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
958 2
        $hints                  = $this->getHints();
959 2
        $hints['hydrationMode'] = $this->getHydrationMode();
960
961 2
        ksort($hints);
962
963 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
0 ignored issues
show
Bug introduced by
It seems like $sql can also be of type string[]; however, parameter $query of Doctrine\DBAL\Cache\Quer...le::generateCacheKeys() does only seem to accept string, 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

963
        return $queryCacheProfile->generateCacheKeys(/** @scrutinizer ignore-type */ $sql, $parameters, $hints);
Loading history...
964
    }
965
966
    /**
967
     * Set the result cache id to use to store the result set cache entry.
968
     * If this is not explicitly set by the developer then a hash is automatically
969
     * generated for you.
970
     *
971
     * @return static This query instance.
972
     */
973 12
    public function setResultCacheId(?string $id) : self
974
    {
975 12
        $this->queryCacheProfile = $this->queryCacheProfile
976 12
            ? $this->queryCacheProfile->setCacheKey($id)
977
            : new QueryCacheProfile(0, $id, $this->em->getConfiguration()->getResultCacheImpl());
978
979 12
        return $this;
980
    }
981
982
    /**
983
     * Get the result cache id to use to store the result set cache entry if set.
984
     *
985
     * @deprecated
986
     */
987
    public function getResultCacheId() : ?string
988
    {
989
        return $this->queryCacheProfile ? $this->queryCacheProfile->getCacheKey() : null;
990
    }
991
992
    /**
993
     * Executes the query and returns a the resulting Statement object.
994
     *
995
     * @return ResultStatement|int The executed database statement that holds the results.
996
     */
997
    abstract protected function doExecute();
998
999
    /**
1000
     * Cleanup Query resource when clone is called.
1001
     */
1002 139
    public function __clone()
1003
    {
1004 139
        $this->parameters = new ArrayCollection();
1005 139
        $this->hints      = $this->em->getConfiguration()->getDefaultQueryHints();
1006 139
    }
1007
1008
    /**
1009
     * Generates a string of currently query to use for the cache second level cache.
1010
     */
1011 29
    protected function getHash() : string
1012
    {
1013 29
        $query  = $this->getSQL();
1014 29
        $hints  = $this->getHints();
1015 29
        $params = array_map(function (Parameter $parameter) {
1016 5
            $value = $parameter->getValue();
1017
1018
            // Small optimization
1019
            // Does not invoke processParameterValue for scalar values
1020 5
            if (is_scalar($value)) {
1021 4
                return $value;
1022
            }
1023
1024 1
            return $this->processParameterValue($value);
1025 29
        }, $this->parameters->getValues());
1026
1027 29
        ksort($hints);
1028
1029 29
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
0 ignored issues
show
Bug introduced by
Are you sure $query of type string|string[] can be used in concatenation? ( Ignorable by Annotation )

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

1029
        return sha1(/** @scrutinizer ignore-type */ $query . '-' . serialize($params) . '-' . serialize($hints));
Loading history...
1030
    }
1031
}
1032