Failed Conditions
Push — 2.7 ( c036c0...266f0d )
by Jonathan
57:23 queued 50:07
created

lib/Doctrine/ORM/AbstractQuery.php (7 issues)

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

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

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

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

536
            $profile = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
537
        }
538
539 1
        $this->_queryCacheProfile = $profile;
540
541 1
        return $this;
542
    }
543
544
    /**
545
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
546
     *
547
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
548
     *
549
     * @return static This query instance.
550
     *
551
     * @throws ORMException
552
     */
553 8
    public function setResultCacheDriver($resultCacheDriver = null)
554
    {
555 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
0 ignored issues
show
$resultCacheDriver is always a sub-type of Doctrine\Common\Cache\Cache.
Loading history...
556
            throw ORMException::invalidResultCacheDriver();
557
        }
558
559 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
560 1
            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
0 ignored issues
show
It seems like $resultCacheDriver can also be of type null; however, parameter $cache of Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does only seem to accept Doctrine\Common\Cache\Cache, maybe add an additional type check? ( Ignorable by Annotation )

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

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