Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

lib/Doctrine/ORM/AbstractQuery.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 968
    public function __construct(EntityManagerInterface $em)
164
    {
165 968
        $this->_em          = $em;
166 968
        $this->parameters   = new ArrayCollection();
167 968
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
168 968
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
169
170 968
        if ($this->hasCache) {
171 32
            $this->cacheLogger = $em->getConfiguration()
172 32
                ->getSecondLevelCacheConfiguration()
173 32
                ->getCacheLogger();
174
        }
175 968
    }
176
177
    /**
178
     * Enable/disable second level query (result) caching for this query.
179
     *
180
     * @param boolean $cacheable
181
     *
182
     * @return static This query instance.
183
     */
184 133
    public function setCacheable($cacheable)
185
    {
186 133
        $this->cacheable = (boolean) $cacheable;
187
188 133
        return $this;
189
    }
190
191
    /**
192
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
193
     */
194 90
    public function isCacheable()
195
    {
196 90
        return $this->cacheable;
197
    }
198
199
    /**
200
     * @param string $cacheRegion
201
     *
202
     * @return static This query instance.
203
     */
204 2
    public function setCacheRegion($cacheRegion)
205
    {
206 2
        $this->cacheRegion = (string) $cacheRegion;
207
208 2
        return $this;
209
    }
210
211
    /**
212
    * Obtain the name of the second level query cache region in which query results will be stored
213
    *
214
    * @return string|null The cache region name; NULL indicates the default region.
215
    */
216 1
    public function getCacheRegion()
217
    {
218 1
        return $this->cacheRegion;
219
    }
220
221
    /**
222
     * @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
223
     */
224 29
    protected function isCacheEnabled()
225
    {
226 29
        return $this->cacheable && $this->hasCache;
227
    }
228
229
    /**
230
     * @return integer
231
     */
232 1
    public function getLifetime()
233
    {
234 1
        return $this->lifetime;
235
    }
236
237
    /**
238
     * Sets the life-time for this query into second level cache.
239
     *
240
     * @param integer $lifetime
241
     *
242
     * @return \Doctrine\ORM\AbstractQuery This query instance.
243
     */
244 2
    public function setLifetime($lifetime)
245
    {
246 2
        $this->lifetime = (integer) $lifetime;
247
248 2
        return $this;
249
    }
250
251
    /**
252
     * @return integer
253
     */
254 1
    public function getCacheMode()
255
    {
256 1
        return $this->cacheMode;
257
    }
258
259
    /**
260
     * @param integer $cacheMode
261
     *
262
     * @return \Doctrine\ORM\AbstractQuery This query instance.
263
     */
264 4
    public function setCacheMode($cacheMode)
265
    {
266 4
        $this->cacheMode = (integer) $cacheMode;
267
268 4
        return $this;
269
    }
270
271
    /**
272
     * Gets the SQL query that corresponds to this query object.
273
     * The returned SQL syntax depends on the connection driver that is used
274
     * by this query object at the time of this method call.
275
     *
276
     * @return string SQL query
277
     */
278
    abstract public function getSQL();
279
280
    /**
281
     * Retrieves the associated EntityManager of this Query instance.
282
     *
283
     * @return \Doctrine\ORM\EntityManager
284
     */
285 890
    public function getEntityManager()
286
    {
287 890
        return $this->_em;
288
    }
289
290
    /**
291
     * Frees the resources used by the query object.
292
     *
293
     * Resets Parameters, Parameter Types and Query Hints.
294
     *
295
     * @return void
296
     */
297 211
    public function free()
298
    {
299 211
        $this->parameters = new ArrayCollection();
300
301 211
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
302 211
    }
303
304
    /**
305
     * Get all defined parameters.
306
     *
307
     * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
308
     */
309 137
    public function getParameters()
310
    {
311 137
        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 219 View Code Duplication
    public function getParameter($key)
322
    {
323 219
        $filteredParameters = $this->parameters->filter(
324 219
            function ($parameter) use ($key)
325
            {
326
                // Must not be identical because of string to integer conversion
327 145
                return ($key == $parameter->getName());
328 219
            }
329
        );
330
331 219
        return count($filteredParameters) ? $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 186 View Code Duplication
    public function setParameters($parameters)
342
    {
343
        // BC compatibility with 2.3-
344 186
        if (is_array($parameters)) {
345 1
            $parameterCollection = new ArrayCollection();
346
347 1
            foreach ($parameters as $key => $value) {
348 1
                $parameterCollection->add(new Parameter($key, $value));
349
            }
350
351 1
            $parameters = $parameterCollection;
352
        }
353
354 186
        $this->parameters = $parameters;
355
356 186
        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 179 View Code Duplication
    public function setParameter($key, $value, $type = null)
371
    {
372 179
        $filteredParameters = $this->parameters->filter(
373 179
            function ($parameter) use ($key)
374
            {
375
                // Must not be identical because of string to integer conversion
376 17
                return ($key == $parameter->getName());
377 179
            }
378
        );
379
380 179
        if (count($filteredParameters)) {
381 4
            $parameter = $filteredParameters->first();
382 4
            $parameter->setValue($value, $type);
383
384 4
            return $this;
385
        }
386
387 177
        $this->parameters->add(new Parameter($key, $value, $type));
388
389 177
        return $this;
390
    }
391
392
    /**
393
     * Processes an individual parameter value.
394
     *
395
     * @param mixed $value
396
     *
397
     * @return array|string
398
     *
399
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
400
     */
401 177
    public function processParameterValue($value)
402
    {
403 177
        if (is_scalar($value)) {
404 164
            return $value;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $value; (integer|double|string|boolean) is incompatible with the return type documented by Doctrine\ORM\AbstractQuery::processParameterValue of type array|string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
405
        }
406
407 90
        if ($value instanceof Collection) {
408 1
            $value = $value->toArray();
409
        }
410
411 90
        if (is_array($value)) {
412 73
            foreach ($value as $key => $paramValue) {
413 73
                $paramValue  = $this->processParameterValue($paramValue);
414 73
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
415
            }
416
417 73
            return $value;
418
        }
419
420 20
        if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
421 14
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
422
423 14
            if ($value === null) {
424
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
425
            }
426
        }
427
428 20
        if ($value instanceof Mapping\ClassMetadata) {
429 1
            return $value->name;
430
        }
431
432 19
        return $value;
433
    }
434
435
    /**
436
     * Sets the ResultSetMapping that should be used for hydration.
437
     *
438
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
439
     *
440
     * @return static This query instance.
441
     */
442 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
443
    {
444 30
        $this->translateNamespaces($rsm);
445 30
        $this->_resultSetMapping = $rsm;
446
447 30
        return $this;
448
    }
449
450
    /**
451
     * Gets the ResultSetMapping used for hydration.
452
     *
453
     * @return \Doctrine\ORM\Query\ResultSetMapping
454
     */
455 22
    protected function getResultSetMapping()
456
    {
457 22
        return $this->_resultSetMapping;
458
    }
459
460
    /**
461
     * Allows to translate entity namespaces to full qualified names.
462
     *
463
     * @param Query\ResultSetMapping $rsm
464
     *
465
     * @return void
466
     */
467
    private function translateNamespaces(Query\ResultSetMapping $rsm)
468
    {
469 30
        $translate = function ($alias) {
470 18
            return $this->_em->getClassMetadata($alias)->getName();
471 30
        };
472
473 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
474 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
475 30
    }
476
477
    /**
478
     * Set a cache profile for hydration caching.
479
     *
480
     * If no result cache driver is set in the QueryCacheProfile, the default
481
     * result cache driver is used from the configuration.
482
     *
483
     * Important: Hydration caching does NOT register entities in the
484
     * UnitOfWork when retrieved from the cache. Never use result cached
485
     * entities for requests that also flush the EntityManager. If you want
486
     * some form of caching with UnitOfWork registration you should use
487
     * {@see AbstractQuery::setResultCacheProfile()}.
488
     *
489
     * @example
490
     * $lifetime = 100;
491
     * $resultKey = "abc";
492
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
493
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
494
     *
495
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
496
     *
497
     * @return static This query instance.
498
     */
499 3 View Code Duplication
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
500
    {
501 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
502
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
503
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
504
        }
505
506 3
        $this->_hydrationCacheProfile = $profile;
507
508 3
        return $this;
509
    }
510
511
    /**
512
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
513
     */
514 3
    public function getHydrationCacheProfile()
515
    {
516 3
        return $this->_hydrationCacheProfile;
517
    }
518
519
    /**
520
     * Set a cache profile for the result cache.
521
     *
522
     * If no result cache driver is set in the QueryCacheProfile, the default
523
     * result cache driver is used from the configuration.
524
     *
525
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
526
     *
527
     * @return static This query instance.
528
     */
529 View Code Duplication
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
530
    {
531
        if ( ! $profile->getResultCacheDriver()) {
532
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
533
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
534
        }
535
536
        $this->_queryCacheProfile = $profile;
537
538
        return $this;
539
    }
540
541
    /**
542
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
543
     *
544
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
545
     *
546
     * @return static This query instance.
547
     *
548
     * @throws ORMException
549
     */
550 8
    public function setResultCacheDriver($resultCacheDriver = null)
551
    {
552 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
553
            throw ORMException::invalidResultCacheDriver();
554
        }
555
556 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
557 1
            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
558 7
            : new QueryCacheProfile(0, null, $resultCacheDriver);
559
560 8
        return $this;
561
    }
562
563
    /**
564
     * Returns the cache driver used for caching result sets.
565
     *
566
     * @deprecated
567
     *
568
     * @return \Doctrine\Common\Cache\Cache Cache driver
569
     */
570 3
    public function getResultCacheDriver()
571
    {
572 3
        if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
573 3
            return $this->_queryCacheProfile->getResultCacheDriver();
574
        }
575
576
        return $this->_em->getConfiguration()->getResultCacheImpl();
577
    }
578
579
    /**
580
     * Set whether or not to cache the results of this query and if so, for
581
     * how long and which ID to use for the cache entry.
582
     *
583
     * @param boolean $bool
584
     * @param integer $lifetime
585
     * @param string  $resultCacheId
586
     *
587
     * @return static This query instance.
588
     */
589 8
    public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
590
    {
591 8
        if ($bool) {
592 8
            $this->setResultCacheLifetime($lifetime);
593 8
            $this->setResultCacheId($resultCacheId);
594
595 8
            return $this;
596
        }
597
598 1
        $this->_queryCacheProfile = null;
599
600 1
        return $this;
601
    }
602
603
    /**
604
     * Defines how long the result cache will be active before expire.
605
     *
606
     * @param integer $lifetime How long the cache entry is valid.
607
     *
608
     * @return static This query instance.
609
     */
610 9
    public function setResultCacheLifetime($lifetime)
611
    {
612 9
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
613
614 9
        $this->_queryCacheProfile = $this->_queryCacheProfile
615 4
            ? $this->_queryCacheProfile->setLifetime($lifetime)
616 5
            : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
617
618 9
        return $this;
619
    }
620
621
    /**
622
     * Retrieves the lifetime of resultset cache.
623
     *
624
     * @deprecated
625
     *
626
     * @return integer
627
     */
628
    public function getResultCacheLifetime()
629
    {
630
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
631
    }
632
633
    /**
634
     * Defines if the result cache is active or not.
635
     *
636
     * @param boolean $expire Whether or not to force resultset cache expiration.
637
     *
638
     * @return static This query instance.
639
     */
640 4
    public function expireResultCache($expire = true)
641
    {
642 4
        $this->_expireResultCache = $expire;
643
644 4
        return $this;
645
    }
646
647
    /**
648
     * Retrieves if the resultset cache is active or not.
649
     *
650
     * @return boolean
651
     */
652 8
    public function getExpireResultCache()
653
    {
654 8
        return $this->_expireResultCache;
655
    }
656
657
    /**
658
     * @return QueryCacheProfile
659
     */
660 1
    public function getQueryCacheProfile()
661
    {
662 1
        return $this->_queryCacheProfile;
663
    }
664
665
    /**
666
     * Change the default fetch mode of an association for this query.
667
     *
668
     * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
669
     *
670
     * @param string $class
671
     * @param string $assocName
672
     * @param int    $fetchMode
673
     *
674
     * @return static This query instance.
675
     */
676 3
    public function setFetchMode($class, $assocName, $fetchMode)
677
    {
678 3
        if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
679
            $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
680
        }
681
682 3
        $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
683
684 3
        return $this;
685
    }
686
687
    /**
688
     * Defines the processing mode to be used during hydration / result set transformation.
689
     *
690
     * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
691
     *                               One of the Query::HYDRATE_* constants.
692
     *
693
     * @return static This query instance.
694
     */
695 376
    public function setHydrationMode($hydrationMode)
696
    {
697 376
        $this->_hydrationMode = $hydrationMode;
698
699 376
        return $this;
700
    }
701
702
    /**
703
     * Gets the hydration mode currently used by the query.
704
     *
705
     * @return integer
706
     */
707 644
    public function getHydrationMode()
708
    {
709 644
        return $this->_hydrationMode;
710
    }
711
712
    /**
713
     * Gets the list of results for the query.
714
     *
715
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
716
     *
717
     * @param int $hydrationMode
718
     *
719
     * @return mixed
720
     */
721 308
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
722
    {
723 308
        return $this->execute(null, $hydrationMode);
724
    }
725
726
    /**
727
     * Gets the array of results for the query.
728
     *
729
     * Alias for execute(null, HYDRATE_ARRAY).
730
     *
731
     * @return array
732
     */
733 29
    public function getArrayResult()
734
    {
735 29
        return $this->execute(null, self::HYDRATE_ARRAY);
736
    }
737
738
    /**
739
     * Gets the scalar results for the query.
740
     *
741
     * Alias for execute(null, HYDRATE_SCALAR).
742
     *
743
     * @return array
744
     */
745 87
    public function getScalarResult()
746
    {
747 87
        return $this->execute(null, self::HYDRATE_SCALAR);
748
    }
749
750
    /**
751
     * Get exactly one result or null.
752
     *
753
     * @param int $hydrationMode
754
     *
755
     * @return mixed
756
     *
757
     * @throws NonUniqueResultException
758
     */
759 16
    public function getOneOrNullResult($hydrationMode = null)
760
    {
761
        try {
762 16
            $result = $this->execute(null, $hydrationMode);
763 1
        } catch (NoResultException $e) {
764 1
            return null;
765
        }
766
767
768 15
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
769 2
            return null;
770
        }
771
772 13
        if ( ! is_array($result)) {
773 1
            return $result;
774
        }
775
776 13
        if (count($result) > 1) {
777 1
            throw new NonUniqueResultException;
778
        }
779
780 12
        return array_shift($result);
781
    }
782
783
    /**
784
     * Gets the single result of the query.
785
     *
786
     * Enforces the presence as well as the uniqueness of the result.
787
     *
788
     * If the result is not unique, a NonUniqueResultException is thrown.
789
     * If there is no result, a NoResultException is thrown.
790
     *
791
     * @param integer $hydrationMode
792
     *
793
     * @return mixed
794
     *
795
     * @throws NonUniqueResultException If the query result is not unique.
796
     * @throws NoResultException        If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
797
     */
798 91
    public function getSingleResult($hydrationMode = null)
799
    {
800 91
        $result = $this->execute(null, $hydrationMode);
801
802 85
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
803 2
            throw new NoResultException;
804
        }
805
806 84
        if ( ! is_array($result)) {
807 9
            return $result;
808
        }
809
810 76
        if (count($result) > 1) {
811 1
            throw new NonUniqueResultException;
812
        }
813
814 75
        return array_shift($result);
815
    }
816
817
    /**
818
     * Gets the single scalar result of the query.
819
     *
820
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
821
     *
822
     * @return mixed The scalar result, or NULL if the query returned no result.
823
     *
824
     * @throws NonUniqueResultException If the query result is not unique.
825
     */
826 11
    public function getSingleScalarResult()
827
    {
828 11
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
829
    }
830
831
    /**
832
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
833
     *
834
     * @param string $name  The name of the hint.
835
     * @param mixed  $value The value of the hint.
836
     *
837
     * @return static This query instance.
838
     */
839 463
    public function setHint($name, $value)
840
    {
841 463
        $this->_hints[$name] = $value;
842
843 463
        return $this;
844
    }
845
846
    /**
847
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
848
     *
849
     * @param string $name The name of the hint.
850
     *
851
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
852
     */
853 788
    public function getHint($name)
854
    {
855 788
        return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
856
    }
857
858
    /**
859
     * Check if the query has a hint
860
     *
861
     * @param string $name The name of the hint
862
     *
863
     * @return bool False if the query does not have any hint
864
     */
865 17
    public function hasHint($name)
866
    {
867 17
        return isset($this->_hints[$name]);
868
    }
869
870
    /**
871
     * Return the key value map of query hints that are currently set.
872
     *
873
     * @return array
874
     */
875 136
    public function getHints()
876
    {
877 136
        return $this->_hints;
878
    }
879
880
    /**
881
     * Executes the query and returns an IterableResult that can be used to incrementally
882
     * iterate over the result.
883
     *
884
     * @param ArrayCollection|array|null $parameters    The query parameters.
885
     * @param integer|null               $hydrationMode The hydration mode to use.
886
     *
887
     * @return \Doctrine\ORM\Internal\Hydration\IterableResult
888
     */
889 10
    public function iterate($parameters = null, $hydrationMode = null)
890
    {
891 10
        if ($hydrationMode !== null) {
892 10
            $this->setHydrationMode($hydrationMode);
893
        }
894
895 10
        if ( ! empty($parameters)) {
896 1
            $this->setParameters($parameters);
897
        }
898
899 10
        $rsm  = $this->getResultSetMapping();
900 7
        $stmt = $this->_doExecute();
901
902 7
        return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
903
    }
904
905
    /**
906
     * Executes the query.
907
     *
908
     * @param ArrayCollection|array|null $parameters Query parameters.
909
     * @param integer|null               $hydrationMode Processing mode to be used during the hydration process.
910
     *
911
     * @return mixed
912
     */
913 472
    public function execute($parameters = null, $hydrationMode = null)
914
    {
915 472
        if ($this->cacheable && $this->isCacheEnabled()) {
916 29
            return $this->executeUsingQueryCache($parameters, $hydrationMode);
917
        }
918
919 445
        return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
920
    }
921
922
    /**
923
     * Execute query ignoring second level cache.
924
     *
925
     * @param ArrayCollection|array|null $parameters
926
     * @param integer|null               $hydrationMode
927
     *
928
     * @return mixed
929
     */
930 472
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
931
    {
932 472
        if ($hydrationMode !== null) {
933 366
            $this->setHydrationMode($hydrationMode);
934
        }
935
936 472
        if ( ! empty($parameters)) {
937
            $this->setParameters($parameters);
938
        }
939
940
        $setCacheEntry = function() {};
941
942 472
        if ($this->_hydrationCacheProfile !== null) {
943 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
944
945 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
946 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
947 2
            $result            = $cache->fetch($cacheKey);
948
949 2
            if (isset($result[$realCacheKey])) {
950 2
                return $result[$realCacheKey];
951
            }
952
953 2
            if ( ! $result) {
954 2
                $result = [];
955
            }
956
957 2
            $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
958 2
                $result[$realCacheKey] = $data;
959
960 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
961 2
            };
962
        }
963
964 472
        $stmt = $this->_doExecute();
965
966 460
        if (is_numeric($stmt)) {
967 27
            $setCacheEntry($stmt);
968
969 27
            return $stmt;
970
        }
971
972 445
        $rsm  = $this->getResultSetMapping();
973 445
        $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
974
975 441
        $setCacheEntry($data);
976
977 441
        return $data;
978
    }
979
980
    /**
981
     * Load from second level cache or executes the query and put into cache.
982
     *
983
     * @param ArrayCollection|array|null $parameters
984
     * @param integer|null               $hydrationMode
985
     *
986
     * @return mixed
987
     */
988 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
989
    {
990 29
        $rsm        = $this->getResultSetMapping();
991 29
        $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
992 29
        $queryKey   = new QueryCacheKey(
993 29
            $this->getHash(),
994 29
            $this->lifetime,
995 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
996 29
            $this->getTimestampKey()
997
        );
998
999 29
        $result     = $queryCache->get($queryKey, $rsm, $this->_hints);
1000
1001 29
        if ($result !== null) {
1002 16
            if ($this->cacheLogger) {
1003 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
1004
            }
1005
1006 16
            return $result;
1007
        }
1008
1009 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
1010 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
1011
1012 26
        if ($this->cacheLogger) {
1013 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
1014
1015 26
            if ($cached) {
1016 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
1017
            }
1018
        }
1019
1020 26
        return $result;
1021
    }
1022
1023
    /**
1024
     * @return \Doctrine\ORM\Cache\TimestampCacheKey|null
1025
     */
1026 29
    private function getTimestampKey()
1027
    {
1028 29
        $entityName = reset($this->_resultSetMapping->aliasMap);
1029
1030 29
        if (empty($entityName)) {
1031 2
            return null;
1032
        }
1033
1034 27
        $metadata = $this->_em->getClassMetadata($entityName);
1035
1036 27
        return new Cache\TimestampCacheKey($metadata->rootEntityName);
1037
    }
1038
1039
    /**
1040
     * Get the result cache id to use to store the result set cache entry.
1041
     * Will return the configured id if it exists otherwise a hash will be
1042
     * automatically generated for you.
1043
     *
1044
     * @return array ($key, $hash)
1045
     */
1046 2
    protected function getHydrationCacheId()
1047
    {
1048 2
        $parameters = [];
1049
1050 2
        foreach ($this->getParameters() as $parameter) {
1051 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1052
        }
1053
1054 2
        $sql                    = $this->getSQL();
1055 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1056 2
        $hints                  = $this->getHints();
1057 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1058
1059 2
        ksort($hints);
1060
1061 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1062
    }
1063
1064
    /**
1065
     * Set the result cache id to use to store the result set cache entry.
1066
     * If this is not explicitly set by the developer then a hash is automatically
1067
     * generated for you.
1068
     *
1069
     * @param string $id
1070
     *
1071
     * @return static This query instance.
1072
     */
1073 11
    public function setResultCacheId($id)
1074
    {
1075 11
        $this->_queryCacheProfile = $this->_queryCacheProfile
1076 11
            ? $this->_queryCacheProfile->setCacheKey($id)
1077
            : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
1078
1079 11
        return $this;
1080
    }
1081
1082
    /**
1083
     * Get the result cache id to use to store the result set cache entry if set.
1084
     *
1085
     * @deprecated
1086
     *
1087
     * @return string
1088
     */
1089
    public function getResultCacheId()
1090
    {
1091
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
1092
    }
1093
1094
    /**
1095
     * Executes the query and returns a the resulting Statement object.
1096
     *
1097
     * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
1098
     */
1099
    abstract protected function _doExecute();
1100
1101
    /**
1102
     * Cleanup Query resource when clone is called.
1103
     *
1104
     * @return void
1105
     */
1106 136
    public function __clone()
1107
    {
1108 136
        $this->parameters = new ArrayCollection();
1109
1110 136
        $this->_hints = [];
1111 136
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
1112 136
    }
1113
1114
    /**
1115
     * Generates a string of currently query to use for the cache second level cache.
1116
     *
1117
     * @return string
1118
     */
1119 29
    protected function getHash()
1120
    {
1121 29
        $query  = $this->getSQL();
1122 29
        $hints  = $this->getHints();
1123 29
        $params = array_map(function(Parameter $parameter) {
1124
            // Small optimization
1125
            // Does not invoke processParameterValue for scalar values
1126 5
            if (is_scalar($value = $parameter->getValue())) {
1127 4
                return $value;
1128
            }
1129
1130 1
            return $this->processParameterValue($value);
1131 29
        }, $this->parameters->getValues());
1132
1133
        ksort($hints);
1134
1135
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1136
    }
1137
}
1138