Cancelled
Pull Request — master (#8013)
by Roman
09:04
created

Query::parse()   B

Complexity

Conditions 10
Paths 13

Size

Total Lines 48
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 10.0071

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 23
c 2
b 0
f 0
nc 13
nop 0
dl 0
loc 48
ccs 23
cts 24
cp 0.9583
crap 10.0071
rs 7.6666

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
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Cache\Cache;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Doctrine\ORM\Cache. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\DBAL\LockMode;
10
use Doctrine\ORM\Internal\Hydration\IterableResult;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use Doctrine\ORM\Query\AST\DeleteStatement;
13
use Doctrine\ORM\Query\AST\SelectStatement;
14
use Doctrine\ORM\Query\AST\UpdateStatement;
15
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
16
use Doctrine\ORM\Query\Parameter;
17
use Doctrine\ORM\Query\ParameterTypeInferer;
18
use Doctrine\ORM\Query\Parser;
19
use Doctrine\ORM\Query\ParserResult;
20
use Doctrine\ORM\Query\QueryException;
21
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
22
use function array_keys;
23
use function array_values;
24
use function assert;
25
use function count;
26
use function in_array;
27
use function ksort;
28
use function md5;
29
use function reset;
30
use function serialize;
31
use function sha1;
32
use function stripos;
33
34
/**
35
 * A Query object represents a DQL query.
36
 */
37
final class Query extends AbstractQuery
38
{
39
    /**
40
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
41
     */
42
    public const STATE_CLEAN = 1;
43
44
    /**
45
     * A query object is in state DIRTY when it has DQL parts that have not yet been
46
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
47
     * is called.
48
     */
49
    public const STATE_DIRTY = 2;
50
51
    /* Query HINTS */
52
53
    /**
54
     * The refresh hint turns any query into a refresh query with the result that
55
     * any local changes in entities are overridden with the fetched values.
56
     */
57
    public const HINT_REFRESH = 'doctrine.refresh';
58
59
    public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
60
61
    public const HINT_CACHE_EVICT = 'doctrine.cache.evict';
62
63
    /**
64
     * Internal hint: is set to the proxy entity that is currently triggered for loading
65
     */
66
    public const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
67
68
    /**
69
     * The forcePartialLoad query hint forces a particular query to return
70
     * partial objects.
71
     *
72
     * @todo Rename: HINT_OPTIMIZE
73
     */
74
    public const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
75
76
    /**
77
     * The includeMetaColumns query hint causes meta columns like foreign keys and
78
     * discriminator columns to be selected and returned as part of the query result.
79
     *
80
     * This hint does only apply to non-object queries.
81
     */
82
    public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
83
84
    /**
85
     * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
86
     * are iterated and executed after the DQL has been parsed into an AST.
87
     */
88
    public const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
89
90
    /**
91
     * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
92
     * and is used for generating the target SQL from any DQL AST tree.
93
     */
94
    public const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
95
96
    //const HINT_READ_ONLY = 'doctrine.readOnly';
97
98
    public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
99
100
    public const HINT_LOCK_MODE = 'doctrine.lockMode';
101
102
    /**
103
     * The current state of this query.
104
     *
105
     * @var int
106
     */
107
    private $state = self::STATE_CLEAN;
108
109
    /**
110
     * A snapshot of the parameter types the query was parsed with.
111
     *
112
     * @var mixed[]
113
     */
114
    private $parsedTypes = [];
115
116
    /**
117
     * Cached DQL query.
118
     *
119
     * @var string
120
     */
121
    private $dql;
122
123
    /**
124
     * The parser result that holds DQL => SQL information.
125
     *
126
     * @var ParserResult
127
     */
128
    private $parserResult;
129
130
    /**
131
     * The first result to return (the "offset").
132
     *
133
     * @var int
134
     */
135
    private $firstResult;
136
137
    /**
138
     * The maximum number of results to return (the "limit").
139
     *
140
     * @var int|null
141
     */
142
    private $maxResults;
143
144
    /**
145
     * The cache driver used for caching queries.
146
     *
147
     * @var Cache|null
148
     */
149
    private $queryCache;
150
151
    /**
152
     * Whether or not expire the query cache.
153
     *
154
     * @var bool
155
     */
156
    private $expireQueryCache = false;
157
158
    /**
159
     * The query cache lifetime.
160
     *
161
     * @var int
162
     */
163
    private $queryCacheTTL;
164
165
    /**
166
     * Whether to use a query cache, if available. Defaults to TRUE.
167
     *
168
     * @var bool
169
     */
170
    private $useQueryCache = true;
171
172
    /**
173
     * Gets the SQL query/queries that correspond to this DQL query.
174
     *
175
     * @return mixed The built sql query or an array of all sql queries.
176
     *
177
     * @override
178
     */
179 342
    public function getSQL()
180
    {
181 342
        return $this->parse()->getSqlExecutor()->getSqlStatements();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parse()->g...r()->getSqlStatements() returns the type string[] 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...
182
    }
183
184
    /**
185
     * Returns the corresponding AST for this DQL query.
186
     *
187
     * @return SelectStatement|UpdateStatement|DeleteStatement
188
     */
189 2
    public function getAST()
190
    {
191 2
        $parser = new Parser($this);
192
193 2
        return $parser->getAST();
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 450
    protected function getResultSetMapping()
200
    {
201
        // parse query or load from cache
202 450
        if ($this->resultSetMapping === null) {
203 38
            $this->resultSetMapping = $this->parse()->getResultSetMapping();
204
        }
205
206 447
        return $this->resultSetMapping;
207
    }
208
209
    /**
210
     * Parses the DQL query, if necessary, and stores the parser result.
211
     *
212
     * Note: Populates $this->parserResult as a side-effect.
213
     *
214
     * @return ParserResult
215
     */
216 775
    private function parse()
217
    {
218 775
        $types = [];
219
220 775
        foreach ($this->parameters as $parameter) {
221 178
            if (!$parameter instanceof Parameter) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space(s) after NOT operator; 0 found
Loading history...
222
                throw new QueryException('Set parameter must be instance of \Doctrine\ORM\Query\Parameter');
223
            }
224
225 178
            $types[$parameter->getName()] = $parameter->getType();
226
        }
227
228
        // Return previous parser result if the query and the filter collection are both clean
229 775
        if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->em->isFiltersStateClean()) {
230 39
            return $this->parserResult;
231
        }
232
233 775
        $this->state       = self::STATE_CLEAN;
234 775
        $this->parsedTypes = $types;
235
236
        // Check query cache.
237 775
        $queryCache = $this->getQueryCacheDriver();
238 775
        if (! ($this->useQueryCache && $queryCache)) {
239 181
            $parser = new Parser($this);
240
241 181
            $this->parserResult = $parser->parse();
242
243 177
            return $this->parserResult;
244
        }
245
246 594
        $hash   = $this->getQueryCacheId();
247 594
        $cached = $this->expireQueryCache ? false : $queryCache->fetch($hash);
248
249 594
        if ($cached instanceof ParserResult) {
250
            // Cache hit.
251 120
            $this->parserResult = $cached;
252
253 120
            return $this->parserResult;
254
        }
255
256
        // Cache miss.
257 542
        $parser = new Parser($this);
258
259 542
        $this->parserResult = $parser->parse();
260
261 526
        $queryCache->save($hash, $this->parserResult, $this->queryCacheTTL);
262
263 526
        return $this->parserResult;
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269 465
    protected function doExecute()
270
    {
271 465
        $executor = $this->parse()->getSqlExecutor();
272
273 460
        if ($this->queryCacheProfile) {
274 8
            $executor->setQueryCacheProfile($this->queryCacheProfile);
275
        } else {
276 454
            $executor->removeQueryCacheProfile();
277
        }
278
279 460
        if ($this->resultSetMapping === null) {
280 418
            $this->resultSetMapping = $this->parserResult->getResultSetMapping();
281
        }
282
283
        // Prepare parameters
284 460
        $paramMappings = $this->parserResult->getParameterMappings();
285 460
        $paramCount    = count($this->parameters);
286 460
        $mappingCount  = count($paramMappings);
287
288 460
        if ($paramCount > $mappingCount) {
289 2
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
290
        }
291
292 459
        if ($paramCount < $mappingCount) {
293 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
294
        }
295
296
        // evict all cache for the entity region
297 458
        if ($this->hasCache && isset($this->hints[self::HINT_CACHE_EVICT]) && $this->hints[self::HINT_CACHE_EVICT]) {
298 2
            $this->evictEntityCacheRegion();
299
        }
300
301 458
        [$sqlParams, $types] = $this->processParameterMappings($paramMappings);
302
303 457
        $this->evictResultSetCache(
304 457
            $executor,
305 457
            $sqlParams,
306 457
            $types,
307 457
            $this->em->getConnection()->getParams()
308
        );
309
310 457
        return $executor->execute($this->em->getConnection(), $sqlParams, $types);
311
    }
312
313
    /**
314
     * @param mixed[] $sqlParams
315
     * @param mixed[] $types
316
     * @param mixed[] $connectionParams
317
     */
318 457
    private function evictResultSetCache(
319
        AbstractSqlExecutor $executor,
320
        array $sqlParams,
321
        array $types,
322
        array $connectionParams
323
    ) {
324 457
        if ($this->queryCacheProfile === null || ! $this->getExpireResultCache()) {
325 457
            return;
326
        }
327
328 2
        $cacheDriver = $this->queryCacheProfile->getResultCacheDriver();
329 2
        $statements  = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
330
331 2
        foreach ($statements as $statement) {
332 2
            $cacheKeys = $this->queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
333
334 2
            $cacheDriver->delete(reset($cacheKeys));
335
        }
336 2
    }
337
338
    /**
339
     * Evict entity cache region
340
     */
341 2
    private function evictEntityCacheRegion()
342
    {
343 2
        $AST = $this->getAST();
344
345 2
        if ($AST instanceof SelectStatement) {
346
            throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
347
        }
348
349 2
        $className = $AST instanceof DeleteStatement
350 1
            ? $AST->deleteClause->abstractSchemaName
351 2
            : $AST->updateClause->abstractSchemaName;
352
353 2
        $this->em->getCache()->evictEntityRegion($className);
354 2
    }
355
356
    /**
357
     * Processes query parameter mappings.
358
     *
359
     * @param mixed[] $paramMappings
360
     *
361
     * @return mixed[][]
362
     *
363
     * @throws Query\QueryException
364
     */
365 458
    private function processParameterMappings($paramMappings)
366
    {
367 458
        $sqlParams = [];
368 458
        $types     = [];
369
370 458
        foreach ($this->parameters as $parameter) {
371 165
            $key = $parameter->getName();
372
373 165
            if (! isset($paramMappings[$key])) {
374 1
                throw QueryException::unknownParameter($key);
375
            }
376
377 164
            [$value, $type] = $this->resolveParameterValue($parameter);
378
379 164
            foreach ($paramMappings[$key] as $position) {
380 164
                $types[$position] = $type;
381
            }
382
383 164
            $sqlPositions      = $paramMappings[$key];
384 164
            $sqlPositionsCount = count($sqlPositions);
385
386
            // optimized multi value sql positions away for now,
387
            // they are not allowed in DQL anyways.
388 164
            $value      = [$value];
389 164
            $countValue = count($value);
390
391 164
            for ($i = 0, $l = $sqlPositionsCount; $i < $l; $i++) {
392 164
                $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
393
            }
394
        }
395
396 457
        if (count($sqlParams) !== count($types)) {
397
            throw QueryException::parameterTypeMismatch();
398
        }
399
400 457
        if ($sqlParams) {
401 164
            ksort($sqlParams);
402 164
            $sqlParams = array_values($sqlParams);
403
404 164
            ksort($types);
405 164
            $types = array_values($types);
406
        }
407
408 457
        return [$sqlParams, $types];
409
    }
410
411
    /** @return mixed[] tuple of (value, type) */
412 164
    private function resolveParameterValue(Parameter $parameter) : array
413
    {
414 164
        if ($parameter->typeWasSpecified()) {
415 5
            return [$parameter->getValue(), $parameter->getType()];
416
        }
417
418 160
        $key           = $parameter->getName();
419 160
        $originalValue = $parameter->getValue();
420 160
        $value         = $originalValue;
421 160
        $rsm           = $this->getResultSetMapping();
422
423 160
        assert($rsm !== null);
424
425 160
        if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) {
426
            $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
0 ignored issues
show
Bug introduced by
The method getMetadataValue() does not exist on Doctrine\ORM\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

426
            /** @scrutinizer ignore-call */ 
427
            $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
427
        }
428
429 160
        if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) {
430 3
            $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->em));
431
        }
432
433 160
        $processedValue = $this->processParameterValue($value);
434
435
        return [
436 160
            $processedValue,
437 160
            $originalValue === $processedValue
438 149
                ? $parameter->getType()
439 160
                : ParameterTypeInferer::inferType($processedValue),
440
        ];
441
    }
442
443
    /**
444
     * Defines a cache driver to be used for caching queries.
445
     *
446
     * @param Cache|null $queryCache Cache driver.
447
     *
448
     * @return Query This query instance.
449
     */
450 6
    public function setQueryCacheDriver($queryCache)
451
    {
452 6
        $this->queryCache = $queryCache;
453
454 6
        return $this;
455
    }
456
457
    /**
458
     * Defines whether the query should make use of a query cache, if available.
459
     *
460
     * @param bool $bool
461
     *
462
     * @return Query This query instance.
463
     */
464 184
    public function useQueryCache($bool)
465
    {
466 184
        $this->useQueryCache = $bool;
467
468 184
        return $this;
469
    }
470
471
    /**
472
     * Returns the cache driver used for query caching.
473
     *
474
     * @return Cache|null The cache driver used for query caching or NULL, if
475
     *                                           this Query does not use query caching.
476
     */
477 775
    public function getQueryCacheDriver()
478
    {
479 775
        if ($this->queryCache) {
480 9
            return $this->queryCache;
481
        }
482
483 766
        return $this->em->getConfiguration()->getQueryCacheImpl();
484
    }
485
486
    /**
487
     * Defines how long the query cache will be active before expire.
488
     *
489
     * @param int $timeToLive How long the cache entry is valid.
490
     *
491
     * @return Query This query instance.
492
     */
493 1
    public function setQueryCacheLifetime($timeToLive)
494
    {
495 1
        if ($timeToLive !== null) {
0 ignored issues
show
introduced by
The condition $timeToLive !== null is always true.
Loading history...
496 1
            $timeToLive = (int) $timeToLive;
497
        }
498
499 1
        $this->queryCacheTTL = $timeToLive;
500
501 1
        return $this;
502
    }
503
504
    /**
505
     * Retrieves the lifetime of resultset cache.
506
     *
507
     * @return int
508
     */
509
    public function getQueryCacheLifetime()
510
    {
511
        return $this->queryCacheTTL;
512
    }
513
514
    /**
515
     * Defines if the query cache is active or not.
516
     *
517
     * @param bool $expire Whether or not to force query cache expiration.
518
     *
519
     * @return Query This query instance.
520
     */
521 7
    public function expireQueryCache($expire = true)
522
    {
523 7
        $this->expireQueryCache = $expire;
524
525 7
        return $this;
526
    }
527
528
    /**
529
     * Retrieves if the query cache is active or not.
530
     *
531
     * @return bool
532
     */
533
    public function getExpireQueryCache()
534
    {
535
        return $this->expireQueryCache;
536
    }
537
538 220
    public function free()
539
    {
540 220
        parent::free();
541
542 220
        $this->dql   = null;
543 220
        $this->state = self::STATE_CLEAN;
544 220
    }
545
546
    /**
547
     * Sets a DQL query string.
548
     *
549
     * @param string $dqlQuery DQL Query.
550
     *
551
     * @return AbstractQuery
552
     */
553 960
    public function setDQL($dqlQuery)
554
    {
555 960
        if ($dqlQuery !== null) {
0 ignored issues
show
introduced by
The condition $dqlQuery !== null is always true.
Loading history...
556 960
            $this->dql   = $dqlQuery;
557 960
            $this->state = self::STATE_DIRTY;
558
        }
559
560 960
        return $this;
561
    }
562
563
    /**
564
     * Returns the DQL query that is represented by this query object.
565
     *
566
     * @return string DQL query.
567
     */
568 905
    public function getDQL()
569
    {
570 905
        return $this->dql;
571
    }
572
573
    /**
574
     * Returns the state of this query object
575
     * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
576
     * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
577
     *
578
     * @see AbstractQuery::STATE_CLEAN
579
     * @see AbstractQuery::STATE_DIRTY
580
     *
581
     * @return int The query state.
582
     */
583
    public function getState()
584
    {
585
        return $this->state;
586
    }
587
588
    /**
589
     * Method to check if an arbitrary piece of DQL exists
590
     *
591
     * @param string $dql Arbitrary piece of DQL to check for.
592
     *
593
     * @return bool
594
     */
595
    public function contains($dql)
596
    {
597
        return stripos($this->getDQL(), $dql) !== false;
598
    }
599
600
    /**
601
     * Sets the position of the first result to retrieve (the "offset").
602
     *
603
     * @param int $firstResult The first result to return.
604
     *
605
     * @return Query This query object.
606
     */
607 221
    public function setFirstResult($firstResult)
608
    {
609 221
        $this->firstResult = $firstResult;
610 221
        $this->state       = self::STATE_DIRTY;
611
612 221
        return $this;
613
    }
614
615
    /**
616
     * Gets the position of the first result the query object was set to retrieve (the "offset").
617
     * Returns NULL if {@link setFirstResult} was not applied to this query.
618
     *
619
     * @return int The position of the first result.
620
     */
621 664
    public function getFirstResult()
622
    {
623 664
        return $this->firstResult;
624
    }
625
626
    /**
627
     * Sets the maximum number of results to retrieve (the "limit").
628
     *
629
     * @param int|null $maxResults
630
     *
631
     * @return Query This query object.
632
     */
633 245
    public function setMaxResults($maxResults)
634
    {
635 245
        $this->maxResults = $maxResults;
636 245
        $this->state      = self::STATE_DIRTY;
637
638 245
        return $this;
639
    }
640
641
    /**
642
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
643
     * Returns NULL if {@link setMaxResults} was not applied to this query.
644
     *
645
     * @return int|null Maximum number of results.
646
     */
647 664
    public function getMaxResults()
648
    {
649 664
        return $this->maxResults;
650
    }
651
652
    /**
653
     * Executes the query and returns an IterableResult that can be used to incrementally
654
     * iterated over the result.
655
     *
656
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters    The query parameters.
657
     * @param int                                            $hydrationMode The hydration mode to use.
658
     *
659
     * @return IterableResult
660
     */
661 10
    public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT)
662
    {
663 10
        $this->setHint(self::HINT_INTERNAL_ITERATION, true);
664
665 10
        return parent::iterate($parameters, $hydrationMode);
666
    }
667
668
    /**
669
     * {@inheritdoc}
670
     */
671 466
    public function setHint($name, $value)
672
    {
673 466
        $this->state = self::STATE_DIRTY;
674
675 466
        return parent::setHint($name, $value);
676
    }
677
678
    /**
679
     * {@inheritdoc}
680
     */
681 363
    public function setHydrationMode($hydrationMode)
682
    {
683 363
        $this->state = self::STATE_DIRTY;
684
685 363
        return parent::setHydrationMode($hydrationMode);
686
    }
687
688
    /**
689
     * Set the lock mode for this Query.
690
     *
691
     * @see \Doctrine\DBAL\LockMode
692
     *
693
     * @param int $lockMode
694
     *
695
     * @return Query
696
     *
697
     * @throws TransactionRequiredException
698
     */
699
    public function setLockMode($lockMode)
700
    {
701
        if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) {
702
            if (! $this->em->getConnection()->isTransactionActive()) {
703
                throw TransactionRequiredException::transactionRequired();
704
            }
705
        }
706
707
        $this->setHint(self::HINT_LOCK_MODE, $lockMode);
708
709
        return $this;
710
    }
711
712
    /**
713
     * Get the current lock mode for this query.
714
     *
715
     * @return int|null The current lock mode of this query or NULL if no specific lock mode is set.
716
     */
717
    public function getLockMode()
718
    {
719
        $lockMode = $this->getHint(self::HINT_LOCK_MODE);
720
721
        if ($lockMode === false) {
722
            return null;
723
        }
724
725
        return $lockMode;
726
    }
727
728
    /**
729
     * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
730
     *
731
     * @return string
732
     */
733 594
    protected function getQueryCacheId()
734
    {
735 594
        ksort($this->hints);
736
737 594
        $platform = $this->getEntityManager()
738 594
            ->getConnection()
739 594
            ->getDatabasePlatform()
740 594
            ->getName();
741
742 594
        return md5(
743 594
            $this->getDQL() . serialize($this->hints) .
744 594
            '&platform=' . $platform .
745 594
            ($this->em->hasFilters() ? $this->em->getFilters()->getHash() : '') .
746 594
            '&firstResult=' . $this->firstResult . '&maxResult=' . $this->maxResults .
747 594
            '&hydrationMode=' . $this->hydrationMode . '&types=' . serialize($this->parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT'
748
        );
749
    }
750
751
    /**
752
     * {@inheritdoc}
753
     */
754 28
    protected function getHash()
755
    {
756 28
        return sha1(parent::getHash() . '-' . $this->firstResult . '-' . $this->maxResults);
757
    }
758
759
    /**
760
     * Cleanup Query resource when clone is called.
761
     */
762 142
    public function __clone()
763
    {
764 142
        parent::__clone();
765
766 142
        $this->state = self::STATE_DIRTY;
767 142
    }
768
}
769