Failed Conditions
Pull Request — master (#7130)
by Michael
12:28
created

AbstractQuery::expireResultCache()   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
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 29
    protected function isCacheEnabled() : bool
202
    {
203 29
        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 $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(int $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 653
    public function getHydrationMode() : int
614
    {
615 653
        return $this->hydrationMode;
616
    }
617
618
    /**
619
     * Gets the list of results for the query.
620
     *
621
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
622
     *
623
     * @return mixed
624
     */
625 293
    public function getResult(int $hydrationMode = self::HYDRATE_OBJECT)
626
    {
627 293
        return $this->execute(null, $hydrationMode);
628
    }
629
630
    /**
631
     * Gets the array of results for the query.
632
     *
633
     * Alias for execute(null, HYDRATE_ARRAY).
634
     *
635
     * @return mixed[]
636
     */
637 24
    public function getArrayResult() : array
638
    {
639 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...
640
    }
641
642
    /**
643
     * Gets the scalar results for the query.
644
     *
645
     * Alias for execute(null, HYDRATE_SCALAR).
646
     *
647
     * @return mixed[]
648
     */
649 87
    public function getScalarResult() : array
650
    {
651 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...
652
    }
653
654
    /**
655
     * Get exactly one result or null.
656
     *
657
     * @return mixed
658
     *
659
     * @throws NonUniqueResultException
660
     */
661 16
    public function getOneOrNullResult(?int $hydrationMode = null)
662
    {
663
        try {
664 16
            $result = $this->execute(null, $hydrationMode);
665 1
        } catch (NoResultException $e) {
666 1
            return null;
667
        }
668
669 15
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
670 1
            return null;
671
        }
672
673 14
        if (! is_array($result)) {
674 1
            return $result;
675
        }
676
677 14
        if (count($result) > 1) {
678 1
            throw new NonUniqueResultException();
679
        }
680
681 13
        return array_shift($result);
682
    }
683
684
    /**
685
     * Gets the single result of the query.
686
     *
687
     * Enforces the presence as well as the uniqueness of the result.
688
     *
689
     * If the result is not unique, a NonUniqueResultException is thrown.
690
     * If there is no result, a NoResultException is thrown.
691
     *
692
     * @return mixed
693
     *
694
     * @throws NonUniqueResultException If the query result is not unique.
695
     * @throws NoResultException        If the query returned no result.
696
     */
697 102
    public function getSingleResult(?int $hydrationMode = null)
698
    {
699 102
        $result = $this->execute(null, $hydrationMode);
700
701 96
        if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
702 2
            throw new NoResultException();
703
        }
704
705 95
        if (! is_array($result)) {
706 9
            return $result;
707
        }
708
709 87
        if (count($result) > 1) {
710 1
            throw new NonUniqueResultException();
711
        }
712
713 86
        return array_shift($result);
714
    }
715
716
    /**
717
     * Gets the single scalar result of the query.
718
     *
719
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
720
     *
721
     * @return mixed The scalar result, or NULL if the query returned no result.
722
     *
723
     * @throws NonUniqueResultException If the query result is not unique.
724
     * @throws NoResultException        If the query returned no result.
725
     */
726 11
    public function getSingleScalarResult()
727
    {
728 11
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
729
    }
730
731
    /**
732
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
733
     *
734
     * @param string $name  The name of the hint.
735
     * @param mixed  $value The value of the hint.
736
     *
737
     * @return static This query instance.
738
     */
739 464
    public function setHint(string $name, $value) : self
740
    {
741 464
        $this->hints[$name] = $value;
742
743 464
        return $this;
744
    }
745
746
    /**
747
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
748
     *
749
     * @param string $name The name of the hint.
750
     *
751
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
752
     */
753 797
    public function getHint(string $name)
754
    {
755 797
        return $this->hints[$name] ?? false;
756
    }
757
758
    /**
759
     * Check if the query has a hint
760
     *
761
     * @param string $name The name of the hint
762
     *
763
     * @return bool False if the query does not have any hint
764
     */
765 17
    public function hasHint(string $name) : bool
766
    {
767 17
        return isset($this->hints[$name]);
768
    }
769
770
    /**
771
     * Return the key value map of query hints that are currently set.
772
     *
773
     * @return mixed[]
774
     */
775 136
    public function getHints() : array
776
    {
777 136
        return $this->hints;
778
    }
779
780
    /**
781
     * Executes the query and returns an IterableResult that can be used to incrementally
782
     * iterate over the result.
783
     *
784
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters    The query parameters.
785
     * @param int|null                                       $hydrationMode The hydration mode to use.
786
     */
787 10
    public function iterate(?iterable $parameters = null, ?int $hydrationMode = null) : IterableResult
788
    {
789 10
        if ($hydrationMode !== null) {
790 10
            $this->setHydrationMode($hydrationMode);
791
        }
792
793 10
        if (! empty($parameters)) {
794 1
            $this->setParameters($parameters);
795
        }
796
797 10
        $rsm  = $this->getResultSetMapping();
798 7
        $stmt = $this->doExecute();
799
800 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

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

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

1021
        return sha1(/** @scrutinizer ignore-type */ $query . '-' . serialize($params) . '-' . serialize($hints));
Loading history...
1022
    }
1023
}
1024