Failed Conditions
Pull Request — master (#6705)
by Michael
14:32
created

AbstractQuery::getResultCacheDriver()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3.1406

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 9.4285
cc 3
eloc 4
nc 2
nop 0
crap 3.1406
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 973
    public function __construct(EntityManagerInterface $em)
164
    {
165 973
        $this->_em          = $em;
166 973
        $this->parameters   = new ArrayCollection();
167 973
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
168 973
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
169
170 973
        if ($this->hasCache) {
171 32
            $this->cacheLogger = $em->getConfiguration()
172 32
                ->getSecondLevelCacheConfiguration()
173 32
                ->getCacheLogger();
174
        }
175 973
    }
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 891
    public function getEntityManager()
286
    {
287 891
        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 141
    public function getParameters()
310
    {
311 141
        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 269
    public function getParameter($key)
322
    {
323 269
        $filteredParameters = $this->parameters->filter(
324 269
            function ($parameter) use ($key)
325
            {
326 151
                $parameterName = $parameter->getName();
327 151
                return $key === $parameterName || (string) $key === (string) $parameterName;
328 269
            }
329
        );
330
331 269
        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 187 View Code Duplication
    public function setParameters($parameters)
342
    {
343
        // BC compatibility with 2.3-
344 187
        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 187
        $this->parameters = $parameters;
355
356 187
        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 184
    public function setParameter($key, $value, $type = null)
371
    {
372 184
        $existingParameter = $this->getParameter($key);
373
374 184
        if ($existingParameter !== null) {
375 5
            $existingParameter->setValue($value, $type);
376
377 5
            return $this;
378
        }
379
380 182
        $this->parameters->add(new Parameter($key, $value, $type));
381
382 182
        return $this;
383
    }
384
385
    /**
386
     * Processes an individual parameter value.
387
     *
388
     * @param mixed $value
389
     *
390
     * @return array|string
391
     *
392
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
393
     */
394 178
    public function processParameterValue($value)
395
    {
396 178
        if (is_scalar($value)) {
397 165
            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...
398
        }
399
400 90
        if ($value instanceof Collection) {
401 1
            $value = $value->toArray();
402
        }
403
404 90
        if (is_array($value)) {
405 73
            foreach ($value as $key => $paramValue) {
406 73
                $paramValue  = $this->processParameterValue($paramValue);
407 73
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
408
            }
409
410 73
            return $value;
411
        }
412
413 20
        if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
414 14
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
415
416 14
            if ($value === null) {
417
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
418
            }
419
        }
420
421 20
        if ($value instanceof Mapping\ClassMetadata) {
422 1
            return $value->name;
423
        }
424
425 19
        return $value;
426
    }
427
428
    /**
429
     * Sets the ResultSetMapping that should be used for hydration.
430
     *
431
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
432
     *
433
     * @return static This query instance.
434
     */
435 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
436
    {
437 30
        $this->translateNamespaces($rsm);
438 30
        $this->_resultSetMapping = $rsm;
439
440 30
        return $this;
441
    }
442
443
    /**
444
     * Gets the ResultSetMapping used for hydration.
445
     *
446
     * @return \Doctrine\ORM\Query\ResultSetMapping
447
     */
448 22
    protected function getResultSetMapping()
449
    {
450 22
        return $this->_resultSetMapping;
451
    }
452
453
    /**
454
     * Allows to translate entity namespaces to full qualified names.
455
     *
456
     * @param Query\ResultSetMapping $rsm
457
     *
458
     * @return void
459
     */
460
    private function translateNamespaces(Query\ResultSetMapping $rsm)
461
    {
462 30
        $translate = function ($alias) {
463 18
            return $this->_em->getClassMetadata($alias)->getName();
464 30
        };
465
466 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
467 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
468 30
    }
469
470
    /**
471
     * Set a cache profile for hydration caching.
472
     *
473
     * If no result cache driver is set in the QueryCacheProfile, the default
474
     * result cache driver is used from the configuration.
475
     *
476
     * Important: Hydration caching does NOT register entities in the
477
     * UnitOfWork when retrieved from the cache. Never use result cached
478
     * entities for requests that also flush the EntityManager. If you want
479
     * some form of caching with UnitOfWork registration you should use
480
     * {@see AbstractQuery::setResultCacheProfile()}.
481
     *
482
     * @example
483
     * $lifetime = 100;
484
     * $resultKey = "abc";
485
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
486
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
487
     *
488
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
489
     *
490
     * @return static This query instance.
491
     */
492 3 View Code Duplication
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
493
    {
494 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
495
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
496
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...getHydrationCacheImpl() on line 495 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...
497
        }
498
499 3
        $this->_hydrationCacheProfile = $profile;
500
501 3
        return $this;
502
    }
503
504
    /**
505
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
506
     */
507 3
    public function getHydrationCacheProfile()
508
    {
509 3
        return $this->_hydrationCacheProfile;
510
    }
511
512
    /**
513
     * Set a cache profile for the result cache.
514
     *
515
     * If no result cache driver is set in the QueryCacheProfile, the default
516
     * result cache driver is used from the configuration.
517
     *
518
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
519
     *
520
     * @return static This query instance.
521
     */
522 View Code Duplication
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
523
    {
524
        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...
525
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
526
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...)->getResultCacheImpl() on line 525 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...
527
        }
528
529
        $this->_queryCacheProfile = $profile;
530
531
        return $this;
532
    }
533
534
    /**
535
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
536
     *
537
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
538
     *
539
     * @return static This query instance.
540
     *
541
     * @throws ORMException
542
     */
543 8
    public function setResultCacheDriver($resultCacheDriver = null)
544
    {
545 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
546
            throw ORMException::invalidResultCacheDriver();
547
        }
548
549 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
550 1
            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by parameter $resultCacheDriver on line 543 can be null; however, Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

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