Completed
Push — master ( b47a39...b7cace )
by Luís
18s
created

AbstractQuery::executeUsingQueryCache()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

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

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

524
        if ( ! $profile->/** @scrutinizer ignore-call */ getResultCacheDriver()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
525
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
526
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
527
        }
528
529
        $this->_queryCacheProfile = $profile;
530
531
        return $this;
532
    }
533
534
    /**
535
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
536
     *
537
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
538
     *
539
     * @return static This query instance.
540
     *
541
     * @throws ORMException
542
     */
543 8
    public function setResultCacheDriver($resultCacheDriver = null)
544
    {
545 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
546
            throw ORMException::invalidResultCacheDriver();
547
        }
548
549 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
550 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

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