Completed
Pull Request — master (#6378)
by Sam
69:27 queued 63:49
created

AbstractQuery::getScalarResult()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
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
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
     * @var \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
     * @var \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 940
    public function __construct(EntityManagerInterface $em)
167
    {
168 940
        $this->_em          = $em;
169 940
        $this->parameters   = new ArrayCollection();
170 940
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
171 940
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
172
173 940
        if ($this->hasCache) {
174 32
            $this->cacheLogger = $em->getConfiguration()
175 32
                ->getSecondLevelCacheConfiguration()
176 32
                ->getCacheLogger();
177
        }
178 940
    }
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 863
    public function getEntityManager()
289
    {
290 863
        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 136
    public function getParameters()
313
    {
314 136
        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 208
    public function getParameter($key)
325
    {
326 208
        $filteredParameters = $this->parameters->filter(
327
            function ($parameter) use ($key)
328
            {
329
                // Must not be identical because of string to integer conversion
330 134
                return ($key == $parameter->getName());
331 208
            }
332
        );
333
334 208
        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 177
    public function setParameters($parameters)
345
    {
346
        // BC compatibility with 2.3-
347 177
        if (is_array($parameters)) {
348
            $parameterCollection = new ArrayCollection();
349
350
            foreach ($parameters as $key => $value) {
351
                $parameterCollection->add(new Parameter($key, $value));
352
            }
353
354
            $parameters = $parameterCollection;
355
        }
356
357 177
        $this->parameters = $parameters;
358
359 177
        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 172
    public function setParameter($key, $value, $type = null)
374
    {
375 172
        $filteredParameters = $this->parameters->filter(
376
            function ($parameter) use ($key)
377
            {
378
                // Must not be identical because of string to integer conversion
379 16
                return ($key == $parameter->getName());
380 172
            }
381
        );
382
383 172
        if (count($filteredParameters)) {
384 4
            $parameter = $filteredParameters->first();
385 4
            $parameter->setValue($value, $type);
386
387 4
            return $this;
388
        }
389
390 170
        $this->parameters->add(new Parameter($key, $value, $type));
391
392 170
        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 163
    public function processParameterValue($value)
405
    {
406 163
        if (is_scalar($value)) {
407 153
            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 81
        if ($value instanceof Collection) {
411 1
            $value = $value->toArray();
412
        }
413
414 81
        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 16
        if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
424 13
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
425
426 13
            if ($value === null) {
427
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
428
            }
429
        }
430
431 16
        if ($value instanceof Mapping\ClassMetadata) {
432 1
            return $value->name;
433
        }
434
435 15
        return $value;
436
    }
437
438
    /**
439
     * Sets the ResultSetMapping that should be used for hydration.
440
     *
441
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
442
     *
443
     * @return static This query instance.
444
     */
445 29
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
446
    {
447 29
        $this->translateNamespaces($rsm);
448 29
        $this->_resultSetMapping = $rsm;
449
450 29
        return $this;
451
    }
452
453
    /**
454
     * Gets the ResultSetMapping used for hydration.
455
     *
456
     * @return \Doctrine\ORM\Query\ResultSetMapping
457
     */
458 21
    protected function getResultSetMapping()
459
    {
460 21
        return $this->_resultSetMapping;
461
    }
462
463
    /**
464
     * Allows to translate entity namespaces to full qualified names.
465
     *
466
     * @param Query\ResultSetMapping $rsm
467
     *
468
     * @return void
469
     */
470 29
    private function translateNamespaces(Query\ResultSetMapping $rsm)
471
    {
472
        $translate = function ($alias) {
473 17
            return $this->_em->getClassMetadata($alias)->getName();
474 29
        };
475
476 29
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
477 29
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
478 29
    }
479
480
    /**
481
     * Set a cache profile for hydration caching.
482
     *
483
     * If no result cache driver is set in the QueryCacheProfile, the default
484
     * result cache driver is used from the configuration.
485
     *
486
     * Important: Hydration caching does NOT register entities in the
487
     * UnitOfWork when retrieved from the cache. Never use result cached
488
     * entities for requests that also flush the EntityManager. If you want
489
     * some form of caching with UnitOfWork registration you should use
490
     * {@see AbstractQuery::setResultCacheProfile()}.
491
     *
492
     * @example
493
     * $lifetime = 100;
494
     * $resultKey = "abc";
495
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
496
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
497
     *
498
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
499
     *
500
     * @return static This query instance.
501
     */
502 3
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
503
    {
504 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
505
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
506
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...getHydrationCacheImpl() on line 505 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...
507
        }
508
509 3
        $this->_hydrationCacheProfile = $profile;
510
511 3
        return $this;
512
    }
513
514
    /**
515
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
516
     */
517 3
    public function getHydrationCacheProfile()
518
    {
519 3
        return $this->_hydrationCacheProfile;
520
    }
521
522
    /**
523
     * Set a cache profile for the result cache.
524
     *
525
     * If no result cache driver is set in the QueryCacheProfile, the default
526
     * result cache driver is used from the configuration.
527
     *
528
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
529
     *
530
     * @return static This query instance.
531
     */
532
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
533
    {
534
        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...
535
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
536
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by $this->_em->getConfigura...)->getResultCacheImpl() on line 535 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...
537
        }
538
539
        $this->_queryCacheProfile = $profile;
540
541
        return $this;
542
    }
543
544
    /**
545
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
546
     *
547
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
548
     *
549
     * @return static This query instance.
550
     *
551
     * @throws ORMException
552
     */
553 8
    public function setResultCacheDriver($resultCacheDriver = null)
554
    {
555 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
556
            throw ORMException::invalidResultCacheDriver();
557
        }
558
559 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
560 1
            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver defined by parameter $resultCacheDriver on line 553 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...
561 7
            : new QueryCacheProfile(0, null, $resultCacheDriver);
562
563 8
        return $this;
564
    }
565
566
    /**
567
     * Returns the cache driver used for caching result sets.
568
     *
569
     * @deprecated
570
     *
571
     * @return \Doctrine\Common\Cache\Cache Cache driver
572
     */
573 3
    public function getResultCacheDriver()
574
    {
575 3
        if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
576 3
            return $this->_queryCacheProfile->getResultCacheDriver();
577
        }
578
579
        return $this->_em->getConfiguration()->getResultCacheImpl();
580
    }
581
582
    /**
583
     * Set whether or not to cache the results of this query and if so, for
584
     * how long and which ID to use for the cache entry.
585
     *
586
     * @param boolean $bool
587
     * @param integer $lifetime
588
     * @param string  $resultCacheId
589
     *
590
     * @return static This query instance.
591
     */
592 6
    public function useResultCache($bool, $lifetime = null, $resultCacheId = null)
593
    {
594 6
        if ($bool) {
595 6
            $this->setResultCacheLifetime($lifetime);
596 6
            $this->setResultCacheId($resultCacheId);
597
598 6
            return $this;
599
        }
600
601 1
        $this->_queryCacheProfile = null;
602
603 1
        return $this;
604
    }
605
606
    /**
607
     * Defines how long the result cache will be active before expire.
608
     *
609
     * @param integer $lifetime How long the cache entry is valid.
610
     *
611
     * @return static This query instance.
612
     */
613 7
    public function setResultCacheLifetime($lifetime)
614
    {
615 7
        $lifetime = ($lifetime !== null) ? (int) $lifetime : 0;
616
617 7
        $this->_queryCacheProfile = $this->_queryCacheProfile
618 4
            ? $this->_queryCacheProfile->setLifetime($lifetime)
619 3
            : new QueryCacheProfile($lifetime, null, $this->_em->getConfiguration()->getResultCacheImpl());
620
621 7
        return $this;
622
    }
623
624
    /**
625
     * Retrieves the lifetime of resultset cache.
626
     *
627
     * @deprecated
628
     *
629
     * @return integer
630
     */
631
    public function getResultCacheLifetime()
632
    {
633
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
634
    }
635
636
    /**
637
     * Defines if the result cache is active or not.
638
     *
639
     * @param boolean $expire Whether or not to force resultset cache expiration.
640
     *
641
     * @return static This query instance.
642
     */
643 2
    public function expireResultCache($expire = true)
644
    {
645 2
        $this->_expireResultCache = $expire;
646
647 2
        return $this;
648
    }
649
650
    /**
651
     * Retrieves if the resultset cache is active or not.
652
     *
653
     * @return boolean
654
     */
655
    public function getExpireResultCache()
656
    {
657
        return $this->_expireResultCache;
658
    }
659
660
    /**
661
     * @return QueryCacheProfile
662
     */
663 1
    public function getQueryCacheProfile()
664
    {
665 1
        return $this->_queryCacheProfile;
666
    }
667
668
    /**
669
     * Change the default fetch mode of an association for this query.
670
     *
671
     * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
672
     *
673
     * @param string $class
674
     * @param string $assocName
675
     * @param int    $fetchMode
676
     *
677
     * @return static This query instance.
678
     */
679 3
    public function setFetchMode($class, $assocName, $fetchMode)
680
    {
681 3
        if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
682
            $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
683
        }
684
685 3
        $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
686
687 3
        return $this;
688
    }
689
690
    /**
691
     * Defines the processing mode to be used during hydration / result set transformation.
692
     *
693
     * @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
694
     *                               One of the Query::HYDRATE_* constants.
695
     *
696
     * @return static This query instance.
697
     */
698 357
    public function setHydrationMode($hydrationMode)
699
    {
700 357
        $this->_hydrationMode = $hydrationMode;
701
702 357
        return $this;
703
    }
704
705
    /**
706
     * Gets the hydration mode currently used by the query.
707
     *
708
     * @return integer
709
     */
710 624
    public function getHydrationMode()
711
    {
712 624
        return $this->_hydrationMode;
713
    }
714
715
    /**
716
     * Gets the list of results for the query.
717
     *
718
     * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
719
     *
720
     * @param int $hydrationMode
721
     *
722
     * @return mixed
723
     */
724 291
    public function getResult($hydrationMode = self::HYDRATE_OBJECT)
725
    {
726 291
        return $this->execute(null, $hydrationMode);
727
    }
728
729
    /**
730
     * Gets the array of results for the query.
731
     *
732
     * Alias for execute(null, HYDRATE_ARRAY).
733
     *
734
     * @return array
735
     */
736 29
    public function getArrayResult()
737
    {
738 29
        return $this->execute(null, self::HYDRATE_ARRAY);
739
    }
740
741
    /**
742
     * Gets the scalar results for the query.
743
     *
744
     * Alias for execute(null, HYDRATE_SCALAR).
745
     *
746
     * @return array
747
     */
748 87
    public function getScalarResult()
749
    {
750 87
        return $this->execute(null, self::HYDRATE_SCALAR);
751
    }
752
753
    /**
754
     * Get exactly one result or null.
755
     *
756
     * @param int $hydrationMode
757
     *
758
     * @return mixed
759
     *
760
     * @throws NonUniqueResultException
761
     */
762 14
    public function getOneOrNullResult($hydrationMode = null)
763
    {
764
        try {
765 14
            $result = $this->execute(null, $hydrationMode);
766 1
        } catch (NoResultException $e) {
767 1
            return null;
768
        }
769
770
771 13
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
772 2
            return null;
773
        }
774
775 11
        if ( ! is_array($result)) {
776 1
            return $result;
777
        }
778
779 11
        if (count($result) > 1) {
780 1
            throw new NonUniqueResultException;
781
        }
782
783 10
        return array_shift($result);
784
    }
785
786
    /**
787
     * Gets the single result of the query.
788
     *
789
     * Enforces the presence as well as the uniqueness of the result.
790
     *
791
     * If the result is not unique, a NonUniqueResultException is thrown.
792
     * If there is no result, a NoResultException is thrown.
793
     *
794
     * @param integer $hydrationMode
795
     *
796
     * @return mixed
797
     *
798
     * @throws NonUniqueResultException If the query result is not unique.
799
     * @throws NoResultException        If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
800
     */
801 87
    public function getSingleResult($hydrationMode = null)
802
    {
803 87
        $result = $this->execute(null, $hydrationMode);
804
805 81
        if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
806 2
            throw new NoResultException;
807
        }
808
809 80
        if ( ! is_array($result)) {
810 8
            return $result;
811
        }
812
813 73
        if (count($result) > 1) {
814 1
            throw new NonUniqueResultException;
815
        }
816
817 72
        return array_shift($result);
818
    }
819
820
    /**
821
     * Gets the single scalar result of the query.
822
     *
823
     * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
824
     *
825
     * @return mixed The scalar result, or NULL if the query returned no result.
826
     *
827
     * @throws NonUniqueResultException If the query result is not unique.
828
     */
829 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 461
    public function setHint($name, $value)
843
    {
844 461
        $this->_hints[$name] = $value;
845
846 461
        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 767
    public function getHint($name)
857
    {
858 767
        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 136
    public function getHints()
879
    {
880 136
        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 9
    public function iterate($parameters = null, $hydrationMode = null)
893
    {
894 9
        if ($hydrationMode !== null) {
895 9
            $this->setHydrationMode($hydrationMode);
896
        }
897
898 9
        if ( ! empty($parameters)) {
899 1
            $this->setParameters($parameters);
900
        }
901
902 9
        $rsm  = $this->getResultSetMapping();
903 6
        $stmt = $this->_doExecute();
904
905 6
        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 448
    public function execute($parameters = null, $hydrationMode = null)
917
    {
918 448
        if ($this->cacheable && $this->isCacheEnabled()) {
919 29
            return $this->executeUsingQueryCache($parameters, $hydrationMode);
920
        }
921
922 421
        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 448
    private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
934
    {
935 448
        if ($hydrationMode !== null) {
936 348
            $this->setHydrationMode($hydrationMode);
937
        }
938
939 448
        if ( ! empty($parameters)) {
940
            $this->setParameters($parameters);
941
        }
942
943
        $setCacheEntry = function() {};
944
945 448
        if ($this->_hydrationCacheProfile !== null) {
946 2
            list($cacheKey, $realCacheKey) = $this->getHydrationCacheId();
947
948 2
            $queryCacheProfile = $this->getHydrationCacheProfile();
949 2
            $cache             = $queryCacheProfile->getResultCacheDriver();
950 2
            $result            = $cache->fetch($cacheKey);
951
952 2
            if (isset($result[$realCacheKey])) {
953 2
                return $result[$realCacheKey];
954
            }
955
956 2
            if ( ! $result) {
957 2
                $result = [];
958
            }
959
960
            $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 448
        $stmt = $this->_doExecute();
968
969 436
        if (is_numeric($stmt)) {
970 25
            $setCacheEntry($stmt);
971
972 25
            return $stmt;
973
        }
974
975 422
        $rsm  = $this->getResultSetMapping();
976 422
        $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
977
978 418
        $setCacheEntry($data);
979
980 418
        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 29
    private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
992
    {
993 29
        $rsm        = $this->getResultSetMapping();
994 29
        $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
995 29
        $queryKey   = new QueryCacheKey(
996 29
            $this->getHash(),
997 29
            $this->lifetime,
998 29
            $this->cacheMode ?: Cache::MODE_NORMAL,
999 29
            $this->getTimestampKey()
1000
        );
1001
1002 29
        $result     = $queryCache->get($queryKey, $rsm, $this->_hints);
1003
1004 29
        if ($result !== null) {
1005 16
            if ($this->cacheLogger) {
1006 16
                $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
1007
            }
1008
1009 16
            return $result;
1010
        }
1011
1012 29
        $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
1013 29
        $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
1014
1015 26
        if ($this->cacheLogger) {
1016 26
            $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
1017
1018 26
            if ($cached) {
1019 26
                $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
1020
            }
1021
        }
1022
1023 26
        return $result;
1024
    }
1025
1026
    /**
1027
     * @return \Doctrine\ORM\Cache\TimestampCacheKey|null
1028
     */
1029 29
    private function getTimestampKey()
1030
    {
1031 29
        $entityName = reset($this->_resultSetMapping->aliasMap);
1032
1033 29
        if (empty($entityName)) {
1034 2
            return null;
1035
        }
1036
1037 27
        $metadata = $this->_em->getClassMetadata($entityName);
1038
1039 27
        return new Cache\TimestampCacheKey($metadata->rootEntityName);
0 ignored issues
show
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...
1040
    }
1041
1042
    /**
1043
     * Get the result cache id to use to store the result set cache entry.
1044
     * Will return the configured id if it exists otherwise a hash will be
1045
     * automatically generated for you.
1046
     *
1047
     * @return array ($key, $hash)
1048
     */
1049 2
    protected function getHydrationCacheId()
1050
    {
1051 2
        $parameters = [];
1052
1053 2
        foreach ($this->getParameters() as $parameter) {
1054 1
            $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
1055
        }
1056
1057 2
        $sql                    = $this->getSQL();
1058 2
        $queryCacheProfile      = $this->getHydrationCacheProfile();
1059 2
        $hints                  = $this->getHints();
1060 2
        $hints['hydrationMode'] = $this->getHydrationMode();
1061
1062 2
        ksort($hints);
1063
1064 2
        return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
1065
    }
1066
1067
    /**
1068
     * Set the result cache id to use to store the result set cache entry.
1069
     * If this is not explicitly set by the developer then a hash is automatically
1070
     * generated for you.
1071
     *
1072
     * @param string $id
1073
     *
1074
     * @return static This query instance.
1075
     */
1076 9
    public function setResultCacheId($id)
1077
    {
1078 9
        $this->_queryCacheProfile = $this->_queryCacheProfile
1079 9
            ? $this->_queryCacheProfile->setCacheKey($id)
1080
            : new QueryCacheProfile(0, $id, $this->_em->getConfiguration()->getResultCacheImpl());
1081
1082 9
        return $this;
1083
    }
1084
1085
    /**
1086
     * Get the result cache id to use to store the result set cache entry if set.
1087
     *
1088
     * @deprecated
1089
     *
1090
     * @return string
1091
     */
1092
    public function getResultCacheId()
1093
    {
1094
        return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;
1095
    }
1096
1097
    /**
1098
     * Executes the query and returns a the resulting Statement object.
1099
     *
1100
     * @return \Doctrine\DBAL\Driver\Statement The executed database statement that holds the results.
1101
     */
1102
    abstract protected function _doExecute();
1103
1104
    /**
1105
     * Cleanup Query resource when clone is called.
1106
     *
1107
     * @return void
1108
     */
1109 136
    public function __clone()
1110
    {
1111 136
        $this->parameters = new ArrayCollection();
1112
1113 136
        $this->_hints = [];
1114 136
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
1115 136
    }
1116
1117
    /**
1118
     * Generates a string of currently query to use for the cache second level cache.
1119
     *
1120
     * @return string
1121
     */
1122 29
    protected function getHash()
1123
    {
1124 29
        $query  = $this->getSQL();
1125 29
        $hints  = $this->getHints();
1126 29
        $params = array_map(function(Parameter $parameter) {
1127
            // Small optimization
1128
            // Does not invoke processParameterValue for scalar values
1129 5
            if (is_scalar($value = $parameter->getValue())) {
1130 4
                return $value;
1131
            }
1132
1133 1
            return $this->processParameterValue($value);
1134 29
        }, $this->parameters->getValues());
1135
1136 29
        ksort($hints);
1137
1138 29
        return sha1($query . '-' . serialize($params) . '-' . serialize($hints));
1139
    }
1140
}
1141