Failed Conditions
Pull Request — 2.7 (#8107)
by
unknown
06:45
created

AbstractQuery::__clone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 4
cts 4
cp 1
crap 1
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 1020
    public function __construct(EntityManagerInterface $em)
166
    {
167 1020
        $this->_em          = $em;
168 1020
        $this->parameters   = new ArrayCollection();
169 1020
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
170 1020
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
171
172 1020
        if ($this->hasCache) {
173 33
            $this->cacheLogger = $em->getConfiguration()
174 33
                ->getSecondLevelCacheConfiguration()
175 33
                ->getCacheLogger();
176
        }
177 1020
    }
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 142
    public function setCacheable($cacheable)
187
    {
188 142
        $this->cacheable = (boolean) $cacheable;
189
190 142
        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 30
    protected function isCacheEnabled()
227
    {
228 30
        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 935
    public function getEntityManager()
288
    {
289 935
        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
        $key = Query\Parameter::normalizeName($key);
326
327 255
        $filteredParameters = $this->parameters->filter(
328
            function (Query\Parameter $parameter) use ($key) : bool {
329 161
                $parameterName = $parameter->getName();
330
331 161
                return $key === $parameterName;
332 255
            }
333
        );
334
335 255
        return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
336
    }
337
338
    /**
339
     * Sets a collection of query parameters.
340
     *
341
     * @param ArrayCollection|mixed[] $parameters
342
     *
343
     * @return static This query instance.
344
     */
345 200
    public function setParameters($parameters)
346
    {
347
        // BC compatibility with 2.3-
348 200
        if (is_array($parameters)) {
349 4
            $parameterCollection = new ArrayCollection();
350
351 4
            foreach ($parameters as $key => $value) {
352 4
                $parameterCollection->add(new Parameter($key, $value));
353
            }
354
355 4
            $parameters = $parameterCollection;
356
        }
357
358 200
        $this->parameters = $parameters;
359
360 200
        return $this;
361
    }
362
363
    /**
364
     * Sets a query parameter.
365
     *
366
     * @param string|int  $key   The parameter position or name.
367
     * @param mixed       $value The parameter value.
368
     * @param string|null $type  The parameter type. If specified, the given value will be run through
369
     *                           the type conversion of this type. This is usually not needed for
370
     *                           strings and numeric types.
371
     *
372
     * @return static This query instance.
373
     */
374 176
    public function setParameter($key, $value, $type = null)
375
    {
376 176
        $existingParameter = $this->getParameter($key);
377
378 176
        if ($existingParameter !== null) {
379 57
            $existingParameter->setValue($value, $type);
380
381 57
            return $this;
382
        }
383
384 174
        $this->parameters->add(new Parameter($key, $value, $type));
385
386 174
        return $this;
387
    }
388
389
    /**
390
     * Processes an individual parameter value.
391
     *
392
     * @param mixed $value
393
     *
394
     * @return array|string
395
     *
396
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
397
     */
398 159
    public function processParameterValue($value)
399
    {
400 159
        if (is_scalar($value)) {
401 146
            return $value;
402
        }
403
404 65
        if ($value instanceof Collection) {
405 1
            $value = $value->toArray();
406
        }
407
408 65
        if (is_array($value)) {
409 48
            foreach ($value as $key => $paramValue) {
410 48
                $paramValue  = $this->processParameterValue($paramValue);
411 48
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
412
            }
413
414 48
            return $value;
415
        }
416
417 20
        if ($value instanceof Mapping\ClassMetadata) {
418 1
            return $value->name;
419
        }
420
421 19
        if (! is_object($value)) {
422 1
            return $value;
423
        }
424
425
        try {
426 18
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
427
428 16
            if ($value === null) {
429 16
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
430
            }
431 3
        } catch (MappingException | ORMMappingException $e) {
432
            // Silence any mapping exceptions. These can occur if the object in
433
            // question is not a mapped entity, in which case we just don't do
434
            // any preparation on the value.
435
        }
436
437 18
        return $value;
438
    }
439
440
    /**
441
     * Sets the ResultSetMapping that should be used for hydration.
442
     *
443
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
444
     *
445
     * @return static This query instance.
446
     */
447 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
448
    {
449 30
        $this->translateNamespaces($rsm);
450 30
        $this->_resultSetMapping = $rsm;
451
452 30
        return $this;
453
    }
454
455
    /**
456
     * Gets the ResultSetMapping used for hydration.
457
     *
458
     * @return \Doctrine\ORM\Query\ResultSetMapping
459
     */
460 22
    protected function getResultSetMapping()
461
    {
462 22
        return $this->_resultSetMapping;
463
    }
464
465
    /**
466
     * Allows to translate entity namespaces to full qualified names.
467
     *
468
     * @param Query\ResultSetMapping $rsm
469
     *
470
     * @return void
471
     */
472 30
    private function translateNamespaces(Query\ResultSetMapping $rsm)
473
    {
474
        $translate = function ($alias) {
475 18
            return $this->_em->getClassMetadata($alias)->getName();
476 30
        };
477
478 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
479 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
480 30
    }
481
482
    /**
483
     * Set a cache profile for hydration caching.
484
     *
485
     * If no result cache driver is set in the QueryCacheProfile, the default
486
     * result cache driver is used from the configuration.
487
     *
488
     * Important: Hydration caching does NOT register entities in the
489
     * UnitOfWork when retrieved from the cache. Never use result cached
490
     * entities for requests that also flush the EntityManager. If you want
491
     * some form of caching with UnitOfWork registration you should use
492
     * {@see AbstractQuery::setResultCacheProfile()}.
493
     *
494
     * @example
495
     * $lifetime = 100;
496
     * $resultKey = "abc";
497
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
498
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
499
     *
500
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
501
     *
502
     * @return static This query instance.
503
     */
504 3
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
505
    {
506 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
507
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
508
            $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

508
            $profile = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
509
        }
510
511 3
        $this->_hydrationCacheProfile = $profile;
512
513 3
        return $this;
514
    }
515
516
    /**
517
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
518
     */
519 3
    public function getHydrationCacheProfile()
520
    {
521 3
        return $this->_hydrationCacheProfile;
522
    }
523
524
    /**
525
     * Set a cache profile for the result cache.
526
     *
527
     * If no result cache driver is set in the QueryCacheProfile, the default
528
     * result cache driver is used from the configuration.
529
     *
530
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
531
     *
532
     * @return static This query instance.
533
     */
534 1
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
535
    {
536 1
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
537
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
538
            $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

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

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