Passed
Pull Request — 2.6 (#7528)
by Marco
06:45
created

Query::resolveParameterValue()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7.0099

Importance

Changes 0
Metric Value
cc 7
eloc 17
nc 9
nop 1
dl 0
loc 28
ccs 16
cts 17
cp 0.9412
crap 7.0099
rs 8.8333
c 0
b 0
f 0
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\Collections\ArrayCollection;
23
use Doctrine\DBAL\LockMode;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
26
use Doctrine\ORM\Query\Parameter;
27
use Doctrine\ORM\Query\ParameterTypeInferer;
28
use Doctrine\ORM\Query\Parser;
29
use Doctrine\ORM\Query\ParserResult;
30
use Doctrine\ORM\Query\QueryException;
31
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
32
use function assert;
33
34
/**
35
 * A Query object represents a DQL query.
36
 *
37
 * @since   1.0
38
 * @author  Guilherme Blanco <[email protected]>
39
 * @author  Konsta Vesterinen <[email protected]>
40
 * @author  Roman Borschel <[email protected]>
41
 */
42
final class Query extends AbstractQuery
43
{
44
    /**
45
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
46
     */
47
    const STATE_CLEAN  = 1;
48
49
    /**
50
     * A query object is in state DIRTY when it has DQL parts that have not yet been
51
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
52
     * is called.
53
     */
54
    const STATE_DIRTY = 2;
55
56
    /* Query HINTS */
57
58
    /**
59
     * The refresh hint turns any query into a refresh query with the result that
60
     * any local changes in entities are overridden with the fetched values.
61
     *
62
     * @var string
63
     */
64
    const HINT_REFRESH = 'doctrine.refresh';
65
66
    /**
67
     * @var string
68
     */
69
    const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
70
71
    /**
72
     * @var string
73
     */
74
    const HINT_CACHE_EVICT = 'doctrine.cache.evict';
75
76
    /**
77
     * Internal hint: is set to the proxy entity that is currently triggered for loading
78
     *
79
     * @var string
80
     */
81
    const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
82
83
    /**
84
     * The forcePartialLoad query hint forces a particular query to return
85
     * partial objects.
86
     *
87
     * @var string
88
     * @todo Rename: HINT_OPTIMIZE
89
     */
90
    const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
91
92
    /**
93
     * The includeMetaColumns query hint causes meta columns like foreign keys and
94
     * discriminator columns to be selected and returned as part of the query result.
95
     *
96
     * This hint does only apply to non-object queries.
97
     *
98
     * @var string
99
     */
100
    const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
101
102
    /**
103
     * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
104
     * are iterated and executed after the DQL has been parsed into an AST.
105
     *
106
     * @var string
107
     */
108
    const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
109
110
    /**
111
     * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
112
     * and is used for generating the target SQL from any DQL AST tree.
113
     *
114
     * @var string
115
     */
116
    const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
117
118
    //const HINT_READ_ONLY = 'doctrine.readOnly';
119
120
    /**
121
     * @var string
122
     */
123
    const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
124
125
    /**
126
     * @var string
127
     */
128
    const HINT_LOCK_MODE = 'doctrine.lockMode';
129
130
    /**
131
     * The current state of this query.
132
     *
133
     * @var integer
134
     */
135
    private $_state = self::STATE_CLEAN;
136
137
    /**
138
     * A snapshot of the parameter types the query was parsed with.
139
     *
140
     * @var array
141
     */
142
    private $_parsedTypes = [];
143
144
    /**
145
     * Cached DQL query.
146
     *
147
     * @var string
148
     */
149
    private $_dql = null;
150
151
    /**
152
     * The parser result that holds DQL => SQL information.
153
     *
154
     * @var \Doctrine\ORM\Query\ParserResult
155
     */
156
    private $_parserResult;
157
158
    /**
159
     * The first result to return (the "offset").
160
     *
161
     * @var integer
162
     */
163
    private $_firstResult = null;
164
165
    /**
166
     * The maximum number of results to return (the "limit").
167
     *
168
     * @var integer|null
169
     */
170
    private $_maxResults = null;
171
172
    /**
173
     * The cache driver used for caching queries.
174
     *
175
     * @var \Doctrine\Common\Cache\Cache|null
176
     */
177
    private $_queryCache;
178
179
    /**
180
     * Whether or not expire the query cache.
181
     *
182
     * @var boolean
183
     */
184
    private $_expireQueryCache = false;
185
186
    /**
187
     * The query cache lifetime.
188
     *
189
     * @var int
190
     */
191
    private $_queryCacheTTL;
192
193
    /**
194
     * Whether to use a query cache, if available. Defaults to TRUE.
195
     *
196
     * @var boolean
197
     */
198
    private $_useQueryCache = true;
199
200
    /**
201
     * Gets the SQL query/queries that correspond to this DQL query.
202
     *
203
     * @return mixed The built sql query or an array of all sql queries.
204
     *
205
     * @override
206
     */
207 340
    public function getSQL()
208
    {
209 340
        return $this->_parse()->getSqlExecutor()->getSqlStatements();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_parse()->...r()->getSqlStatements() returns the type array which is incompatible with the return type mandated by Doctrine\ORM\AbstractQuery::getSQL() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
210
    }
211
212
    /**
213
     * Returns the corresponding AST for this DQL query.
214
     *
215
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
216
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
217
     *         \Doctrine\ORM\Query\AST\DeleteStatement
218
     */
219 2
    public function getAST()
220
    {
221 2
        $parser = new Parser($this);
222
223 2
        return $parser->getAST();
224
    }
225
226
    /**
227
     * {@inheritdoc}
228
     */
229 461
    protected function getResultSetMapping()
230
    {
231
        // parse query or load from cache
232 461
        if ($this->_resultSetMapping === null) {
233 38
            $this->_resultSetMapping = $this->_parse()->getResultSetMapping();
234
        }
235
236 458
        return $this->_resultSetMapping;
237
    }
238
239
    /**
240
     * Parses the DQL query, if necessary, and stores the parser result.
241
     *
242
     * Note: Populates $this->_parserResult as a side-effect.
243
     *
244
     * @return \Doctrine\ORM\Query\ParserResult
245
     */
246 786
    private function _parse()
247
    {
248 786
        $types = [];
249
250 786
        foreach ($this->parameters as $parameter) {
251
            /** @var Query\Parameter $parameter */
252 183
            $types[$parameter->getName()] = $parameter->getType();
253
        }
254
255
        // Return previous parser result if the query and the filter collection are both clean
256 786
        if ($this->_state === self::STATE_CLEAN && $this->_parsedTypes === $types && $this->_em->isFiltersStateClean()) {
257 39
            return $this->_parserResult;
258
        }
259
260 786
        $this->_state = self::STATE_CLEAN;
261 786
        $this->_parsedTypes = $types;
262
263
        // Check query cache.
264 786
        if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) {
265 172
            $parser = new Parser($this);
266
267 172
            $this->_parserResult = $parser->parse();
268
269 168
            return $this->_parserResult;
270
        }
271
272 614
        $hash   = $this->_getQueryCacheId();
273 614
        $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
274
275 614
        if ($cached instanceof ParserResult) {
276
            // Cache hit.
277 124
            $this->_parserResult = $cached;
278
279 124
            return $this->_parserResult;
280
        }
281
282
        // Cache miss.
283 561
        $parser = new Parser($this);
284
285 561
        $this->_parserResult = $parser->parse();
286
287 543
        $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
288
289 543
        return $this->_parserResult;
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 478
    protected function _doExecute()
296
    {
297 478
        $executor = $this->_parse()->getSqlExecutor();
298
299 471
        if ($this->_queryCacheProfile) {
300 8
            $executor->setQueryCacheProfile($this->_queryCacheProfile);
301
        } else {
302 465
            $executor->removeQueryCacheProfile();
303
        }
304
305 471
        if ($this->_resultSetMapping === null) {
306 429
            $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
307
        }
308
309
        // Prepare parameters
310 471
        $paramMappings = $this->_parserResult->getParameterMappings();
311 471
        $paramCount = count($this->parameters);
312 471
        $mappingCount = count($paramMappings);
313
314 471
        if ($paramCount > $mappingCount) {
315 2
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
316
        }
317
318 470
        if ($paramCount < $mappingCount) {
319 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
320
        }
321
322
        // evict all cache for the entity region
323 469
        if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) {
324 2
            $this->evictEntityCacheRegion();
325
        }
326
327 469
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
328
329 468
        $this->evictResultSetCache(
330 468
            $executor,
331 468
            $sqlParams,
332 468
            $types,
333 468
            $this->_em->getConnection()->getParams()
334
        );
335
336 468
        return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
337
    }
338
339 468
    private function evictResultSetCache(
340
        AbstractSqlExecutor $executor,
341
        array $sqlParams,
342
        array $types,
343
        array $connectionParams
344
    ) {
345 468
        if (null === $this->_queryCacheProfile || ! $this->getExpireResultCache()) {
346 468
            return;
347
        }
348
349 2
        $cacheDriver = $this->_queryCacheProfile->getResultCacheDriver();
350 2
        $statements  = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
351
352 2
        foreach ($statements as $statement) {
353 2
            $cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
354
355 2
            $cacheDriver->delete(reset($cacheKeys));
356
        }
357 2
    }
358
359
    /**
360
     * Evict entity cache region
361
     */
362 2
    private function evictEntityCacheRegion()
363
    {
364 2
        $AST = $this->getAST();
365
366 2
        if ($AST instanceof \Doctrine\ORM\Query\AST\SelectStatement) {
0 ignored issues
show
introduced by
$AST is always a sub-type of Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
367
            throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
368
        }
369
370 2
        $className = ($AST instanceof \Doctrine\ORM\Query\AST\DeleteStatement)
371 1
            ? $AST->deleteClause->abstractSchemaName
372 2
            : $AST->updateClause->abstractSchemaName;
373
374 2
        $this->_em->getCache()->evictEntityRegion($className);
375 2
    }
376
377
    /**
378
     * Processes query parameter mappings.
379
     *
380
     * @param array $paramMappings
381
     *
382
     * @return array
383
     *
384
     * @throws Query\QueryException
385
     */
386 469
    private function processParameterMappings($paramMappings)
387
    {
388 469
        $sqlParams = [];
389 469
        $types     = [];
390
391 469
        foreach ($this->parameters as $parameter) {
392 170
            $key = $parameter->getName();
393
394 170
            if ( ! isset($paramMappings[$key])) {
395 1
                throw QueryException::unknownParameter($key);
396
            }
397
398 169
            [$value, $type] = $this->resolveParameterValue($parameter);
399
400 169
            foreach ($paramMappings[$key] as $position) {
401 169
                $types[$position] = $type;
402
            }
403
404 169
            $sqlPositions = $paramMappings[$key];
405
406
            // optimized multi value sql positions away for now,
407
            // they are not allowed in DQL anyways.
408 169
            $value = [$value];
409 169
            $countValue = count($value);
410
411 169
            for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
412 169
                $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
413
            }
414
        }
415
416 468
        if (count($sqlParams) != count($types)) {
417
            throw QueryException::parameterTypeMismatch();
418
        }
419
420 468
        if ($sqlParams) {
421 169
            ksort($sqlParams);
422 169
            $sqlParams = array_values($sqlParams);
423
424 169
            ksort($types);
425 169
            $types = array_values($types);
426
        }
427
428 468
        return [$sqlParams, $types];
429
    }
430
431
    /** @return mixed[] tuple of (value, type) */
432 169
    private function resolveParameterValue(Parameter $parameter) : array
433
    {
434 169
        if ($parameter->typeWasSpecified()) {
435 5
            return [$parameter->getValue(), $parameter->getType()];
436
        }
437
438 165
        $key           = $parameter->getName();
439 165
        $originalValue = $parameter->getValue();
440 165
        $value         = $originalValue;
441 165
        $rsm           = $this->getResultSetMapping();
442
443 165
        assert($rsm !== null);
444
445 165
        if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) {
446
            $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
447
        }
448
449 165
        if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) {
450 3
            $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
451
        }
452
453 165
        $processedValue = $this->processParameterValue($value);
454
455
        return [
456 165
            $processedValue,
457 165
            $originalValue === $processedValue
458 153
                ? $parameter->getType()
459 165
                : ParameterTypeInferer::inferType($processedValue),
460
        ];
461
    }
462
463
    /**
464
     * Defines a cache driver to be used for caching queries.
465
     *
466
     * @param \Doctrine\Common\Cache\Cache|null $queryCache Cache driver.
467
     *
468
     * @return Query This query instance.
469
     */
470 6
    public function setQueryCacheDriver($queryCache)
471
    {
472 6
        $this->_queryCache = $queryCache;
473
474 6
        return $this;
475
    }
476
477
    /**
478
     * Defines whether the query should make use of a query cache, if available.
479
     *
480
     * @param boolean $bool
481
     *
482
     * @return Query This query instance.
483
     */
484 175
    public function useQueryCache($bool)
485
    {
486 175
        $this->_useQueryCache = $bool;
487
488 175
        return $this;
489
    }
490
491
    /**
492
     * Returns the cache driver used for query caching.
493
     *
494
     * @return \Doctrine\Common\Cache\Cache|null The cache driver used for query caching or NULL, if
495
     *                                           this Query does not use query caching.
496
     */
497 614
    public function getQueryCacheDriver()
498
    {
499 614
        if ($this->_queryCache) {
500 9
            return $this->_queryCache;
501
        }
502
503 605
        return $this->_em->getConfiguration()->getQueryCacheImpl();
504
    }
505
506
    /**
507
     * Defines how long the query cache will be active before expire.
508
     *
509
     * @param integer $timeToLive How long the cache entry is valid.
510
     *
511
     * @return Query This query instance.
512
     */
513 1
    public function setQueryCacheLifetime($timeToLive)
514
    {
515 1
        if ($timeToLive !== null) {
0 ignored issues
show
introduced by
The condition $timeToLive !== null is always true.
Loading history...
516 1
            $timeToLive = (int) $timeToLive;
517
        }
518
519 1
        $this->_queryCacheTTL = $timeToLive;
520
521 1
        return $this;
522
    }
523
524
    /**
525
     * Retrieves the lifetime of resultset cache.
526
     *
527
     * @return int
528
     */
529
    public function getQueryCacheLifetime()
530
    {
531
        return $this->_queryCacheTTL;
532
    }
533
534
    /**
535
     * Defines if the query cache is active or not.
536
     *
537
     * @param boolean $expire Whether or not to force query cache expiration.
538
     *
539
     * @return Query This query instance.
540
     */
541 7
    public function expireQueryCache($expire = true)
542
    {
543 7
        $this->_expireQueryCache = $expire;
544
545 7
        return $this;
546
    }
547
548
    /**
549
     * Retrieves if the query cache is active or not.
550
     *
551
     * @return bool
552
     */
553
    public function getExpireQueryCache()
554
    {
555
        return $this->_expireQueryCache;
556
    }
557
558
    /**
559
     * @override
560
     */
561 212
    public function free()
562
    {
563 212
        parent::free();
564
565 212
        $this->_dql = null;
566 212
        $this->_state = self::STATE_CLEAN;
567 212
    }
568
569
    /**
570
     * Sets a DQL query string.
571
     *
572
     * @param string $dqlQuery DQL Query.
573
     *
574
     * @return \Doctrine\ORM\AbstractQuery
575
     */
576 979
    public function setDQL($dqlQuery)
577
    {
578 979
        if ($dqlQuery !== null) {
0 ignored issues
show
introduced by
The condition $dqlQuery !== null is always true.
Loading history...
579 979
            $this->_dql = $dqlQuery;
580 979
            $this->_state = self::STATE_DIRTY;
581
        }
582
583 979
        return $this;
584
    }
585
586
    /**
587
     * Returns the DQL query that is represented by this query object.
588
     *
589
     * @return string DQL query.
590
     */
591 924
    public function getDQL()
592
    {
593 924
        return $this->_dql;
594
    }
595
596
    /**
597
     * Returns the state of this query object
598
     * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
599
     * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
600
     *
601
     * @see AbstractQuery::STATE_CLEAN
602
     * @see AbstractQuery::STATE_DIRTY
603
     *
604
     * @return integer The query state.
605
     */
606
    public function getState()
607
    {
608
        return $this->_state;
609
    }
610
611
    /**
612
     * Method to check if an arbitrary piece of DQL exists
613
     *
614
     * @param string $dql Arbitrary piece of DQL to check for.
615
     *
616
     * @return boolean
617
     */
618
    public function contains($dql)
619
    {
620
        return stripos($this->getDQL(), $dql) !== false;
621
    }
622
623
    /**
624
     * Sets the position of the first result to retrieve (the "offset").
625
     *
626
     * @param integer $firstResult The first result to return.
627
     *
628
     * @return Query This query object.
629
     */
630 223
    public function setFirstResult($firstResult)
631
    {
632 223
        $this->_firstResult = $firstResult;
633 223
        $this->_state       = self::STATE_DIRTY;
634
635 223
        return $this;
636
    }
637
638
    /**
639
     * Gets the position of the first result the query object was set to retrieve (the "offset").
640
     * Returns NULL if {@link setFirstResult} was not applied to this query.
641
     *
642
     * @return integer The position of the first result.
643
     */
644 671
    public function getFirstResult()
645
    {
646 671
        return $this->_firstResult;
647
    }
648
649
    /**
650
     * Sets the maximum number of results to retrieve (the "limit").
651
     *
652
     * @param integer|null $maxResults
653
     *
654
     * @return Query This query object.
655
     */
656 247
    public function setMaxResults($maxResults)
657
    {
658 247
        $this->_maxResults = $maxResults;
659 247
        $this->_state      = self::STATE_DIRTY;
660
661 247
        return $this;
662
    }
663
664
    /**
665
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
666
     * Returns NULL if {@link setMaxResults} was not applied to this query.
667
     *
668
     * @return integer|null Maximum number of results.
669
     */
670 671
    public function getMaxResults()
671
    {
672 671
        return $this->_maxResults;
673
    }
674
675
    /**
676
     * Executes the query and returns an IterableResult that can be used to incrementally
677
     * iterated over the result.
678
     *
679
     * @param ArrayCollection|array|null $parameters    The query parameters.
680
     * @param string|int                 $hydrationMode The hydration mode to use.
681
     *
682
     * @return \Doctrine\ORM\Internal\Hydration\IterableResult
683
     */
684 10
    public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT)
685
    {
686 10
        $this->setHint(self::HINT_INTERNAL_ITERATION, true);
687
688 10
        return parent::iterate($parameters, $hydrationMode);
689
    }
690
691
    /**
692
     * {@inheritdoc}
693
     */
694 468
    public function setHint($name, $value)
695
    {
696 468
        $this->_state = self::STATE_DIRTY;
697
698 468
        return parent::setHint($name, $value);
699
    }
700
701
    /**
702
     * {@inheritdoc}
703
     */
704 369
    public function setHydrationMode($hydrationMode)
705
    {
706 369
        $this->_state = self::STATE_DIRTY;
707
708 369
        return parent::setHydrationMode($hydrationMode);
709
    }
710
711
    /**
712
     * Set the lock mode for this Query.
713
     *
714
     * @see \Doctrine\DBAL\LockMode
715
     *
716
     * @param int $lockMode
717
     *
718
     * @return Query
719
     *
720
     * @throws TransactionRequiredException
721
     */
722
    public function setLockMode($lockMode)
723
    {
724
        if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) {
725
            if ( ! $this->_em->getConnection()->isTransactionActive()) {
726
                throw TransactionRequiredException::transactionRequired();
727
            }
728
        }
729
730
        $this->setHint(self::HINT_LOCK_MODE, $lockMode);
731
732
        return $this;
733
    }
734
735
    /**
736
     * Get the current lock mode for this query.
737
     *
738
     * @return int|null The current lock mode of this query or NULL if no specific lock mode is set.
739
     */
740
    public function getLockMode()
741
    {
742
        $lockMode = $this->getHint(self::HINT_LOCK_MODE);
743
744
        if (false === $lockMode) {
745
            return null;
746
        }
747
748
        return $lockMode;
749
    }
750
751
    /**
752
     * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
753
     *
754
     * @return string
755
     */
756 614
    protected function _getQueryCacheId()
757
    {
758 614
        ksort($this->_hints);
759
760 614
        $platform = $this->getEntityManager()
761 614
            ->getConnection()
762 614
            ->getDatabasePlatform()
763 614
            ->getName();
764
765 614
        return md5(
766 614
            $this->getDQL() . serialize($this->_hints) .
767 614
            '&platform=' . $platform .
768 614
            ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
769 614
            '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
770 614
            '&hydrationMode=' . $this->_hydrationMode . '&types=' . serialize($this->_parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT'
771
        );
772
    }
773
774
     /**
775
     * {@inheritdoc}
776
     */
777 28
    protected function getHash()
778
    {
779 28
        return sha1(parent::getHash(). '-'. $this->_firstResult . '-' . $this->_maxResults);
780
    }
781
782
    /**
783
     * Cleanup Query resource when clone is called.
784
     *
785
     * @return void
786
     */
787 142
    public function __clone()
788
    {
789 142
        parent::__clone();
790
791 142
        $this->_state = self::STATE_DIRTY;
792 142
    }
793
}
794