Passed
Pull Request — 2.7 (#7701)
by
unknown
09:43
created

AbstractQuery::getCacheRegion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM;
21
22
use Doctrine\Common\Persistence\Mapping\MappingException;
23
use Doctrine\Common\Util\ClassUtils;
24
use Doctrine\Common\Collections\Collection;
25
use Doctrine\Common\Collections\ArrayCollection;
26
27
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
28
use Doctrine\ORM\Query\Parameter;
29
use Doctrine\ORM\Cache\QueryCacheKey;
30
use Doctrine\DBAL\Cache\QueryCacheProfile;
31
32
/**
33
 * Base contract for ORM queries. Base class for Query and NativeQuery.
34
 *
35
 * @link    www.doctrine-project.org
36
 * @since   2.0
37
 * @author  Benjamin Eberlei <[email protected]>
38
 * @author  Guilherme Blanco <[email protected]>
39
 * @author  Jonathan Wage <[email protected]>
40
 * @author  Roman Borschel <[email protected]>
41
 * @author  Konsta Vesterinen <[email protected]>
42
 */
43
abstract class AbstractQuery
44
{
45
    /* Hydration mode constants */
46
47
    /**
48
     * Hydrates an object graph. This is the default behavior.
49
     */
50
    const HYDRATE_OBJECT = 1;
51
52
    /**
53
     * Hydrates an array graph.
54
     */
55
    const HYDRATE_ARRAY = 2;
56
57
    /**
58
     * Hydrates a flat, rectangular result set with scalar values.
59
     */
60
    const HYDRATE_SCALAR = 3;
61
62
    /**
63
     * Hydrates a single scalar value.
64
     */
65
    const HYDRATE_SINGLE_SCALAR = 4;
66
67
    /**
68
     * Very simple object hydrator (optimized for performance).
69
     */
70
    const HYDRATE_SIMPLEOBJECT = 5;
71
72
    /**
73
     * The parameter map of this query.
74
     *
75
     * @var \Doctrine\Common\Collections\ArrayCollection
76
     */
77
    protected $parameters;
78
79
    /**
80
     * The user-specified ResultSetMapping to use.
81
     *
82
     * @var \Doctrine\ORM\Query\ResultSetMapping
83
     */
84
    protected $_resultSetMapping;
85
86
    /**
87
     * The entity manager used by this query object.
88
     *
89
     * @var EntityManagerInterface
90
     */
91
    protected $_em;
92
93
    /**
94
     * The map of query hints.
95
     *
96
     * @var array
97
     */
98
    protected $_hints = [];
99
100
    /**
101
     * The hydration mode.
102
     *
103
     * @var string|int
104
     */
105
    protected $_hydrationMode = self::HYDRATE_OBJECT;
106
107
    /**
108
     * @var \Doctrine\DBAL\Cache\QueryCacheProfile
109
     */
110
    protected $_queryCacheProfile;
111
112
    /**
113
     * Whether or not expire the result cache.
114
     *
115
     * @var boolean
116
     */
117
    protected $_expireResultCache = false;
118
119
    /**
120
     * @var \Doctrine\DBAL\Cache\QueryCacheProfile
121
     */
122
    protected $_hydrationCacheProfile;
123
124
    /**
125
     * Whether to use second level cache, if available.
126
     *
127
     * @var boolean
128
     */
129
    protected $cacheable = false;
130
131
    /**
132
     * @var boolean
133
     */
134
    protected $hasCache = false;
135
136
    /**
137
     * Second level cache region name.
138
     *
139
     * @var string|null
140
     */
141
    protected $cacheRegion;
142
143
    /**
144
     * Second level query cache mode.
145
     *
146
     * @var integer|null
147
     */
148
    protected $cacheMode;
149
150
    /**
151
     * @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
152
     */
153
    protected $cacheLogger;
154
155
    /**
156
     * @var integer
157
     */
158
    protected $lifetime = 0;
159
160
    /**
161
     * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
162
     *
163
     * @param \Doctrine\ORM\EntityManagerInterface $em
164
     */
165 1006
    public function __construct(EntityManagerInterface $em)
166
    {
167 1006
        $this->_em          = $em;
168 1006
        $this->parameters   = new ArrayCollection();
169 1006
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
170 1006
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
171
172 1006
        if ($this->hasCache) {
173 32
            $this->cacheLogger = $em->getConfiguration()
174 32
                ->getSecondLevelCacheConfiguration()
175 32
                ->getCacheLogger();
176
        }
177 1006
    }
178
179
    /**
180
     * Enable/disable second level query (result) caching for this query.
181
     *
182
     * @param boolean $cacheable
183
     *
184
     * @return static This query instance.
185
     */
186 136
    public function setCacheable($cacheable)
187
    {
188 136
        $this->cacheable = (boolean) $cacheable;
189
190 136
        return $this;
191
    }
192
193
    /**
194
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
195
     */
196 92
    public function isCacheable()
197
    {
198 92
        return $this->cacheable;
199
    }
200
201
    /**
202
     * @param string $cacheRegion
203
     *
204
     * @return static This query instance.
205
     */
206 2
    public function setCacheRegion($cacheRegion)
207
    {
208 2
        $this->cacheRegion = (string) $cacheRegion;
209
210 2
        return $this;
211
    }
212
213
    /**
214
    * Obtain the name of the second level query cache region in which query results will be stored
215
    *
216
    * @return string|null The cache region name; NULL indicates the default region.
217
    */
218 1
    public function getCacheRegion()
219
    {
220 1
        return $this->cacheRegion;
221
    }
222
223
    /**
224
     * @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
225
     */
226 29
    protected function isCacheEnabled()
227
    {
228 29
        return $this->cacheable && $this->hasCache;
229
    }
230
231
    /**
232
     * @return integer
233
     */
234 1
    public function getLifetime()
235
    {
236 1
        return $this->lifetime;
237
    }
238
239
    /**
240
     * Sets the life-time for this query into second level cache.
241
     *
242
     * @param integer $lifetime
243
     *
244
     * @return \Doctrine\ORM\AbstractQuery This query instance.
245
     */
246 2
    public function setLifetime($lifetime)
247
    {
248 2
        $this->lifetime = (integer) $lifetime;
249
250 2
        return $this;
251
    }
252
253
    /**
254
     * @return integer
255
     */
256 1
    public function getCacheMode()
257
    {
258 1
        return $this->cacheMode;
259
    }
260
261
    /**
262
     * @param integer $cacheMode
263
     *
264
     * @return \Doctrine\ORM\AbstractQuery This query instance.
265
     */
266 4
    public function setCacheMode($cacheMode)
267
    {
268 4
        $this->cacheMode = (integer) $cacheMode;
269
270 4
        return $this;
271
    }
272
273
    /**
274
     * Gets the SQL query that corresponds to this query object.
275
     * The returned SQL syntax depends on the connection driver that is used
276
     * by this query object at the time of this method call.
277
     *
278
     * @return string SQL query
279
     */
280
    abstract public function getSQL();
281
282
    /**
283
     * Retrieves the associated EntityManager of this Query instance.
284
     *
285
     * @return \Doctrine\ORM\EntityManager
286
     */
287 921
    public function getEntityManager()
288
    {
289 921
        return $this->_em;
290
    }
291
292
    /**
293
     * Frees the resources used by the query object.
294
     *
295
     * Resets Parameters, Parameter Types and Query Hints.
296
     *
297
     * @return void
298
     */
299 212
    public function free()
300
    {
301 212
        $this->parameters = new ArrayCollection();
302
303 212
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
304 212
    }
305
306
    /**
307
     * Get all defined parameters.
308
     *
309
     * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
310
     */
311 146
    public function getParameters()
312
    {
313 146
        return $this->parameters;
314
    }
315
316
    /**
317
     * Gets a query parameter.
318
     *
319
     * @param mixed $key The key (index or name) of the bound parameter.
320
     *
321
     * @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
322
     */
323 277
    public function getParameter($key)
324
    {
325 277
        $filteredParameters = $this->parameters->filter(
326 277
            function (Query\Parameter $parameter) use ($key) : bool {
327 159
                $parameterName = $parameter->getName();
328
329 159
                return $key === $parameterName || (string) $key === (string) $parameterName;
330 277
            }
331
        );
332
333 277
        return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
334
    }
335
336
    /**
337
     * Sets a collection of query parameters.
338
     *
339
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
340
     *
341
     * @return static This query instance.
342
     */
343 194
    public function setParameters($parameters)
344
    {
345
        // BC compatibility with 2.3-
346 194
        if (is_array($parameters)) {
347 4
            $parameterCollection = new ArrayCollection();
348
349 4
            foreach ($parameters as $key => $value) {
350 4
                $parameterCollection->add(new Parameter($key, $value));
351
            }
352
353 4
            $parameters = $parameterCollection;
354
        }
355
356 194
        $this->parameters = $parameters;
357
358 194
        return $this;
359
    }
360
361
    /**
362
     * Sets a query parameter.
363
     *
364
     * @param string|int  $key   The parameter position or name.
365
     * @param mixed       $value The parameter value.
366
     * @param string|null $type  The parameter type. If specified, the given value will be run through
367
     *                           the type conversion of this type. This is usually not needed for
368
     *                           strings and numeric types.
369
     *
370
     * @return static This query instance.
371
     */
372 188
    public function setParameter($key, $value, $type = null)
373
    {
374 188
        $existingParameter = $this->getParameter($key);
375
376 188
        if ($existingParameter !== null) {
377 6
            $existingParameter->setValue($value, $type);
378
379 6
            return $this;
380
        }
381
382 186
        $this->parameters->add(new Parameter($key, $value, $type));
383
384 186
        return $this;
385
    }
386
387
    /**
388
     * Processes an individual parameter value.
389
     *
390
     * @param mixed $value
391
     *
392
     * @return array|string
393
     *
394
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
395
     */
396 187
    public function processParameterValue($value)
397
    {
398 187
        if (is_scalar($value)) {
399 172
            return $value;
400
        }
401
402 93
        if ($value instanceof Collection) {
403 1
            $value = $value->toArray();
404
        }
405
406 93
        if (is_array($value)) {
407 74
            foreach ($value as $key => $paramValue) {
408 74
                $paramValue  = $this->processParameterValue($paramValue);
409 74
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
410
            }
411
412 74
            return $value;
413
        }
414
415 22
        if ($value instanceof Mapping\ClassMetadata) {
416 1
            return $value->name;
417
        }
418
419 21
        if (! is_object($value)) {
420 1
            return $value;
421
        }
422
423
        try {
424 20
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
425
426 15
            if ($value === null) {
427 15
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
428
            }
429 6
        } catch (MappingException | ORMMappingException $e) {
430
            // Silence any mapping exceptions. These can occur if the object in
431
            // question is not a mapped entity, in which case we just don't do
432
            // any preparation on the value.
433
        }
434
435 20
        return $value;
436
    }
437
438
    /**
439
     * Sets the ResultSetMapping that should be used for hydration.
440
     *
441
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
442
     *
443
     * @return static This query instance.
444
     */
445 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
446
    {
447 30
        $this->translateNamespaces($rsm);
448 30
        $this->_resultSetMapping = $rsm;
449
450 30
        return $this;
451
    }
452
453
    /**
454
     * Gets the ResultSetMapping used for hydration.
455
     *
456
     * @return \Doctrine\ORM\Query\ResultSetMapping
457
     */
458 22
    protected function getResultSetMapping()
459
    {
460 22
        return $this->_resultSetMapping;
461
    }
462
463
    /**
464
     * Allows to translate entity namespaces to full qualified names.
465
     *
466
     * @param Query\ResultSetMapping $rsm
467
     *
468
     * @return void
469
     */
470
    private function translateNamespaces(Query\ResultSetMapping $rsm)
471
    {
472 30
        $translate = function ($alias) {
473 18
            return $this->_em->getClassMetadata($alias)->getName();
474 30
        };
475
476 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
477 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
478 30
    }
479
480
    /**
481
     * Set a cache profile for hydration caching.
482
     *
483
     * If no result cache driver is set in the QueryCacheProfile, the default
484
     * result cache driver is used from the configuration.
485
     *
486
     * Important: Hydration caching does NOT register entities in the
487
     * UnitOfWork when retrieved from the cache. Never use result cached
488
     * entities for requests that also flush the EntityManager. If you want
489
     * some form of caching with UnitOfWork registration you should use
490
     * {@see AbstractQuery::setResultCacheProfile()}.
491
     *
492
     * @example
493
     * $lifetime = 100;
494
     * $resultKey = "abc";
495
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
496
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
497
     *
498
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
499
     *
500
     * @return static This query instance.
501
     */
502 3
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
503
    {
504 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
505
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
506
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver can also be of type null; however, parameter $cache of Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does only seem to accept Doctrine\Common\Cache\Cache, maybe add an additional type check? ( Ignorable by Annotation )

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

506
            $profile = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
507
        }
508
509 3
        $this->_hydrationCacheProfile = $profile;
510
511 3
        return $this;
512
    }
513
514
    /**
515
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
516
     */
517 3
    public function getHydrationCacheProfile()
518
    {
519 3
        return $this->_hydrationCacheProfile;
520
    }
521
522
    /**
523
     * Set a cache profile for the result cache.
524
     *
525
     * If no result cache driver is set in the QueryCacheProfile, the default
526
     * result cache driver is used from the configuration.
527
     *
528
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
529
     *
530
     * @return static This query instance.
531
     */
532 1
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
533
    {
534 1
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
535
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
536
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver can also be of type null; however, parameter $cache of Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does only seem to accept Doctrine\Common\Cache\Cache, maybe add an additional type check? ( Ignorable by Annotation )

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

536
            $profile = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
537
        }
538
539 1
        $this->_queryCacheProfile = $profile;
540
541 1
        return $this;
542
    }
543
544
    /**
545
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
546
     *
547
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
548
     *
549
     * @return static This query instance.
550
     *
551
     * @throws ORMException
552
     */
553 12
    public function setResultCacheDriver($resultCacheDriver = null)
554
    {
555 12
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
0 ignored issues
show
introduced by
$resultCacheDriver is always a sub-type of Doctrine\Common\Cache\Cache.
Loading history...
556
            throw ORMException::invalidResultCacheDriver();
557
        }
558
559 12
        $this->_queryCacheProfile = $this->_queryCacheProfile
560 2
            ? $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

560
            ? $this->_queryCacheProfile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver)
Loading history...
561 10
            : new QueryCacheProfile(0, null, $resultCacheDriver);
562
563 12
        return $this;
564
    }
565
566
    /**
567
     * Returns the cache driver used for caching result sets.
568
     *
569
     * @deprecated
570
     *
571
     * @return \Doctrine\Common\Cache\Cache Cache driver
572
     */
573 3
    public function getResultCacheDriver()
574
    {
575 3
        if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
576 3
            return $this->_queryCacheProfile->getResultCacheDriver();
577
        }
578
579
        return $this->_em->getConfiguration()->getResultCacheImpl();
580
    }
581
582
    /**
583
     * Set whether or not to cache the results of this query and if so, for
584
     * how long and which ID to use for the cache entry.
585
     *
586
     * @deprecated 3.0 Use {@see enableResultCache} and {@see disableResultCache} instead.
587
     *
588
     * @param boolean $useCache
589
     * @param integer $lifetime
590
     * @param string  $resultCacheId
591
     *
592
     * @return static This query instance.
593
     */
594 3
    public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
595
    {
596 3
        return $useCache
597 2
            ? $this->enableResultCache($lifetime, $resultCacheId)
598 3
            : $this->disableResultCache();
599
    }
600
601
    /**
602
     * Enables caching of the results of this query, for given or default amount of seconds
603
     * and optionally specifies which ID to use for the cache entry.
604
     *
605
     * @param integer|null $lifetime      How long the cache entry is valid, in seconds.
606
     * @param string|null  $resultCacheId ID to use for the cache entry.
607
     *
608
     * @return static This query instance.
609
     */
610 11
    public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null): self
611
    {
612 11
        $this->setResultCacheLifetime($lifetime);
613 11
        $this->setResultCacheId($resultCacheId);
614
615 11
        return $this;
616
    }
617
618
    /**
619
     * Disables caching of the results of this query.
620
     *
621
     * @return static This query instance.
622
     */
623 3
    public function disableResultCache(): self
624
    {
625 3
        $this->_queryCacheProfile = null;
626
627 3
        return $this;
628
    }
629
630
    /**
631
     * Defines how long the result cache will be active before expire.
632
     *
633
     * @param integer $lifetime How long the cache entry is valid.
634
     *
635
     * @return static This query instance.
636
     */
637 12
    public function setResultCacheLifetime($lifetime)
638
    {
639 12
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
0 ignored issues
show
introduced by
The condition $lifetime !== null is always true.
Loading history...
640
641 12
        $this->_queryCacheProfile = $this->_queryCacheProfile
642 5
            ? $this->_queryCacheProfile->setLifetime($lifetime)
643 7
            : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
644
645 12
        return $this;
646
    }
647
648
    /**
649
     * Retrieves the lifetime of resultset cache.
650
     *
651
     * @deprecated
652
     *
653
     * @return integer
654
     */
655
    public function getResultCacheLifetime()
656
    {
657
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
658
    }
659
660
    /**
661
     * Defines if the result cache is active or not.
662
     *
663
     * @param boolean $expire Whether or not to force resultset cache expiration.
664
     *
665
     * @return static This query instance.
666
     */
667 4
    public function expireResultCache($expire = true)
668
    {
669 4
        $this->_expireResultCache = $expire;
670
671 4
        return $this;
672
    }
673
674
    /**
675
     * Retrieves if the resultset cache is active or not.
676
     *
677
     * @return boolean
678
     */
679 10
    public function getExpireResultCache()
680
    {
681 10
        return $this->_expireResultCache;
682
    }
683
684
    /**
685
     * @return QueryCacheProfile
686
     */
687 1
    public function getQueryCacheProfile()
688
    {
689 1
        return $this->_queryCacheProfile;
690
    }
691
692
    /**
693
     * Change the default fetch mode of an association for this query.
694
     *
695
     * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
696
     *
697
     * @param string $class
698
     * @param string $assocName
699
     * @param int    $fetchMode
700
     *
701
     * @return static This query instance.
702
     */
703 3
    public function setFetchMode($class, $assocName, $fetchMode)
704
    {
705 3
        if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
706
            $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
707
        }
708
709 3
        $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
710
711 3
        return $this;
712
    }
713
714
    /**
715
     * Defines the processing mode to be used during hydration / result set transformation.
716
     *
717
     * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
718
     *                                  One of the Query::HYDRATE_* constants.
719
     *
720
     * @return static This query instance.
721
     */
722 394
    public function setHydrationMode($hydrationMode)
723
    {
724 394
        $this->_hydrationMode = $hydrationMode;
725
726 394
        return $this;
727
    }
728
729
    /**
730
     * Gets the hydration mode currently used by the query.
731
     *
732
     * @return string|int
733
     */
734 668
    public function getHydrationMode()
735
    {
736 668
        return $this->_hydrationMode;
737
    }
738
739
    /**
740
     * Gets the list of results for the query.
741
     *
742
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
743
     *
744
     * @param string|int $hydrationMode
745
     *
746
     * @return mixed
747
     */
748 315
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
749
    {
750 315
        return $this->execute(null, $hydrationMode);
751
    }
752
753
    /**
754
     * Gets the array of results for the query.
755
     *
756
     * Alias for execute(null, HYDRATE_ARRAY).
757
     *
758
     * @return array
759
     */
760 27
    public function getArrayResult()
761
    {
762 27
        return $this->execute(null, self::HYDRATE_ARRAY);
763
    }
764
765
    /**
766
     * Gets the scalar results for the query.
767
     *
768
     * Alias for execute(null, HYDRATE_SCALAR).
769
     *
770
     * @return array
771
     */
772 87
    public function getScalarResult()
773
    {
774 87
        return $this->execute(null, self::HYDRATE_SCALAR);
775
    }
776
777
    /**
778
     * Get exactly one result or null.
779
     *
780
     * @param string|int $hydrationMode
781
     *
782
     * @return mixed
783
     *
784
     * @throws NonUniqueResultException
785
     */
786 18
    public function getOneOrNullResult($hydrationMode = null)
787
    {
788
        try {
789 18
            $result = $this->execute(null, $hydrationMode);
790 1
        } catch (NoResultException $e) {
791 1
            return null;
792
        }
793
794
795 17
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
796 2
            return null;
797
        }
798
799 15
        if ( ! is_array($result)) {
800 1
            return $result;
801
        }
802
803 15
        if (count($result) > 1) {
804 1
            throw new NonUniqueResultException;
805
        }
806
807 14
        return array_shift($result);
808
    }
809
810
    /**
811
     * Gets the single result of the query.
812
     *
813
     * Enforces the presence as well as the uniqueness of the result.
814
     *
815
     * If the result is not unique, a NonUniqueResultException is thrown.
816
     * If there is no result, a NoResultException is thrown.
817
     *
818
     * @param string|int $hydrationMode
819
     *
820
     * @return mixed
821
     *
822
     * @throws NonUniqueResultException If the query result is not unique.
823
     * @throws NoResultException        If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
824
     */
825 107
    public function getSingleResult($hydrationMode = null)
826
    {
827 107
        $result = $this->execute(null, $hydrationMode);
828
829 101
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
830 2
            throw new NoResultException;
831
        }
832
833 100
        if ( ! is_array($result)) {
834 9
            return $result;
835
        }
836
837 92
        if (count($result) > 1) {
838 1
            throw new NonUniqueResultException;
839
        }
840
841 91
        return array_shift($result);
842
    }
843
844
    /**
845
     * Gets the single scalar result of the query.
846
     *
847
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
848
     *
849
     * @return mixed The scalar result, or NULL if the query returned no result.
850
     *
851
     * @throws NonUniqueResultException If the query result is not unique.
852
     */
853 11
    public function getSingleScalarResult()
854
    {
855 11
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
856
    }
857
858
    /**
859
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
860
     *
861
     * @param string $name  The name of the hint.
862
     * @param mixed  $value The value of the hint.
863
     *
864
     * @return static This query instance.
865
     */
866 470
    public function setHint($name, $value)
867
    {
868 470
        $this->_hints[$name] = $value;
869
870 470
        return $this;
871
    }
872
873
    /**
874
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
875
     *
876
     * @param string $name The name of the hint.
877
     *
878
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
879
     */
880 814
    public function getHint($name)
881
    {
882 814
        return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
883
    }
884
885
    /**
886
     * Check if the query has a hint
887
     *
888
     * @param string $name The name of the hint
889
     *
890
     * @return bool False if the query does not have any hint
891
     */
892 19
    public function hasHint($name)
893
    {
894 19
        return isset($this->_hints[$name]);
895
    }
896
897
    /**
898
     * Return the key value map of query hints that are currently set.
899
     *
900
     * @return array
901
     */
902 139
    public function getHints()
903
    {
904 139
        return $this->_hints;
905
    }
906
907
    /**
908
     * Executes the query and returns an IterableResult that can be used to incrementally
909
     * iterate over the result.
910
     *
911
     * @param ArrayCollection|array|null $parameters    The query parameters.
912
     * @param string|int|null            $hydrationMode The hydration mode to use.
913
     *
914
     * @return \Doctrine\ORM\Internal\Hydration\IterableResult
915
     */
916 10
    public function iterate($parameters = null, $hydrationMode = null)
917
    {
918 10
        if ($hydrationMode !== null) {
919 10
            $this->setHydrationMode($hydrationMode);
920
        }
921
922 10
        if ( ! empty($parameters)) {
923 1
            $this->setParameters($parameters);
924
        }
925
926 10
        $rsm  = $this->getResultSetMapping();
927 7
        $stmt = $this->_doExecute();
928
929 7
        return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
930
    }
931
932
    /**
933
     * Executes the query.
934
     *
935
     * @param ArrayCollection|array|null $parameters Query parameters.
936
     * @param string|int|null            $hydrationMode Processing mode to be used during the hydration process.
937
     *
938
     * @return mixed
939
     */
940 496
    public function execute($parameters = null, $hydrationMode = null)
941
    {
942 496
        if ($this->cacheable && $this->isCacheEnabled()) {
943 29
            return $this->executeUsingQueryCache($parameters, $hydrationMode);
944
        }
945
946 469
        return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
947
    }
948
949
    /**
950
     * Execute query ignoring second level cache.
951
     *
952
     * @param ArrayCollection|array|null $parameters
953
     * @param string|int|null            $hydrationMode
954
     *
955
     * @return mixed
956
     */
957 496
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
958
    {
959 496
        if ($hydrationMode !== null) {
960 384
            $this->setHydrationMode($hydrationMode);
961
        }
962
963 496
        if ( ! empty($parameters)) {
964
            $this->setParameters($parameters);
965
        }
966
967
        $setCacheEntry = function() {};
968
969 496
        if ($this->_hydrationCacheProfile !== null) {
970 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
971
972 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
973 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
974 2
            $result            = $cache->fetch($cacheKey);
975
976 2
            if (isset($result[$realCacheKey])) {
977 2
                return $result[$realCacheKey];
978
            }
979
980 2
            if ( ! $result) {
981 2
                $result = [];
982
            }
983
984 2
            $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
985 2
                $result[$realCacheKey] = $data;
986
987 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
988 2
            };
989
        }
990
991 496
        $stmt = $this->_doExecute();
992
993 486
        if (is_numeric($stmt)) {
0 ignored issues
show
introduced by
The condition is_numeric($stmt) is always false.
Loading history...
994 27
            $setCacheEntry($stmt);
995
996 27
            return $stmt;
997
        }
998
999 471
        $rsm  = $this->getResultSetMapping();
1000 471
        $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
1001
1002 467
        $setCacheEntry($data);
1003
1004 467
        return $data;
1005
    }
1006
1007
    /**
1008
     * Load from second level cache or executes the query and put into cache.
1009
     *
1010
     * @param ArrayCollection|array|null $parameters
1011
     * @param string|int|null            $hydrationMode
1012
     *
1013
     * @return mixed
1014
     */
1015 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
1016
    {
1017 29
        $rsm        = $this->getResultSetMapping();
1018 29
        $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
1019 29
        $queryKey   = new QueryCacheKey(
1020 29
            $this->getHash(),
1021 29
            $this->lifetime,
1022 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
1023 29
            $this->getTimestampKey()
1024
        );
1025
1026 29
        $result     = $queryCache->get($queryKey, $rsm, $this->_hints);
1027
1028 29
        if ($result !== null) {
1029 16
            if ($this->cacheLogger) {
1030 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
1031
            }
1032
1033 16
            return $result;
1034
        }
1035
1036 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
1037 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
1038
1039 26
        if ($this->cacheLogger) {
1040 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
1041
1042 26
            if ($cached) {
1043 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
1044
            }
1045
        }
1046
1047 26
        return $result;
1048
    }
1049
1050
    /**
1051
     * @return \Doctrine\ORM\Cache\TimestampCacheKey|null
1052
     */
1053 29
    private function getTimestampKey()
1054
    {
1055 29
        $entityName = reset($this->_resultSetMapping->aliasMap);
1056
1057 29
        if (empty($entityName)) {
1058 2
            return null;
1059
        }
1060
1061 27
        $metadata = $this->_em->getClassMetadata($entityName);
1062
1063 27
        return new Cache\TimestampCacheKey($metadata->rootEntityName);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1064
    }
1065
1066
    /**
1067
     * Get the result cache id to use to store the result set cache entry.
1068
     * Will return the configured id if it exists otherwise a hash will be
1069
     * automatically generated for you.
1070
     *
1071
     * @return array ($key, $hash)
1072
     */
1073 2
    protected function getHydrationCacheId()
1074
    {
1075 2
        $parameters = [];
1076
1077 2
        foreach ($this->getParameters() as $parameter) {
1078 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1079
        }
1080
1081 2
        $sql                    = $this->getSQL();
1082 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1083 2
        $hints                  = $this->getHints();
1084 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1085
1086 2
        ksort($hints);
1087
1088 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1089
    }
1090
1091
    /**
1092
     * Set the result cache id to use to store the result set cache entry.
1093
     * If this is not explicitly set by the developer then a hash is automatically
1094
     * generated for you.
1095
     *
1096
     * @param string $id
1097
     *
1098
     * @return static This query instance.
1099
     */
1100 16
    public function setResultCacheId($id)
1101
    {
1102 16
        $this->_queryCacheProfile = $this->_queryCacheProfile
1103 16
            ? $this->_queryCacheProfile->setCacheKey($id)
1104
            : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
1105
1106 16
        return $this;
1107
    }
1108
1109
    /**
1110
     * Get the result cache id to use to store the result set cache entry if set.
1111
     *
1112
     * @deprecated
1113
     *
1114
     * @return string
1115
     */
1116
    public function getResultCacheId()
1117
    {
1118
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
1119
    }
1120
1121
    /**
1122
     * Executes the query and returns a the resulting Statement object.
1123
     *
1124
     * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
1125
     */
1126
    abstract protected function _doExecute();
1127
1128
    /**
1129
     * Cleanup Query resource when clone is called.
1130
     *
1131
     * @return void
1132
     */
1133 142
    public function __clone()
1134
    {
1135 142
        $this->parameters = new ArrayCollection();
1136
1137 142
        $this->_hints = [];
1138 142
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
1139 142
    }
1140
1141
    /**
1142
     * Generates a string of currently query to use for the cache second level cache.
1143
     *
1144
     * @return string
1145
     */
1146 29
    protected function getHash()
1147
    {
1148 29
        $query  = $this->getSQL();
1149 29
        $hints  = $this->getHints();
1150 29
        $params = array_map(function(Parameter $parameter) {
1151
            // Small optimization
1152
            // Does not invoke processParameterValue for scalar values
1153 5
            if (is_scalar($value = $parameter->getValue())) {
1154 4
                return $value;
1155
            }
1156
1157 1
            return $this->processParameterValue($value);
1158 29
        }, $this->parameters->getValues());
1159
1160 29
        ksort($hints);
1161
1162 29
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1163
    }
1164
}
1165