Failed Conditions
Pull Request — 2.6 (#7506)
by
unknown
09:52
created

AbstractQuery::setResultCacheProfile()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.3332

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 2
nop 1
dl 0
loc 10
ccs 4
cts 6
cp 0.6667
crap 3.3332
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 ArrayCollection|Parameter[]
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 1015
    public function __construct(EntityManagerInterface $em)
166
    {
167 1015
        $this->_em          = $em;
168 1015
        $this->parameters   = new ArrayCollection();
169 1015
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
170 1015
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
171
172 1015
        if ($this->hasCache) {
173 32
            $this->cacheLogger = $em->getConfiguration()
174 32
                ->getSecondLevelCacheConfiguration()
175 32
                ->getCacheLogger();
176
        }
177 1015
    }
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 141
    public function setCacheable($cacheable)
187
    {
188 141
        $this->cacheable = (boolean) $cacheable;
189
190 141
        return $this;
191
    }
192
193
    /**
194
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
195
     */
196 97
    public function isCacheable()
197
    {
198 97
        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 930
    public function getEntityManager()
288
    {
289 930
        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 ArrayCollection The defined query parameters.
310
     */
311 151
    public function getParameters()
312
    {
313 151
        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 255
    public function getParameter($key)
324
    {
325 255
        $filteredParameters = $this->parameters->filter(
326
            function (Query\Parameter $parameter) use ($key) : bool {
327 161
                $parameterName = $parameter->getName();
328
329 161
                return $key === $parameterName || (string) $key === (string) $parameterName;
330 255
            }
331
        );
332
333 255
        return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
334
    }
335
336
    /**
337
     * Sets a collection of query parameters.
338
     *
339
     * @param ArrayCollection|mixed[] $parameters
340
     *
341
     * @return static This query instance.
342
     */
343 200
    public function setParameters($parameters)
344
    {
345
        // BC compatibility with 2.3-
346 200
        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 200
        $this->parameters = $parameters;
357
358 200
        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 176
    public function setParameter($key, $value, $type = null)
373
    {
374 176
        $existingParameter = $this->getParameter($key);
375
376 176
        if ($existingParameter !== null) {
377 57
            $existingParameter->setValue($value, $type);
378
379 57
            return $this;
380
        }
381
382 174
        $this->parameters->add(new Parameter($key, $value, $type));
383
384 174
        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 159
    public function processParameterValue($value)
397
    {
398 159
        if (is_scalar($value)) {
399 146
            return $value;
400
        }
401
402 65
        if ($value instanceof Collection) {
403 1
            $value = $value->toArray();
404
        }
405
406 65
        if (is_array($value)) {
407 48
            foreach ($value as $key => $paramValue) {
408 48
                $paramValue  = $this->processParameterValue($paramValue);
409 48
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
410
            }
411
412 48
            return $value;
413
        }
414
415 20
        if ($value instanceof Mapping\ClassMetadata) {
416 1
            return $value->name;
417
        }
418
419 19
        if (! is_object($value)) {
420 1
            return $value;
421
        }
422
423
        try {
424 18
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
425
426 16
            if ($value === null) {
427 16
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
428
            }
429 3
        } 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 18
        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 30
    private function translateNamespaces(Query\ResultSetMapping $rsm)
471
    {
472
        $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 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
587
     *
588
     * @param bool   $useCache
589
     * @param int    $lifetime
590
     * @param string $resultCacheId
591
     *
592
     * @return static This query instance.
593
     */
594 4
    public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
595
    {
596 4
        return $useCache
597 3
            ? $this->enableResultCache($lifetime, $resultCacheId)
598 4
            : $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 int|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 400
    public function setHydrationMode($hydrationMode)
723
    {
724 400
        $this->_hydrationMode = $hydrationMode;
725
726 400
        return $this;
727
    }
728
729
    /**
730
     * Gets the hydration mode currently used by the query.
731
     *
732
     * @return string|int
733
     */
734 677
    public function getHydrationMode()
735
    {
736 677
        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 321
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
749
    {
750 321
        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 63
    public function getScalarResult()
773
    {
774 63
        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 19
    public function getOneOrNullResult($hydrationMode = null)
787
    {
788
        try {
789 19
            $result = $this->execute(null, $hydrationMode);
790 1
        } catch (NoResultException $e) {
791 1
            return null;
792
        }
793
794
795 18
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
796 3
            return null;
797
        }
798
799 16
        if ( ! is_array($result)) {
800 1
            return $result;
801
        }
802
803 16
        if (count($result) > 1) {
804 1
            throw new NonUniqueResultException;
805
        }
806
807 15
        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.
850
     *
851
     * @throws NoResultException        If the query returned no result.
852
     * @throws NonUniqueResultException If the query result is not unique.
853
     */
854 11
    public function getSingleScalarResult()
855
    {
856 11
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
857
    }
858
859
    /**
860
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
861
     *
862
     * @param string $name  The name of the hint.
863
     * @param mixed  $value The value of the hint.
864
     *
865
     * @return static This query instance.
866
     */
867 448
    public function setHint($name, $value)
868
    {
869 448
        $this->_hints[$name] = $value;
870
871 448
        return $this;
872
    }
873
874
    /**
875
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
876
     *
877
     * @param string $name The name of the hint.
878
     *
879
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
880
     */
881 807
    public function getHint($name)
882
    {
883 807
        return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
884
    }
885
886
    /**
887
     * Check if the query has a hint
888
     *
889
     * @param string $name The name of the hint
890
     *
891
     * @return bool False if the query does not have any hint
892
     */
893 21
    public function hasHint($name)
894
    {
895 21
        return isset($this->_hints[$name]);
896
    }
897
898
    /**
899
     * Return the key value map of query hints that are currently set.
900
     *
901
     * @return array
902
     */
903 144
    public function getHints()
904
    {
905 144
        return $this->_hints;
906
    }
907
908
    /**
909
     * Executes the query and returns an IterableResult that can be used to incrementally
910
     * iterate over the result.
911
     *
912
     * @param ArrayCollection|array|null $parameters    The query parameters.
913
     * @param string|int|null            $hydrationMode The hydration mode to use.
914
     *
915
     * @return \Doctrine\ORM\Internal\Hydration\IterableResult
916
     */
917 10
    public function iterate($parameters = null, $hydrationMode = null)
918
    {
919 10
        if ($hydrationMode !== null) {
920 10
            $this->setHydrationMode($hydrationMode);
921
        }
922
923 10
        if ( ! empty($parameters)) {
924 1
            $this->setParameters($parameters);
925
        }
926
927 10
        $rsm  = $this->getResultSetMapping();
928 7
        $stmt = $this->_doExecute();
929
930 7
        return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
931
    }
932
933
    /**
934
     * Executes the query.
935
     *
936
     * @param ArrayCollection|array|null $parameters Query parameters.
937
     * @param string|int|null            $hydrationMode Processing mode to be used during the hydration process.
938
     *
939
     * @return mixed
940
     */
941 503
    public function execute($parameters = null, $hydrationMode = null)
942
    {
943 503
        if ($this->cacheable && $this->isCacheEnabled()) {
944 29
            return $this->executeUsingQueryCache($parameters, $hydrationMode);
945
        }
946
947 476
        return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
948
    }
949
950
    /**
951
     * Execute query ignoring second level cache.
952
     *
953
     * @param ArrayCollection|array|null $parameters
954
     * @param string|int|null            $hydrationMode
955
     *
956
     * @return mixed
957
     */
958 503
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
959
    {
960 503
        if ($hydrationMode !== null) {
961 390
            $this->setHydrationMode($hydrationMode);
962
        }
963
964 503
        if ( ! empty($parameters)) {
965
            $this->setParameters($parameters);
966
        }
967
968
        $setCacheEntry = function() {};
969
970 503
        if ($this->_hydrationCacheProfile !== null) {
971 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
972
973 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
974 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
975 2
            $result            = $cache->fetch($cacheKey);
976
977 2
            if (isset($result[$realCacheKey])) {
978 2
                return $result[$realCacheKey];
979
            }
980
981 2
            if ( ! $result) {
982 2
                $result = [];
983
            }
984
985
            $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
986 2
                $result[$realCacheKey] = $data;
987
988 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
989 2
            };
990
        }
991
992 503
        $stmt = $this->_doExecute();
993
994 493
        if (is_numeric($stmt)) {
0 ignored issues
show
introduced by
The condition is_numeric($stmt) is always false.
Loading history...
995 29
            $setCacheEntry($stmt);
996
997 29
            return $stmt;
998
        }
999
1000 478
        $rsm  = $this->getResultSetMapping();
1001 478
        $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
1002
1003 474
        $setCacheEntry($data);
1004
1005 474
        return $data;
1006
    }
1007
1008
    /**
1009
     * Load from second level cache or executes the query and put into cache.
1010
     *
1011
     * @param ArrayCollection|array|null $parameters
1012
     * @param string|int|null            $hydrationMode
1013
     *
1014
     * @return mixed
1015
     */
1016 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
1017
    {
1018 29
        $rsm        = $this->getResultSetMapping();
1019 29
        $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
1020 29
        $queryKey   = new QueryCacheKey(
1021 29
            $this->getHash(),
1022 29
            $this->lifetime,
1023 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
1024 29
            $this->getTimestampKey()
1025
        );
1026
1027 29
        $result     = $queryCache->get($queryKey, $rsm, $this->_hints);
1028
1029 29
        if ($result !== null) {
1030 16
            if ($this->cacheLogger) {
1031 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
1032
            }
1033
1034 16
            return $result;
1035
        }
1036
1037 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
1038 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
1039
1040 26
        if ($this->cacheLogger) {
1041 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
1042
1043 26
            if ($cached) {
1044 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
1045
            }
1046
        }
1047
1048 26
        return $result;
1049
    }
1050
1051
    /**
1052
     * @return \Doctrine\ORM\Cache\TimestampCacheKey|null
1053
     */
1054 29
    private function getTimestampKey()
1055
    {
1056 29
        $entityName = reset($this->_resultSetMapping->aliasMap);
1057
1058 29
        if (empty($entityName)) {
1059 2
            return null;
1060
        }
1061
1062 27
        $metadata = $this->_em->getClassMetadata($entityName);
1063
1064 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...
1065
    }
1066
1067
    /**
1068
     * Get the result cache id to use to store the result set cache entry.
1069
     * Will return the configured id if it exists otherwise a hash will be
1070
     * automatically generated for you.
1071
     *
1072
     * @return array ($key, $hash)
1073
     */
1074 2
    protected function getHydrationCacheId()
1075
    {
1076 2
        $parameters = [];
1077
1078 2
        foreach ($this->getParameters() as $parameter) {
1079 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1080
        }
1081
1082 2
        $sql                    = $this->getSQL();
1083 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1084 2
        $hints                  = $this->getHints();
1085 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1086
1087 2
        ksort($hints);
1088
1089 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1090
    }
1091
1092
    /**
1093
     * Set the result cache id to use to store the result set cache entry.
1094
     * If this is not explicitly set by the developer then a hash is automatically
1095
     * generated for you.
1096
     *
1097
     * @param string $id
1098
     *
1099
     * @return static This query instance.
1100
     */
1101 16
    public function setResultCacheId($id)
1102
    {
1103 16
        $this->_queryCacheProfile = $this->_queryCacheProfile
1104 16
            ? $this->_queryCacheProfile->setCacheKey($id)
1105
            : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
1106
1107 16
        return $this;
1108
    }
1109
1110
    /**
1111
     * Get the result cache id to use to store the result set cache entry if set.
1112
     *
1113
     * @deprecated
1114
     *
1115
     * @return string
1116
     */
1117
    public function getResultCacheId()
1118
    {
1119
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
1120
    }
1121
1122
    /**
1123
     * Executes the query and returns a the resulting Statement object.
1124
     *
1125
     * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
1126
     */
1127
    abstract protected function _doExecute();
1128
1129
    /**
1130
     * Cleanup Query resource when clone is called.
1131
     *
1132
     * @return void
1133
     */
1134 147
    public function __clone()
1135
    {
1136 147
        $this->parameters = new ArrayCollection();
1137
1138 147
        $this->_hints = [];
1139 147
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
1140 147
    }
1141
1142
    /**
1143
     * Generates a string of currently query to use for the cache second level cache.
1144
     *
1145
     * @return string
1146
     */
1147 29
    protected function getHash()
1148
    {
1149 29
        $query  = $this->getSQL();
1150 29
        $hints  = $this->getHints();
1151
        $params = array_map(function(Parameter $parameter) {
1152
            // Small optimization
1153
            // Does not invoke processParameterValue for scalar values
1154 5
            if (is_scalar($value = $parameter->getValue())) {
1155 4
                return $value;
1156
            }
1157
1158 1
            return $this->processParameterValue($value);
1159 29
        }, $this->parameters->getValues());
1160
1161 29
        ksort($hints);
1162
1163 29
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1164
    }
1165
}
1166