Passed
Pull Request — 2.6 (#6210)
by Christophe
08:02
created

AbstractQuery::getTimestampKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 2
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\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 1000
    public function __construct(EntityManagerInterface $em)
164
    {
165 1000
        $this->_em          = $em;
166 1000
        $this->parameters   = new ArrayCollection();
167 1000
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
168 1000
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
169
170 1000
        if ($this->hasCache) {
171 32
            $this->cacheLogger = $em->getConfiguration()
172 32
                ->getSecondLevelCacheConfiguration()
173 32
                ->getCacheLogger();
174
        }
175 1000
    }
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 136
    public function setCacheable($cacheable)
185
    {
186 136
        $this->cacheable = (boolean) $cacheable;
187
188 136
        return $this;
189
    }
190
191
    /**
192
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
193
     */
194 92
    public function isCacheable()
195
    {
196 92
        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 917
    public function getEntityManager()
286
    {
287 917
        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 212
    public function free()
298
    {
299 212
        $this->parameters = new ArrayCollection();
300
301 212
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
302 212
    }
303
304
    /**
305
     * Get all defined parameters.
306
     *
307
     * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
308
     */
309 146
    public function getParameters()
310
    {
311 146
        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 276
    public function getParameter($key)
322
    {
323 276
        $filteredParameters = $this->parameters->filter(
324 276
            function (Query\Parameter $parameter) use ($key) : bool {
325 158
                $parameterName = $parameter->getName();
326
327 158
                return $key === $parameterName || (string) $key === (string) $parameterName;
328 276
            }
329
        );
330
331 276
        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 194
    public function setParameters($parameters)
342
    {
343
        // BC compatibility with 2.3-
344 194
        if (is_array($parameters)) {
345 4
            $parameterCollection = new ArrayCollection();
346
347 4
            foreach ($parameters as $key => $value) {
348 4
                $parameterCollection->add(new Parameter($key, $value));
349
            }
350
351 4
            $parameters = $parameterCollection;
352
        }
353
354 194
        $this->parameters = $parameters;
355
356 194
        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 187
    public function setParameter($key, $value, $type = null)
371
    {
372 187
        $existingParameter = $this->getParameter($key);
373
374 187
        if ($existingParameter !== null) {
375 5
            $existingParameter->setValue($value, $type);
376
377 5
            return $this;
378
        }
379
380 185
        $this->parameters->add(new Parameter($key, $value, $type));
381
382 185
        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 184
    public function processParameterValue($value)
395
    {
396 184
        if (is_scalar($value)) {
397 171
            return $value;
398
        }
399
400 91
        if ($value instanceof Collection) {
401 1
            $value = $value->toArray();
402
        }
403
404 91
        if (is_array($value)) {
405 74
            foreach ($value as $key => $paramValue) {
406 74
                $paramValue  = $this->processParameterValue($paramValue);
407 74
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
408
            }
409
410 74
            return $value;
411
        }
412
413 20
        if (is_object($value) && (
414 20
            $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))
415 20
            || ! $this->_em->getMetadataFactory()->isTransient(ClassUtils::getClass($value))
416
        )) {
417 14
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
418
419 14
            if ($value === null) {
420
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
421
            }
422
        }
423
424 20
        if ($value instanceof Mapping\ClassMetadata) {
425 1
            return $value->name;
426
        }
427
428 19
        return $value;
429
    }
430
431
    /**
432
     * Sets the ResultSetMapping that should be used for hydration.
433
     *
434
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
435
     *
436
     * @return static This query instance.
437
     */
438 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
439
    {
440 30
        $this->translateNamespaces($rsm);
441 30
        $this->_resultSetMapping = $rsm;
442
443 30
        return $this;
444
    }
445
446
    /**
447
     * Gets the ResultSetMapping used for hydration.
448
     *
449
     * @return \Doctrine\ORM\Query\ResultSetMapping
450
     */
451 22
    protected function getResultSetMapping()
452
    {
453 22
        return $this->_resultSetMapping;
454
    }
455
456
    /**
457
     * Allows to translate entity namespaces to full qualified names.
458
     *
459
     * @param Query\ResultSetMapping $rsm
460
     *
461
     * @return void
462
     */
463
    private function translateNamespaces(Query\ResultSetMapping $rsm)
464
    {
465 30
        $translate = function ($alias) {
466 18
            return $this->_em->getClassMetadata($alias)->getName();
467 30
        };
468
469 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
470 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
471 30
    }
472
473
    /**
474
     * Set a cache profile for hydration caching.
475
     *
476
     * If no result cache driver is set in the QueryCacheProfile, the default
477
     * result cache driver is used from the configuration.
478
     *
479
     * Important: Hydration caching does NOT register entities in the
480
     * UnitOfWork when retrieved from the cache. Never use result cached
481
     * entities for requests that also flush the EntityManager. If you want
482
     * some form of caching with UnitOfWork registration you should use
483
     * {@see AbstractQuery::setResultCacheProfile()}.
484
     *
485
     * @example
486
     * $lifetime = 100;
487
     * $resultKey = "abc";
488
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
489
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
490
     *
491
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
492
     *
493
     * @return static This query instance.
494
     */
495 3
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
496
    {
497 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
498
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
499
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
500
        }
501
502 3
        $this->_hydrationCacheProfile = $profile;
503
504 3
        return $this;
505
    }
506
507
    /**
508
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
509
     */
510 3
    public function getHydrationCacheProfile()
511
    {
512 3
        return $this->_hydrationCacheProfile;
513
    }
514
515
    /**
516
     * Set a cache profile for the result cache.
517
     *
518
     * If no result cache driver is set in the QueryCacheProfile, the default
519
     * result cache driver is used from the configuration.
520
     *
521
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
522
     *
523
     * @return static This query instance.
524
     */
525 1
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
526
    {
527 1
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
528
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
529
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
530
        }
531
532 1
        $this->_queryCacheProfile = $profile;
533
534 1
        return $this;
535
    }
536
537
    /**
538
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
539
     *
540
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
541
     *
542
     * @return static This query instance.
543
     *
544
     * @throws ORMException
545
     */
546 8
    public function setResultCacheDriver($resultCacheDriver = null)
547
    {
548 8
        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...
549
            throw ORMException::invalidResultCacheDriver();
550
        }
551
552 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
553 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

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