Completed
Pull Request — master (#5883)
by Sebastian
19:21
created

AbstractQuery::expireResultCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 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\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
use Doctrine\ORM\Cache;
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 = array();
99
100
    /**
101
     * The hydration mode.
102
     *
103
     * @var integer
104
     */
105
    protected $_hydrationMode = self::HYDRATE_OBJECT;
106
107
    /**
108
     * @param \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
     * @param \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 942
    public function __construct(EntityManagerInterface $em)
166
    {
167 942
        $this->_em          = $em;
168 942
        $this->parameters   = new ArrayCollection();
169 942
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
170 942
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
171
172 942
        if ($this->hasCache) {
173 28
            $this->cacheLogger = $em->getConfiguration()
174 28
                ->getSecondLevelCacheConfiguration()
175 28
                ->getCacheLogger();
176
        }
177 942
    }
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 129
    public function setCacheable($cacheable)
187
    {
188 129
        $this->cacheable = (boolean) $cacheable;
189
190 129
        return $this;
191
    }
192
193
    /**
194
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
195
     */
196 90
    public function isCacheable()
197
    {
198 90
        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 25
    protected function isCacheEnabled()
227
    {
228 25
        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 864
    public function getEntityManager()
288
    {
289 864
        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 211
    public function free()
300
    {
301 211
        $this->parameters = new ArrayCollection();
302
303 211
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
304 211
    }
305
306
    /**
307
     * Get all defined parameters.
308
     *
309
     * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
310
     */
311 137
    public function getParameters()
312
    {
313 137
        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 210
    public function getParameter($key)
324
    {
325 210
        $filteredParameters = $this->parameters->filter(
326
            function ($parameter) use ($key)
327
            {
328
                // Must not be identical because of string to integer conversion
329 136
                return ($key == $parameter->getName());
330 210
            }
331
        );
332
333 210
        return count($filteredParameters) ? $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 178
    public function setParameters($parameters)
344
    {
345
        // BC compatibility with 2.3-
346 178
        if (is_array($parameters)) {
347 1
            $parameterCollection = new ArrayCollection();
348
349 1
            foreach ($parameters as $key => $value) {
350 1
                $parameterCollection->add(new Parameter($key, $value));
351
            }
352
353 1
            $parameters = $parameterCollection;
354
        }
355
356 178
        $this->parameters = $parameters;
357
358 178
        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 173
    public function setParameter($key, $value, $type = null)
373
    {
374 173
        $filteredParameters = $this->parameters->filter(
375
            function ($parameter) use ($key)
376
            {
377
                // Must not be identical because of string to integer conversion
378 16
                return ($key == $parameter->getName());
379 173
            }
380
        );
381
382 173
        if (count($filteredParameters)) {
383 3
            $parameter = $filteredParameters->first();
384 3
            $parameter->setValue($value, $type);
385
386 3
            return $this;
387
        }
388
389 171
        $this->parameters->add(new Parameter($key, $value, $type));
390
391 171
        return $this;
392
    }
393
394
    /**
395
     * Processes an individual parameter value.
396
     *
397
     * @param mixed $value
398
     *
399
     * @return array|string
400
     *
401
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
402
     */
403 167
    public function processParameterValue($value)
404
    {
405 167
        if (is_scalar($value)) {
406 154
            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...
407
        }
408
409 85
        if ($value instanceof Collection) {
410 1
            $value = $value->toArray();
411
        }
412
413 85
        if (is_array($value)) {
414 68
            foreach ($value as $key => $paramValue) {
415 68
                $paramValue  = $this->processParameterValue($paramValue);
416 68
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
417
            }
418
419 68
            return $value;
420
        }
421
422 20
        if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
423 14
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
424
425 14
            if ($value === null) {
426
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
427
            }
428
        }
429
430 20
        if ($value instanceof Mapping\ClassMetadata) {
431 1
            return $value->name;
432
        }
433
434 19
        return $value;
435
    }
436
437
    /**
438
     * Sets the ResultSetMapping that should be used for hydration.
439
     *
440
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
441
     *
442
     * @return static This query instance.
443
     */
444 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
445
    {
446 30
        $this->translateNamespaces($rsm);
447 30
        $this->_resultSetMapping = $rsm;
448
449 30
        return $this;
450
    }
451
452
    /**
453
     * Gets the ResultSetMapping used for hydration.
454
     *
455
     * @return \Doctrine\ORM\Query\ResultSetMapping
456
     */
457 22
    protected function getResultSetMapping()
458
    {
459 22
        return $this->_resultSetMapping;
460
    }
461
462
    /**
463
     * Allows to translate entity namespaces to full qualified names.
464
     *
465
     * @param Query\ResultSetMapping $rsm
466
     *
467
     * @return void
468
     */
469 30
    private function translateNamespaces(Query\ResultSetMapping $rsm)
470
    {
471
        $translate = function ($alias) {
472 18
            return $this->_em->getClassMetadata($alias)->getName();
473 30
        };
474
475 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
476 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
477 30
    }
478
479
    /**
480
     * Set a cache profile for hydration caching.
481
     *
482
     * If no result cache driver is set in the QueryCacheProfile, the default
483
     * result cache driver is used from the configuration.
484
     *
485
     * Important: Hydration caching does NOT register entities in the
486
     * UnitOfWork when retrieved from the cache. Never use result cached
487
     * entities for requests that also flush the EntityManager. If you want
488
     * some form of caching with UnitOfWork registration you should use
489
     * {@see AbstractQuery::setResultCacheProfile()}.
490
     *
491
     * @example
492
     * $lifetime = 100;
493
     * $resultKey = "abc";
494
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
495
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
496
     *
497
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
498
     *
499
     * @return static This query instance.
500
     */
501 3
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
502
    {
503 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
504
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
505
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...getHydrationCacheImpl() on line 504 can be null; however, Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
506
        }
507
508 3
        $this->_hydrationCacheProfile = $profile;
509
510 3
        return $this;
511
    }
512
513
    /**
514
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
515
     */
516 3
    public function getHydrationCacheProfile()
517
    {
518 3
        return $this->_hydrationCacheProfile;
519
    }
520
521
    /**
522
     * Set a cache profile for the result cache.
523
     *
524
     * If no result cache driver is set in the QueryCacheProfile, the default
525
     * result cache driver is used from the configuration.
526
     *
527
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
528
     *
529
     * @return static This query instance.
530
     */
531
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
532
    {
533
        if ( ! $profile->getResultCacheDriver()) {
0 ignored issues
show
Bug introduced by
It seems like $profile is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
534
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
535
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...)->getResultCacheImpl() on line 534 can be null; however, Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
536
        }
537
538
        $this->_queryCacheProfile = $profile;
539
540
        return $this;
541
    }
542
543
    /**
544
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
545
     *
546
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
547
     *
548
     * @return static This query instance.
549
     *
550
     * @throws ORMException
551
     */
552 8
    public function setResultCacheDriver($resultCacheDriver = null)
553
    {
554 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
555
            throw ORMException::invalidResultCacheDriver();
556
        }
557
558 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
559 1
            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
560 7
            : new QueryCacheProfile(0, null, $resultCacheDriver);
561
562 8
        return $this;
563
    }
564
565
    /**
566
     * Returns the cache driver used for caching result sets.
567
     *
568
     * @deprecated
569
     *
570
     * @return \Doctrine\Common\Cache\Cache Cache driver
571
     */
572 3
    public function getResultCacheDriver()
573
    {
574 3
        if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
575 3
            return $this->_queryCacheProfile->getResultCacheDriver();
576
        }
577
578
        return $this->_em->getConfiguration()->getResultCacheImpl();
579
    }
580
581
    /**
582
     * Set whether or not to cache the results of this query and if so, for
583
     * how long and which ID to use for the cache entry.
584
     *
585
     * @param boolean $bool
586
     * @param integer $lifetime
587
     * @param string  $resultCacheId
588
     *
589
     * @return static This query instance.
590
     */
591 6
    public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
592
    {
593 6
        if ($bool) {
594 6
            $this->setResultCacheLifetime($lifetime);
595 6
            $this->setResultCacheId($resultCacheId);
596
597 6
            return $this;
598
        }
599
600 1
        $this->_queryCacheProfile = null;
601
602 1
        return $this;
603
    }
604
605
    /**
606
     * Defines how long the result cache will be active before expire.
607
     *
608
     * @param integer $lifetime How long the cache entry is valid.
609
     *
610
     * @return static This query instance.
611
     */
612 7
    public function setResultCacheLifetime($lifetime)
613
    {
614 7
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
615
616 7
        $this->_queryCacheProfile = $this->_queryCacheProfile
617 4
            ? $this->_queryCacheProfile->setLifetime($lifetime)
618 3
            : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
619
620 7
        return $this;
621
    }
622
623
    /**
624
     * Retrieves the lifetime of resultset cache.
625
     *
626
     * @deprecated
627
     *
628
     * @return integer
629
     */
630
    public function getResultCacheLifetime()
631
    {
632
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
633
    }
634
635
    /**
636
     * Defines if the result cache is active or not.
637
     *
638
     * @param boolean $expire Whether or not to force resultset cache expiration.
639
     *
640
     * @return static This query instance.
641
     */
642 2
    public function expireResultCache($expire = true)
643
    {
644 2
        $this->_expireResultCache = $expire;
645
646 2
        return $this;
647
    }
648
649
    /**
650
     * Retrieves if the resultset cache is active or not.
651
     *
652
     * @return boolean
653
     */
654
    public function getExpireResultCache()
655
    {
656
        return $this->_expireResultCache;
657
    }
658
659
    /**
660
     * @return QueryCacheProfile
661
     */
662 1
    public function getQueryCacheProfile()
663
    {
664 1
        return $this->_queryCacheProfile;
665
    }
666
667
    /**
668
     * Change the default fetch mode of an association for this query.
669
     *
670
     * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
671
     *
672
     * @param string $class
673
     * @param string $assocName
674
     * @param int    $fetchMode
675
     *
676
     * @return static This query instance.
677
     */
678 3
    public function setFetchMode($class, $assocName, $fetchMode)
679
    {
680 3
        if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
681
            $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
682
        }
683
684 3
        $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
685
686 3
        return $this;
687
    }
688
689
    /**
690
     * Defines the processing mode to be used during hydration / result set transformation.
691
     *
692
     * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
693
     *                               One of the Query::HYDRATE_* constants.
694
     *
695
     * @return static This query instance.
696
     */
697 358
    public function setHydrationMode($hydrationMode)
698
    {
699 358
        $this->_hydrationMode = $hydrationMode;
700
701 358
        return $this;
702
    }
703
704
    /**
705
     * Gets the hydration mode currently used by the query.
706
     *
707
     * @return integer
708
     */
709 621
    public function getHydrationMode()
710
    {
711 621
        return $this->_hydrationMode;
712
    }
713
714
    /**
715
     * Gets the list of results for the query.
716
     *
717
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
718
     *
719
     * @param int $hydrationMode
720
     *
721
     * @return array
722
     */
723 292
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
724
    {
725 292
        return $this->execute(null, $hydrationMode);
726
    }
727
728
    /**
729
     * Gets the array of results for the query.
730
     *
731
     * Alias for execute(null, HYDRATE_ARRAY).
732
     *
733
     * @return array
734
     */
735 29
    public function getArrayResult()
736
    {
737 29
        return $this->execute(null, self::HYDRATE_ARRAY);
738
    }
739
740
    /**
741
     * Gets the scalar results for the query.
742
     *
743
     * Alias for execute(null, HYDRATE_SCALAR).
744
     *
745
     * @return array
746
     */
747 87
    public function getScalarResult()
748
    {
749 87
        return $this->execute(null, self::HYDRATE_SCALAR);
750
    }
751
752
    /**
753
     * Get exactly one result or null.
754
     *
755
     * @param int $hydrationMode
756
     *
757
     * @return mixed
758
     *
759
     * @throws NonUniqueResultException
760
     */
761 12
    public function getOneOrNullResult($hydrationMode = null)
762
    {
763
        try {
764 12
            $result = $this->execute(null, $hydrationMode);
765 1
        } catch (NoResultException $e) {
766 1
            return null;
767
        }
768
769
770 11
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
771 2
            return null;
772
        }
773
774 9
        if ( ! is_array($result)) {
775 1
            return $result;
776
        }
777
778 9
        if (count($result) > 1) {
779 1
            throw new NonUniqueResultException;
780
        }
781
782 8
        return array_shift($result);
783
    }
784
785
    /**
786
     * Gets the single result of the query.
787
     *
788
     * Enforces the presence as well as the uniqueness of the result.
789
     *
790
     * If the result is not unique, a NonUniqueResultException is thrown.
791
     * If there is no result, a NoResultException is thrown.
792
     *
793
     * @param integer $hydrationMode
794
     *
795
     * @return mixed
796
     *
797
     * @throws NonUniqueResultException If the query result is not unique.
798
     * @throws NoResultException        If the query returned no result.
799
     */
800 87
    public function getSingleResult($hydrationMode = null)
801
    {
802 87
        $result = $this->execute(null, $hydrationMode);
803
804 81
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
805 2
            throw new NoResultException;
806
        }
807
808 80
        if ( ! is_array($result)) {
809 8
            return $result;
810
        }
811
812 73
        if (count($result) > 1) {
813 1
            throw new NonUniqueResultException;
814
        }
815
816 72
        return array_shift($result);
817
    }
818
819
    /**
820
     * Gets the single scalar result of the query.
821
     *
822
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
823
     *
824
     * @return mixed
825
     *
826
     * @throws NonUniqueResultException If the query result is not unique.
827
     * @throws NoResultException        If the query returned no result.
828
     */
829 10
    public function getSingleScalarResult()
830
    {
831 10
        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 462
    public function setHint($name, $value)
843
    {
844 462
        $this->_hints[$name] = $value;
845
846 462
        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 766
    public function getHint($name)
857
    {
858 766
        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 17
    public function hasHint($name)
869
    {
870 17
        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 132
    public function getHints()
879
    {
880 132
        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 integer|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 integer|null               $hydrationMode Processing mode to be used during the hydration process.
913
     *
914
     * @return mixed
915
     */
916 449
    public function execute($parameters = null, $hydrationMode = null)
917
    {
918 449
        if ($this->cacheable && $this->isCacheEnabled()) {
919 25
            return $this->executeUsingQueryCache($parameters, $hydrationMode);
920
        }
921
922 426
        return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
923
    }
924
925
    /**
926
     * Execute query ignoring second level cache.
927
     *
928
     * @param ArrayCollection|array|null $parameters
929
     * @param integer|null               $hydrationMode
930
     *
931
     * @return mixed
932
     */
933 449
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
934
    {
935 449
        if ($hydrationMode !== null) {
936 348
            $this->setHydrationMode($hydrationMode);
937
        }
938
939 449
        if ( ! empty($parameters)) {
940
            $this->setParameters($parameters);
941
        }
942
943
        $setCacheEntry = function() {};
944
945 449
        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 = array();
958
            }
959
960
            $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 449
        $stmt = $this->_doExecute();
968
969 437
        if (is_numeric($stmt)) {
970 27
            $setCacheEntry($stmt);
971
972 27
            return $stmt;
973
        }
974
975 421
        $rsm  = $this->getResultSetMapping();
976 421
        $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
977
978 417
        $setCacheEntry($data);
979
980 417
        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 integer|null               $hydrationMode
988
     *
989
     * @return mixed
990
     */
991 25
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
992
    {
993 25
        $rsm        = $this->getResultSetMapping();
994 25
        $querykey   = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
995 25
        $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
996 25
        $result     = $queryCache->get($querykey, $rsm, $this->_hints);
997
998 25
        if ($result !== null) {
999 15
            if ($this->cacheLogger) {
1000 15
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
1001
            }
1002
1003 15
            return $result;
1004
        }
1005
1006 25
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
1007 25
        $cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
1008
1009 22
        if ($this->cacheLogger) {
1010 22
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
1011
1012 22
            if ($cached) {
1013 22
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
1014
            }
1015
        }
1016
1017 22
        return $result;
1018
    }
1019
1020
    /**
1021
     * Get the result cache id to use to store the result set cache entry.
1022
     * Will return the configured id if it exists otherwise a hash will be
1023
     * automatically generated for you.
1024
     *
1025
     * @return array ($key, $hash)
1026
     */
1027 2
    protected function getHydrationCacheId()
1028
    {
1029 2
        $parameters = array();
1030
1031 2
        foreach ($this->getParameters() as $parameter) {
1032 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1033
        }
1034
1035 2
        $sql                    = $this->getSQL();
1036 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1037 2
        $hints                  = $this->getHints();
1038 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1039
1040 2
        ksort($hints);
1041
1042 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1043
    }
1044
1045
    /**
1046
     * Set the result cache id to use to store the result set cache entry.
1047
     * If this is not explicitly set by the developer then a hash is automatically
1048
     * generated for you.
1049
     *
1050
     * @param string $id
1051
     *
1052
     * @return static This query instance.
1053
     */
1054 9
    public function setResultCacheId($id)
1055
    {
1056 9
        $this->_queryCacheProfile = $this->_queryCacheProfile
1057 9
            ? $this->_queryCacheProfile->setCacheKey($id)
1058
            : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
1059
1060 9
        return $this;
1061
    }
1062
1063
    /**
1064
     * Get the result cache id to use to store the result set cache entry if set.
1065
     *
1066
     * @deprecated
1067
     *
1068
     * @return string
1069
     */
1070
    public function getResultCacheId()
1071
    {
1072
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
1073
    }
1074
1075
    /**
1076
     * Executes the query and returns a the resulting Statement object.
1077
     *
1078
     * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
1079
     */
1080
    abstract protected function _doExecute();
1081
1082
    /**
1083
     * Cleanup Query resource when clone is called.
1084
     *
1085
     * @return void
1086
     */
1087 136
    public function __clone()
1088
    {
1089 136
        $this->parameters = new ArrayCollection();
1090
1091 136
        $this->_hints = array();
1092 136
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
1093 136
    }
1094
1095
    /**
1096
     * Generates a string of currently query to use for the cache second level cache.
1097
     *
1098
     * @return string
1099
     */
1100 25
    protected function getHash()
1101
    {
1102 25
        $query  = $this->getSQL();
1103 25
        $hints  = $this->getHints();
1104 25
        $params = array_map(function(Parameter $parameter) {
1105
            // Small optimization
1106
            // Does not invoke processParameterValue for scalar values
1107 5
            if (is_scalar($value = $parameter->getValue())) {
1108 4
                return $value;
1109
            }
1110
1111 1
            return $this->processParameterValue($value);
1112 25
        }, $this->parameters->getValues());
1113
1114 25
        ksort($hints);
1115
1116 25
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1117
    }
1118
}
1119