Passed
Pull Request — 2.6 (#7471)
by Andreas
10:25
created

AbstractQuery::processParameterValue()   B

Complexity

Conditions 10
Paths 17

Size

Total Lines 37
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 10.0145

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 17
nop 1
dl 0
loc 37
ccs 18
cts 19
cp 0.9474
crap 10.0145
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Persistence\Mapping\MappingException;
23
use Doctrine\Common\Util\ClassUtils;
24
use Doctrine\Common\Collections\Collection;
25
use Doctrine\Common\Collections\ArrayCollection;
26
27
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
28
use Doctrine\ORM\Query\Parameter;
29
use Doctrine\ORM\Cache\QueryCacheKey;
30
use Doctrine\DBAL\Cache\QueryCacheProfile;
31
32
/**
33
 * Base contract for ORM queries. Base class for Query and NativeQuery.
34
 *
35
 * @link    www.doctrine-project.org
36
 * @since   2.0
37
 * @author  Benjamin Eberlei <[email protected]>
38
 * @author  Guilherme Blanco <[email protected]>
39
 * @author  Jonathan Wage <[email protected]>
40
 * @author  Roman Borschel <[email protected]>
41
 * @author  Konsta Vesterinen <[email protected]>
42
 */
43
abstract class AbstractQuery
44
{
45
    /* Hydration mode constants */
46
47
    /**
48
     * Hydrates an object graph. This is the default behavior.
49
     */
50
    const HYDRATE_OBJECT = 1;
51
52
    /**
53
     * Hydrates an array graph.
54
     */
55
    const HYDRATE_ARRAY = 2;
56
57
    /**
58
     * Hydrates a flat, rectangular result set with scalar values.
59
     */
60
    const HYDRATE_SCALAR = 3;
61
62
    /**
63
     * Hydrates a single scalar value.
64
     */
65
    const HYDRATE_SINGLE_SCALAR = 4;
66
67
    /**
68
     * Very simple object hydrator (optimized for performance).
69
     */
70
    const HYDRATE_SIMPLEOBJECT = 5;
71
72
    /**
73
     * The parameter map of this query.
74
     *
75
     * @var \Doctrine\Common\Collections\ArrayCollection
76
     */
77
    protected $parameters;
78
79
    /**
80
     * The user-specified ResultSetMapping to use.
81
     *
82
     * @var \Doctrine\ORM\Query\ResultSetMapping
83
     */
84
    protected $_resultSetMapping;
85
86
    /**
87
     * The entity manager used by this query object.
88
     *
89
     * @var EntityManagerInterface
90
     */
91
    protected $_em;
92
93
    /**
94
     * The map of query hints.
95
     *
96
     * @var array
97
     */
98
    protected $_hints = [];
99
100
    /**
101
     * The hydration mode.
102
     *
103
     * @var integer
104
     */
105
    protected $_hydrationMode = self::HYDRATE_OBJECT;
106
107
    /**
108
     * @var \Doctrine\DBAL\Cache\QueryCacheProfile
109
     */
110
    protected $_queryCacheProfile;
111
112
    /**
113
     * Whether or not expire the result cache.
114
     *
115
     * @var boolean
116
     */
117
    protected $_expireResultCache = false;
118
119
    /**
120
     * @var \Doctrine\DBAL\Cache\QueryCacheProfile
121
     */
122
    protected $_hydrationCacheProfile;
123
124
    /**
125
     * Whether to use second level cache, if available.
126
     *
127
     * @var boolean
128
     */
129
    protected $cacheable = false;
130
131
    /**
132
     * @var boolean
133
     */
134
    protected $hasCache = false;
135
136
    /**
137
     * Second level cache region name.
138
     *
139
     * @var string|null
140
     */
141
    protected $cacheRegion;
142
143
    /**
144
     * Second level query cache mode.
145
     *
146
     * @var integer|null
147
     */
148
    protected $cacheMode;
149
150
    /**
151
     * @var \Doctrine\ORM\Cache\Logging\CacheLogger|null
152
     */
153
    protected $cacheLogger;
154
155
    /**
156
     * @var integer
157
     */
158
    protected $lifetime = 0;
159
160
    /**
161
     * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
162
     *
163
     * @param \Doctrine\ORM\EntityManagerInterface $em
164
     */
165 1001
    public function __construct(EntityManagerInterface $em)
166
    {
167 1001
        $this->_em          = $em;
168 1001
        $this->parameters   = new ArrayCollection();
169 1001
        $this->_hints       = $em->getConfiguration()->getDefaultQueryHints();
170 1001
        $this->hasCache     = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
171
172 1001
        if ($this->hasCache) {
173 32
            $this->cacheLogger = $em->getConfiguration()
174 32
                ->getSecondLevelCacheConfiguration()
175 32
                ->getCacheLogger();
176
        }
177 1001
    }
178
179
    /**
180
     * Enable/disable second level query (result) caching for this query.
181
     *
182
     * @param boolean $cacheable
183
     *
184
     * @return static This query instance.
185
     */
186 136
    public function setCacheable($cacheable)
187
    {
188 136
        $this->cacheable = (boolean) $cacheable;
189
190 136
        return $this;
191
    }
192
193
    /**
194
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
195
     */
196 92
    public function isCacheable()
197
    {
198 92
        return $this->cacheable;
199
    }
200
201
    /**
202
     * @param string $cacheRegion
203
     *
204
     * @return static This query instance.
205
     */
206 2
    public function setCacheRegion($cacheRegion)
207
    {
208 2
        $this->cacheRegion = (string) $cacheRegion;
209
210 2
        return $this;
211
    }
212
213
    /**
214
    * Obtain the name of the second level query cache region in which query results will be stored
215
    *
216
    * @return string|null The cache region name; NULL indicates the default region.
217
    */
218 1
    public function getCacheRegion()
219
    {
220 1
        return $this->cacheRegion;
221
    }
222
223
    /**
224
     * @return boolean TRUE if the query cache and second level cache are enabled, FALSE otherwise.
225
     */
226 29
    protected function isCacheEnabled()
227
    {
228 29
        return $this->cacheable && $this->hasCache;
229
    }
230
231
    /**
232
     * @return integer
233
     */
234 1
    public function getLifetime()
235
    {
236 1
        return $this->lifetime;
237
    }
238
239
    /**
240
     * Sets the life-time for this query into second level cache.
241
     *
242
     * @param integer $lifetime
243
     *
244
     * @return \Doctrine\ORM\AbstractQuery This query instance.
245
     */
246 2
    public function setLifetime($lifetime)
247
    {
248 2
        $this->lifetime = (integer) $lifetime;
249
250 2
        return $this;
251
    }
252
253
    /**
254
     * @return integer
255
     */
256 1
    public function getCacheMode()
257
    {
258 1
        return $this->cacheMode;
259
    }
260
261
    /**
262
     * @param integer $cacheMode
263
     *
264
     * @return \Doctrine\ORM\AbstractQuery This query instance.
265
     */
266 4
    public function setCacheMode($cacheMode)
267
    {
268 4
        $this->cacheMode = (integer) $cacheMode;
269
270 4
        return $this;
271
    }
272
273
    /**
274
     * Gets the SQL query that corresponds to this query object.
275
     * The returned SQL syntax depends on the connection driver that is used
276
     * by this query object at the time of this method call.
277
     *
278
     * @return string SQL query
279
     */
280
    abstract public function getSQL();
281
282
    /**
283
     * Retrieves the associated EntityManager of this Query instance.
284
     *
285
     * @return \Doctrine\ORM\EntityManager
286
     */
287 917
    public function getEntityManager()
288
    {
289 917
        return $this->_em;
290
    }
291
292
    /**
293
     * Frees the resources used by the query object.
294
     *
295
     * Resets Parameters, Parameter Types and Query Hints.
296
     *
297
     * @return void
298
     */
299 212
    public function free()
300
    {
301 212
        $this->parameters = new ArrayCollection();
302
303 212
        $this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
304 212
    }
305
306
    /**
307
     * Get all defined parameters.
308
     *
309
     * @return \Doctrine\Common\Collections\ArrayCollection The defined query parameters.
310
     */
311 146
    public function getParameters()
312
    {
313 146
        return $this->parameters;
314
    }
315
316
    /**
317
     * Gets a query parameter.
318
     *
319
     * @param mixed $key The key (index or name) of the bound parameter.
320
     *
321
     * @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
322
     */
323 276
    public function getParameter($key)
324
    {
325 276
        $filteredParameters = $this->parameters->filter(
326 276
            function (Query\Parameter $parameter) use ($key) : bool {
327 158
                $parameterName = $parameter->getName();
328
329 158
                return $key === $parameterName || (string) $key === (string) $parameterName;
330 276
            }
331
        );
332
333 276
        return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
334
    }
335
336
    /**
337
     * Sets a collection of query parameters.
338
     *
339
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters
340
     *
341
     * @return static This query instance.
342
     */
343 194
    public function setParameters($parameters)
344
    {
345
        // BC compatibility with 2.3-
346 194
        if (is_array($parameters)) {
347 4
            $parameterCollection = new ArrayCollection();
348
349 4
            foreach ($parameters as $key => $value) {
350 4
                $parameterCollection->add(new Parameter($key, $value));
351
            }
352
353 4
            $parameters = $parameterCollection;
354
        }
355
356 194
        $this->parameters = $parameters;
357
358 194
        return $this;
359
    }
360
361
    /**
362
     * Sets a query parameter.
363
     *
364
     * @param string|int  $key   The parameter position or name.
365
     * @param mixed       $value The parameter value.
366
     * @param string|null $type  The parameter type. If specified, the given value will be run through
367
     *                           the type conversion of this type. This is usually not needed for
368
     *                           strings and numeric types.
369
     *
370
     * @return static This query instance.
371
     */
372 187
    public function setParameter($key, $value, $type = null)
373
    {
374 187
        $existingParameter = $this->getParameter($key);
375
376 187
        if ($existingParameter !== null) {
377 5
            $existingParameter->setValue($value, $type);
378
379 5
            return $this;
380
        }
381
382 185
        $this->parameters->add(new Parameter($key, $value, $type));
383
384 185
        return $this;
385
    }
386
387
    /**
388
     * Processes an individual parameter value.
389
     *
390
     * @param mixed $value
391
     *
392
     * @return array|string
393
     *
394
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
395
     */
396 185
    public function processParameterValue($value)
397
    {
398 185
        if (is_scalar($value)) {
399 171
            return $value;
400
        }
401
402 92
        if ($value instanceof Collection) {
403 1
            $value = $value->toArray();
404
        }
405
406 92
        if (is_array($value)) {
407 74
            foreach ($value as $key => $paramValue) {
408 74
                $paramValue  = $this->processParameterValue($paramValue);
409 74
                $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
410
            }
411
412 74
            return $value;
413
        }
414
415 21
        if ($value instanceof Mapping\ClassMetadata) {
416 1
            return $value->name;
417
        }
418
419 20
        if ( ! is_object($value)) {
420
            return $value;
421
        }
422
423
        try {
424 20
            $value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
425
426 15
            if ($value === null) {
427 15
                throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
428
            }
429 6
        } catch (MappingException|ORMMappingException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
430
        }
431
432 20
        return $value;
433
    }
434
435
    /**
436
     * Sets the ResultSetMapping that should be used for hydration.
437
     *
438
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
439
     *
440
     * @return static This query instance.
441
     */
442 30
    public function setResultSetMapping(Query\ResultSetMapping $rsm)
443
    {
444 30
        $this->translateNamespaces($rsm);
445 30
        $this->_resultSetMapping = $rsm;
446
447 30
        return $this;
448
    }
449
450
    /**
451
     * Gets the ResultSetMapping used for hydration.
452
     *
453
     * @return \Doctrine\ORM\Query\ResultSetMapping
454
     */
455 22
    protected function getResultSetMapping()
456
    {
457 22
        return $this->_resultSetMapping;
458
    }
459
460
    /**
461
     * Allows to translate entity namespaces to full qualified names.
462
     *
463
     * @param Query\ResultSetMapping $rsm
464
     *
465
     * @return void
466
     */
467
    private function translateNamespaces(Query\ResultSetMapping $rsm)
468
    {
469 30
        $translate = function ($alias) {
470 18
            return $this->_em->getClassMetadata($alias)->getName();
471 30
        };
472
473 30
        $rsm->aliasMap = array_map($translate, $rsm->aliasMap);
474 30
        $rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);
475 30
    }
476
477
    /**
478
     * Set a cache profile for hydration caching.
479
     *
480
     * If no result cache driver is set in the QueryCacheProfile, the default
481
     * result cache driver is used from the configuration.
482
     *
483
     * Important: Hydration caching does NOT register entities in the
484
     * UnitOfWork when retrieved from the cache. Never use result cached
485
     * entities for requests that also flush the EntityManager. If you want
486
     * some form of caching with UnitOfWork registration you should use
487
     * {@see AbstractQuery::setResultCacheProfile()}.
488
     *
489
     * @example
490
     * $lifetime = 100;
491
     * $resultKey = "abc";
492
     * $query->setHydrationCacheProfile(new QueryCacheProfile());
493
     * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
494
     *
495
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
496
     *
497
     * @return static This query instance.
498
     */
499 3
    public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
500
    {
501 3
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
502
            $resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
503
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver can also be of type null; however, parameter $cache of Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does only seem to accept Doctrine\Common\Cache\Cache, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

503
            $profile = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
504
        }
505
506 3
        $this->_hydrationCacheProfile = $profile;
507
508 3
        return $this;
509
    }
510
511
    /**
512
     * @return \Doctrine\DBAL\Cache\QueryCacheProfile
513
     */
514 3
    public function getHydrationCacheProfile()
515
    {
516 3
        return $this->_hydrationCacheProfile;
517
    }
518
519
    /**
520
     * Set a cache profile for the result cache.
521
     *
522
     * If no result cache driver is set in the QueryCacheProfile, the default
523
     * result cache driver is used from the configuration.
524
     *
525
     * @param \Doctrine\DBAL\Cache\QueryCacheProfile $profile
526
     *
527
     * @return static This query instance.
528
     */
529 1
    public function setResultCacheProfile(QueryCacheProfile $profile = null)
530
    {
531 1
        if ($profile !== null && ! $profile->getResultCacheDriver()) {
532
            $resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
533
            $profile = $profile->setResultCacheDriver($resultCacheDriver);
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver can also be of type null; however, parameter $cache of Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does only seem to accept Doctrine\Common\Cache\Cache, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

533
            $profile = $profile->setResultCacheDriver(/** @scrutinizer ignore-type */ $resultCacheDriver);
Loading history...
534
        }
535
536 1
        $this->_queryCacheProfile = $profile;
537
538 1
        return $this;
539
    }
540
541
    /**
542
     * Defines a cache driver to be used for caching result sets and implicitly enables caching.
543
     *
544
     * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
545
     *
546
     * @return static This query instance.
547
     *
548
     * @throws ORMException
549
     */
550 8
    public function setResultCacheDriver($resultCacheDriver = null)
551
    {
552 8
        if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
0 ignored issues
show
introduced by
$resultCacheDriver is always a sub-type of Doctrine\Common\Cache\Cache.
Loading history...
553
            throw ORMException::invalidResultCacheDriver();
554
        }
555
556 8
        $this->_queryCacheProfile = $this->_queryCacheProfile
557 1
            ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
0 ignored issues
show
Bug introduced by
It seems like $resultCacheDriver can also be of type null; however, parameter $cache of Doctrine\DBAL\Cache\Quer...:setResultCacheDriver() does only seem to accept Doctrine\Common\Cache\Cache, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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