Completed
Pull Request — master (#6210)
by Christophe
11:09
created

AbstractQuery::setResultCacheId()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
dl 0
loc 8
c 0
b 0
f 0
ccs 4
cts 5
cp 0.8
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 2.032
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
use Doctrine\ORM\Query\ResultSetMapping;
32
33
/**
34
 * Base contract for ORM queries. Base class for Query and NativeQuery.
35
 *
36
 * @link    www.doctrine-project.org
37
 * @since   2.0
38
 * @author  Benjamin Eberlei <[email protected]>
39
 * @author  Guilherme Blanco <[email protected]>
40
 * @author  Jonathan Wage <[email protected]>
41
 * @author  Roman Borschel <[email protected]>
42
 * @author  Konsta Vesterinen <[email protected]>
43
 */
44
abstract class AbstractQuery
45
{
46
    /* Hydration mode constants */
47
48
    /**
49
     * Hydrates an object graph. This is the default behavior.
50
     */
51
    const HYDRATE_OBJECT = 1;
52
53
    /**
54
     * Hydrates an array graph.
55
     */
56
    const HYDRATE_ARRAY = 2;
57
58
    /**
59
     * Hydrates a flat, rectangular result set with scalar values.
60
     */
61
    const HYDRATE_SCALAR = 3;
62
63
    /**
64
     * Hydrates a single scalar value.
65
     */
66
    const HYDRATE_SINGLE_SCALAR = 4;
67
68
    /**
69
     * Very simple object hydrator (optimized for performance).
70
     */
71
    const HYDRATE_SIMPLEOBJECT = 5;
72
73
    /**
74
     * The parameter map of this query.
75
     *
76
     * @var \Doctrine\Common\Collections\ArrayCollection
77
     */
78
    protected $parameters;
79
80
    /**
81
     * The user-specified ResultSetMapping to use.
82
     *
83
     * @var \Doctrine\ORM\Query\ResultSetMapping
84
     */
85
    protected $_resultSetMapping;
86
87
    /**
88
     * The entity manager used by this query object.
89
     *
90
     * @var EntityManagerInterface
91
     */
92
    protected $_em;
93
94
    /**
95
     * The map of query hints.
96
     *
97
     * @var array
98
     */
99
    protected $_hints = [];
100
101
    /**
102
     * The hydration mode.
103
     *
104
     * @var integer
105
     */
106
    protected $_hydrationMode = self::HYDRATE_OBJECT;
107
108
    /**
109
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile
110
     */
111
    protected $_queryCacheProfile;
112
113
    /**
114
     * Whether or not expire the result cache.
115
     *
116
     * @var boolean
117
     */
118
    protected $_expireResultCache = false;
119
120
    /**
121
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile
122
     */
123
    protected $_hydrationCacheProfile;
124
125
    /**
126
     * Whether to use second level cache, if available.
127
     *
128
     * @var boolean
129
     */
130
    protected $cacheable = false;
131
132
    /**
133
     * @var boolean
134
     */
135
    protected $hasCache = false;
136
137
    /**
138
     * Second level cache region name.
139
     *
140
     * @var string|null
141
     */
142
    protected $cacheRegion;
143
144
    /**
145
     * Second level query cache mode.
146
     *
147
     * @var integer|null
148
     */
149
    protected $cacheMode;
150
151
    /**
152
     * @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
153
     */
154
    protected $cacheLogger;
155
156
    /**
157
     * @var integer
158
     */
159
    protected $lifetime = 0;
160
161
    /**
162
     * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
163
     *
164
     * @param \Doctrine\ORM\EntityManagerInterface $em
165
     */
166 949
    public function __construct(EntityManagerInterface $em)
167
    {
168 949
        $this->_em          = $em;
169 949
        $this->parameters   = new ArrayCollection();
170 949
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
171 949
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
172
173 949
        if ($this->hasCache) {
174 32
            $this->cacheLogger = $em->getConfiguration()
175 32
                ->getSecondLevelCacheConfiguration()
176 32
                ->getCacheLogger();
177
        }
178 949
    }
179
180
    /**
181
     * Enable/disable second level query (result) caching for this query.
182
     *
183
     * @param boolean $cacheable
184
     *
185
     * @return static This query instance.
186
     */
187 133
    public function setCacheable($cacheable)
188
    {
189 133
        $this->cacheable = (boolean) $cacheable;
190
191 133
        return $this;
192
    }
193
194
    /**
195
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
196
     */
197 90
    public function isCacheable()
198
    {
199 90
        return $this->cacheable;
200
    }
201
202
    /**
203
     * @param string $cacheRegion
204
     *
205
     * @return static This query instance.
206
     */
207 2
    public function setCacheRegion($cacheRegion)
208
    {
209 2
        $this->cacheRegion = (string) $cacheRegion;
210
211 2
        return $this;
212
    }
213
214
    /**
215
    * Obtain the name of the second level query cache region in which query results will be stored
216
    *
217
    * @return string|null The cache region name; NULL indicates the default region.
218
    */
219 1
    public function getCacheRegion()
220
    {
221 1
        return $this->cacheRegion;
222
    }
223
224
    /**
225
     * @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
226
     */
227 29
    protected function isCacheEnabled()
228
    {
229 29
        return $this->cacheable && $this->hasCache;
230
    }
231
232
    /**
233
     * @return integer
234
     */
235 1
    public function getLifetime()
236
    {
237 1
        return $this->lifetime;
238
    }
239
240
    /**
241
     * Sets the life-time for this query into second level cache.
242
     *
243
     * @param integer $lifetime
244
     *
245
     * @return \Doctrine\ORM\AbstractQuery This query instance.
246
     */
247 2
    public function setLifetime($lifetime)
248
    {
249 2
        $this->lifetime = (integer) $lifetime;
250
251 2
        return $this;
252
    }
253
254
    /**
255
     * @return integer
256
     */
257 1
    public function getCacheMode()
258
    {
259 1
        return $this->cacheMode;
260
    }
261
262
    /**
263
     * @param integer $cacheMode
264
     *
265
     * @return \Doctrine\ORM\AbstractQuery This query instance.
266
     */
267 4
    public function setCacheMode($cacheMode)
268
    {
269 4
        $this->cacheMode = (integer) $cacheMode;
270
271 4
        return $this;
272
    }
273
274
    /**
275
     * Gets the SQL query that corresponds to this query object.
276
     * The returned SQL syntax depends on the connection driver that is used
277
     * by this query object at the time of this method call.
278
     *
279
     * @return string SQL query
280
     */
281
    abstract public function getSQL();
282
283
    /**
284
     * Retrieves the associated EntityManager of this Query instance.
285
     *
286
     * @return \Doctrine\ORM\EntityManager
287
     */
288 871
    public function getEntityManager()
289
    {
290 871
        return $this->_em;
291
    }
292
293
    /**
294
     * Frees the resources used by the query object.
295
     *
296
     * Resets Parameters, Parameter Types and Query Hints.
297
     *
298
     * @return void
299
     */
300 211
    public function free()
301
    {
302 211
        $this->parameters = new ArrayCollection();
303
304 211
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
305 211
    }
306
307
    /**
308
     * Get all defined parameters.
309
     *
310
     * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
311
     */
312 137
    public function getParameters()
313
    {
314 137
        return $this->parameters;
315
    }
316
317
    /**
318
     * Gets a query parameter.
319
     *
320
     * @param mixed $key The key (index or name) of the bound parameter.
321
     *
322
     * @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
323
     */
324 212
    public function getParameter($key)
325
    {
326 212
        $filteredParameters = $this->parameters->filter(
327
            function ($parameter) use ($key)
328
            {
329
                // Must not be identical because of string to integer conversion
330 138
                return ($key == $parameter->getName());
331 212
            }
332
        );
333
334 212
        return count($filteredParameters) ? $filteredParameters->first() : null;
335
    }
336
337
    /**
338
     * Sets a collection of query parameters.
339
     *
340
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
341
     *
342
     * @return static This query instance.
343
     */
344 180
    public function setParameters($parameters)
345
    {
346
        // BC compatibility with 2.3-
347 180
        if (is_array($parameters)) {
348 1
            $parameterCollection = new ArrayCollection();
349
350 1
            foreach ($parameters as $key => $value) {
351 1
                $parameterCollection->add(new Parameter($key, $value));
352
            }
353
354 1
            $parameters = $parameterCollection;
355
        }
356
357 180
        $this->parameters = $parameters;
358
359 180
        return $this;
360
    }
361
362
    /**
363
     * Sets a query parameter.
364
     *
365
     * @param string|int  $key   The parameter position or name.
366
     * @param mixed       $value The parameter value.
367
     * @param string|null $type  The parameter type. If specified, the given value will be run through
368
     *                           the type conversion of this type. This is usually not needed for
369
     *                           strings and numeric types.
370
     *
371
     * @return static This query instance.
372
     */
373 175
    public function setParameter($key, $value, $type = null)
374
    {
375 175
        $filteredParameters = $this->parameters->filter(
376
            function ($parameter) use ($key)
377
            {
378
                // Must not be identical because of string to integer conversion
379 17
                return ($key == $parameter->getName());
380 175
            }
381
        );
382
383 175
        if (count($filteredParameters)) {
384 4
            $parameter = $filteredParameters->first();
385 4
            $parameter->setValue($value, $type);
386
387 4
            return $this;
388
        }
389
390 173
        $this->parameters->add(new Parameter($key, $value, $type));
391
392 173
        return $this;
393
    }
394
395
    /**
396
     * Processes an individual parameter value.
397
     *
398
     * @param mixed $value
399
     *
400
     * @return array|string
401
     *
402
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
403
     */
404 169
    public function processParameterValue($value)
405
    {
406 169
        if (is_scalar($value)) {
407 156
            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...
408
        }
409
410 85
        if ($value instanceof Collection) {
411 1
            $value = $value->toArray();
412
        }
413
414 85
        if (is_array($value)) {
415 68
            foreach ($value as $key => $paramValue) {
416 68
                $paramValue  = $this->processParameterValue($paramValue);
417 68
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
418
            }
419
420 68
            return $value;
421
        }
422
423 20
        if (is_object($value) && (
424 20
            $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))
425 20
            || ! $this->_em->getMetadataFactory()->isTransient(ClassUtils::getClass($value))
426
        )) {
427 14
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
428
429 14
            if ($value === null) {
430
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
431
            }
432
        }
433
434 20
        if ($value instanceof Mapping\ClassMetadata) {
435 1
            return $value->name;
436
        }
437
438 19
        return $value;
439
    }
440
441
    /**
442
     * Sets the ResultSetMapping that should be used for hydration.
443
     *
444
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
445
     *
446
     * @return static This query instance.
447
     */
448 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
449
    {
450 30
        $this->translateNamespaces($rsm);
451 30
        $this->_resultSetMapping = $rsm;
452
453 30
        return $this;
454
    }
455
456
    /**
457
     * Gets the ResultSetMapping used for hydration.
458
     *
459
     * @return \Doctrine\ORM\Query\ResultSetMapping
460
     */
461 22
    protected function getResultSetMapping()
462
    {
463 22
        return $this->_resultSetMapping;
464
    }
465
466
    /**
467
     * Allows to translate entity namespaces to full qualified names.
468
     *
469
     * @param Query\ResultSetMapping $rsm
470
     *
471
     * @return void
472
     */
473 30
    private function translateNamespaces(Query\ResultSetMapping $rsm)
474
    {
475
        $translate = function ($alias) {
476 18
            return $this->_em->getClassMetadata($alias)->getName();
477 30
        };
478
479 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
480 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
481 30
    }
482
483
    /**
484
     * Set a cache profile for hydration caching.
485
     *
486
     * If no result cache driver is set in the QueryCacheProfile, the default
487
     * result cache driver is used from the configuration.
488
     *
489
     * Important: Hydration caching does NOT register entities in the
490
     * UnitOfWork when retrieved from the cache. Never use result cached
491
     * entities for requests that also flush the EntityManager. If you want
492
     * some form of caching with UnitOfWork registration you should use
493
     * {@see AbstractQuery::setResultCacheProfile()}.
494
     *
495
     * @example
496
     * $lifetime = 100;
497
     * $resultKey = "abc";
498
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
499
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
500
     *
501
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
502
     *
503
     * @return static This query instance.
504
     */
505 3
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
506
    {
507 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
508
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
509
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...getHydrationCacheImpl() on line 508 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...
510
        }
511
512 3
        $this->_hydrationCacheProfile = $profile;
513
514 3
        return $this;
515
    }
516
517
    /**
518
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
519
     */
520 3
    public function getHydrationCacheProfile()
521
    {
522 3
        return $this->_hydrationCacheProfile;
523
    }
524
525
    /**
526
     * Set a cache profile for the result cache.
527
     *
528
     * If no result cache driver is set in the QueryCacheProfile, the default
529
     * result cache driver is used from the configuration.
530
     *
531
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
532
     *
533
     * @return static This query instance.
534
     */
535
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
536
    {
537
        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...
538
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
539
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...)->getResultCacheImpl() on line 538 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...
540
        }
541
542
        $this->_queryCacheProfile = $profile;
543
544
        return $this;
545
    }
546
547
    /**
548
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
549
     *
550
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
551
     *
552
     * @return static This query instance.
553
     *
554
     * @throws ORMException
555
     */
556 8
    public function setResultCacheDriver($resultCacheDriver = null)
557
    {
558 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
559
            throw ORMException::invalidResultCacheDriver();
560
        }
561
562 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
563 1
            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
564 7
            : new QueryCacheProfile(0, null, $resultCacheDriver);
565
566 8
        return $this;
567
    }
568
569
    /**
570
     * Returns the cache driver used for caching result sets.
571
     *
572
     * @deprecated
573
     *
574
     * @return \Doctrine\Common\Cache\Cache Cache driver
575
     */
576 3
    public function getResultCacheDriver()
577
    {
578 3
        if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
579 3
            return $this->_queryCacheProfile->getResultCacheDriver();
580
        }
581
582
        return $this->_em->getConfiguration()->getResultCacheImpl();
583
    }
584
585
    /**
586
     * Set whether or not to cache the results of this query and if so, for
587
     * how long and which ID to use for the cache entry.
588
     *
589
     * @param boolean $bool
590
     * @param integer $lifetime
591
     * @param string  $resultCacheId
592
     *
593
     * @return static This query instance.
594
     */
595 6
    public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
596
    {
597 6
        if ($bool) {
598 6
            $this->setResultCacheLifetime($lifetime);
599 6
            $this->setResultCacheId($resultCacheId);
600
601 6
            return $this;
602
        }
603
604 1
        $this->_queryCacheProfile = null;
605
606 1
        return $this;
607
    }
608
609
    /**
610
     * Defines how long the result cache will be active before expire.
611
     *
612
     * @param integer $lifetime How long the cache entry is valid.
613
     *
614
     * @return static This query instance.
615
     */
616 7
    public function setResultCacheLifetime($lifetime)
617
    {
618 7
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
619
620 7
        $this->_queryCacheProfile = $this->_queryCacheProfile
621 4
            ? $this->_queryCacheProfile->setLifetime($lifetime)
622 3
            : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
623
624 7
        return $this;
625
    }
626
627
    /**
628
     * Retrieves the lifetime of resultset cache.
629
     *
630
     * @deprecated
631
     *
632
     * @return integer
633
     */
634
    public function getResultCacheLifetime()
635
    {
636
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
637
    }
638
639
    /**
640
     * Defines if the result cache is active or not.
641
     *
642
     * @param boolean $expire Whether or not to force resultset cache expiration.
643
     *
644
     * @return static This query instance.
645
     */
646 2
    public function expireResultCache($expire = true)
647
    {
648 2
        $this->_expireResultCache = $expire;
649
650 2
        return $this;
651
    }
652
653
    /**
654
     * Retrieves if the resultset cache is active or not.
655
     *
656
     * @return boolean
657
     */
658
    public function getExpireResultCache()
659
    {
660
        return $this->_expireResultCache;
661
    }
662
663
    /**
664
     * @return QueryCacheProfile
665
     */
666 1
    public function getQueryCacheProfile()
667
    {
668 1
        return $this->_queryCacheProfile;
669
    }
670
671
    /**
672
     * Change the default fetch mode of an association for this query.
673
     *
674
     * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
675
     *
676
     * @param string $class
677
     * @param string $assocName
678
     * @param int    $fetchMode
679
     *
680
     * @return static This query instance.
681
     */
682 3
    public function setFetchMode($class, $assocName, $fetchMode)
683
    {
684 3
        if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
685
            $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
686
        }
687
688 3
        $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
689
690 3
        return $this;
691
    }
692
693
    /**
694
     * Defines the processing mode to be used during hydration / result set transformation.
695
     *
696
     * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
697
     *                               One of the Query::HYDRATE_* constants.
698
     *
699
     * @return static This query instance.
700
     */
701 363
    public function setHydrationMode($hydrationMode)
702
    {
703 363
        $this->_hydrationMode = $hydrationMode;
704
705 363
        return $this;
706
    }
707
708
    /**
709
     * Gets the hydration mode currently used by the query.
710
     *
711
     * @return integer
712
     */
713 628
    public function getHydrationMode()
714
    {
715 628
        return $this->_hydrationMode;
716
    }
717
718
    /**
719
     * Gets the list of results for the query.
720
     *
721
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
722
     *
723
     * @param int $hydrationMode
724
     *
725
     * @return mixed
726
     */
727 296
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
728
    {
729 296
        return $this->execute(null, $hydrationMode);
730
    }
731
732
    /**
733
     * Gets the array of results for the query.
734
     *
735
     * Alias for execute(null, HYDRATE_ARRAY).
736
     *
737
     * @return array
738
     */
739 29
    public function getArrayResult()
740
    {
741 29
        return $this->execute(null, self::HYDRATE_ARRAY);
742
    }
743
744
    /**
745
     * Gets the scalar results for the query.
746
     *
747
     * Alias for execute(null, HYDRATE_SCALAR).
748
     *
749
     * @return array
750
     */
751 87
    public function getScalarResult()
752
    {
753 87
        return $this->execute(null, self::HYDRATE_SCALAR);
754
    }
755
756
    /**
757
     * Get exactly one result or null.
758
     *
759
     * @param int $hydrationMode
760
     *
761
     * @return mixed
762
     *
763
     * @throws NonUniqueResultException
764
     */
765 13
    public function getOneOrNullResult($hydrationMode = null)
766
    {
767
        try {
768 13
            $result = $this->execute(null, $hydrationMode);
769 1
        } catch (NoResultException $e) {
770 1
            return null;
771
        }
772
773
774 12
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
775 2
            return null;
776
        }
777
778 10
        if ( ! is_array($result)) {
779 1
            return $result;
780
        }
781
782 10
        if (count($result) > 1) {
783 1
            throw new NonUniqueResultException;
784
        }
785
786 9
        return array_shift($result);
787
    }
788
789
    /**
790
     * Gets the single result of the query.
791
     *
792
     * Enforces the presence as well as the uniqueness of the result.
793
     *
794
     * If the result is not unique, a NonUniqueResultException is thrown.
795
     * If there is no result, a NoResultException is thrown.
796
     *
797
     * @param integer $hydrationMode
798
     *
799
     * @return mixed
800
     *
801
     * @throws NonUniqueResultException If the query result is not unique.
802
     * @throws NoResultException        If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
803
     */
804 89
    public function getSingleResult($hydrationMode = null)
805
    {
806 89
        $result = $this->execute(null, $hydrationMode);
807
808 83
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
809 2
            throw new NoResultException;
810
        }
811
812 82
        if ( ! is_array($result)) {
813 8
            return $result;
814
        }
815
816 75
        if (count($result) > 1) {
817 1
            throw new NonUniqueResultException;
818
        }
819
820 74
        return array_shift($result);
821
    }
822
823
    /**
824
     * Gets the single scalar result of the query.
825
     *
826
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
827
     *
828
     * @return mixed The scalar result, or NULL if the query returned no result.
829
     *
830
     * @throws NonUniqueResultException If the query result is not unique.
831
     */
832 10
    public function getSingleScalarResult()
833
    {
834 10
        return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
835
    }
836
837
    /**
838
     * Sets a query hint. If the hint name is not recognized, it is silently ignored.
839
     *
840
     * @param string $name  The name of the hint.
841
     * @param mixed  $value The value of the hint.
842
     *
843
     * @return static This query instance.
844
     */
845 462
    public function setHint($name, $value)
846
    {
847 462
        $this->_hints[$name] = $value;
848
849 462
        return $this;
850
    }
851
852
    /**
853
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
854
     *
855
     * @param string $name The name of the hint.
856
     *
857
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
858
     */
859 773
    public function getHint($name)
860
    {
861 773
        return isset($this->_hints[$name]) ? $this->_hints[$name] : false;
862
    }
863
864
    /**
865
     * Check if the query has a hint
866
     *
867
     * @param string $name The name of the hint
868
     *
869
     * @return bool False if the query does not have any hint
870
     */
871 17
    public function hasHint($name)
872
    {
873 17
        return isset($this->_hints[$name]);
874
    }
875
876
    /**
877
     * Return the key value map of query hints that are currently set.
878
     *
879
     * @return array
880
     */
881 136
    public function getHints()
882
    {
883 136
        return $this->_hints;
884
    }
885
886
    /**
887
     * Executes the query and returns an IterableResult that can be used to incrementally
888
     * iterate over the result.
889
     *
890
     * @param ArrayCollection|array|null $parameters    The query parameters.
891
     * @param integer|null               $hydrationMode The hydration mode to use.
892
     *
893
     * @return \Doctrine\ORM\Internal\Hydration\IterableResult
894
     */
895 10
    public function iterate($parameters = null, $hydrationMode = null)
896
    {
897 10
        if ($hydrationMode !== null) {
898 10
            $this->setHydrationMode($hydrationMode);
899
        }
900
901 10
        if ( ! empty($parameters)) {
902 1
            $this->setParameters($parameters);
903
        }
904
905 10
        $rsm  = $this->getResultSetMapping();
906 7
        $stmt = $this->_doExecute();
907
908 7
        return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
909
    }
910
911
    /**
912
     * Executes the query.
913
     *
914
     * @param ArrayCollection|array|null $parameters Query parameters.
915
     * @param integer|null               $hydrationMode Processing mode to be used during the hydration process.
916
     *
917
     * @return mixed
918
     */
919 456
    public function execute($parameters = null, $hydrationMode = null)
920
    {
921 456
        if ($this->cacheable && $this->isCacheEnabled()) {
922 29
            return $this->executeUsingQueryCache($parameters, $hydrationMode);
923
        }
924
925 429
        return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
926
    }
927
928
    /**
929
     * Execute query ignoring second level cache.
930
     *
931
     * @param ArrayCollection|array|null $parameters
932
     * @param integer|null               $hydrationMode
933
     *
934
     * @return mixed
935
     */
936 456
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
937
    {
938 456
        if ($hydrationMode !== null) {
939 353
            $this->setHydrationMode($hydrationMode);
940
        }
941
942 456
        if ( ! empty($parameters)) {
943
            $this->setParameters($parameters);
944
        }
945
946
        $setCacheEntry = function() {};
947
948 456
        if ($this->_hydrationCacheProfile !== null) {
949 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
950
951 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
952 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
953 2
            $result            = $cache->fetch($cacheKey);
954
955 2
            if (isset($result[$realCacheKey])) {
956 2
                return $result[$realCacheKey];
957
            }
958
959 2
            if ( ! $result) {
960 2
                $result = [];
961
            }
962
963
            $setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
964 2
                $result[$realCacheKey] = $data;
965
966 2
                $cache->save($cacheKey, $result, $queryCacheProfile->getLifetime());
967 2
            };
968
        }
969
970 456
        $stmt = $this->_doExecute();
971
972 444
        if (is_numeric($stmt)) {
973 27
            $setCacheEntry($stmt);
974
975 27
            return $stmt;
976
        }
977
978 428
        $rsm  = $this->getResultSetMapping();
979 428
        $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
980
981 424
        $setCacheEntry($data);
982
983 424
        return $data;
984
    }
985
986
    /**
987
     * Load from second level cache or executes the query and put into cache.
988
     *
989
     * @param ArrayCollection|array|null $parameters
990
     * @param integer|null               $hydrationMode
991
     *
992
     * @return mixed
993
     */
994 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
995
    {
996 29
        $rsm        = $this->getResultSetMapping();
997 29
        $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
998 29
        $queryKey   = new QueryCacheKey(
999 29
            $this->getHash(),
1000 29
            $this->lifetime,
1001 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
1002 29
            $this->getTimestampKey()
1003
        );
1004
1005 29
        $result     = $queryCache->get($queryKey, $rsm, $this->_hints);
1006
1007 29
        if ($result !== null) {
1008 16
            if ($this->cacheLogger) {
1009 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
1010
            }
1011
1012 16
            return $result;
1013
        }
1014
1015 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
1016 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
1017
1018 26
        if ($this->cacheLogger) {
1019 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
1020
1021 26
            if ($cached) {
1022 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
1023
            }
1024
        }
1025
1026 26
        return $result;
1027
    }
1028
1029
    /**
1030
     * @return \Doctrine\ORM\Cache\TimestampCacheKey|null
1031
     */
1032 29
    private function getTimestampKey()
1033
    {
1034 29
        $entityName = reset($this->_resultSetMapping->aliasMap);
1035
1036 29
        if (empty($entityName)) {
1037 2
            return null;
1038
        }
1039
1040 27
        $metadata = $this->_em->getClassMetadata($entityName);
1041
1042 27
        return new Cache\TimestampCacheKey($metadata->rootEntityName);
0 ignored issues
show
Bug introduced by
Accessing rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1043
    }
1044
1045
    /**
1046
     * Get the result cache id to use to store the result set cache entry.
1047
     * Will return the configured id if it exists otherwise a hash will be
1048
     * automatically generated for you.
1049
     *
1050
     * @return array ($key, $hash)
1051
     */
1052 2
    protected function getHydrationCacheId()
1053
    {
1054 2
        $parameters = [];
1055
1056 2
        foreach ($this->getParameters() as $parameter) {
1057 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1058
        }
1059
1060 2
        $sql                    = $this->getSQL();
1061 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1062 2
        $hints                  = $this->getHints();
1063 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1064
1065 2
        ksort($hints);
1066
1067 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1068
    }
1069
1070
    /**
1071
     * Set the result cache id to use to store the result set cache entry.
1072
     * If this is not explicitly set by the developer then a hash is automatically
1073
     * generated for you.
1074
     *
1075
     * @param string $id
1076
     *
1077
     * @return static This query instance.
1078
     */
1079 9
    public function setResultCacheId($id)
1080
    {
1081 9
        $this->_queryCacheProfile = $this->_queryCacheProfile
1082 9
            ? $this->_queryCacheProfile->setCacheKey($id)
1083
            : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
1084
1085 9
        return $this;
1086
    }
1087
1088
    /**
1089
     * Get the result cache id to use to store the result set cache entry if set.
1090
     *
1091
     * @deprecated
1092
     *
1093
     * @return string
1094
     */
1095
    public function getResultCacheId()
1096
    {
1097
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
1098
    }
1099
1100
    /**
1101
     * Executes the query and returns a the resulting Statement object.
1102
     *
1103
     * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
1104
     */
1105
    abstract protected function _doExecute();
1106
1107
    /**
1108
     * Cleanup Query resource when clone is called.
1109
     *
1110
     * @return void
1111
     */
1112 136
    public function __clone()
1113
    {
1114 136
        $this->parameters = new ArrayCollection();
1115
1116 136
        $this->_hints = [];
1117 136
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
1118 136
    }
1119
1120
    /**
1121
     * Generates a string of currently query to use for the cache second level cache.
1122
     *
1123
     * @return string
1124
     */
1125 29
    protected function getHash()
1126
    {
1127 29
        $query  = $this->getSQL();
1128 29
        $hints  = $this->getHints();
1129 29
        $params = array_map(function(Parameter $parameter) {
1130
            // Small optimization
1131
            // Does not invoke processParameterValue for scalar values
1132 5
            if (is_scalar($value = $parameter->getValue())) {
1133 4
                return $value;
1134
            }
1135
1136 1
            return $this->processParameterValue($value);
1137 29
        }, $this->parameters->getValues());
1138
1139 29
        ksort($hints);
1140
1141 29
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1142
    }
1143
}
1144