Completed
Push — master ( 8c259e...a3e53b )
by Guilherme
27:10 queued 12:02
created

Query::getQueryCacheId()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 1
nop 0
dl 0
loc 15
ccs 12
cts 12
cp 1
crap 2
rs 9.9
c 0
b 0
f 0
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 346
    public function getSQL()
180
    {
181 346
        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 779
    private function parse()
217
    {
218 779
        $types = [];
219
220 779
        foreach ($this->parameters as $parameter) {
221
            /** @var Query\Parameter $parameter */
222 178
            $types[$parameter->getName()] = $parameter->getType();
223
        }
224
225
        // Return previous parser result if the query and the filter collection are both clean
226 779
        if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->em->isFiltersStateClean()) {
227 39
            return $this->parserResult;
228
        }
229
230 779
        $this->state       = self::STATE_CLEAN;
231 779
        $this->parsedTypes = $types;
232
233
        // Check query cache.
234 779
        $queryCache = $this->getQueryCacheDriver();
235 779
        if (! ($this->useQueryCache && $queryCache)) {
236 185
            $parser = new Parser($this);
237
238 185
            $this->parserResult = $parser->parse();
239
240 181
            return $this->parserResult;
241
        }
242
243 594
        $hash   = $this->getQueryCacheId();
244 594
        $cached = $this->expireQueryCache ? false : $queryCache->fetch($hash);
245
246 594
        if ($cached instanceof ParserResult) {
247
            // Cache hit.
248 120
            $this->parserResult = $cached;
249
250 120
            return $this->parserResult;
251
        }
252
253
        // Cache miss.
254 542
        $parser = new Parser($this);
255
256 542
        $this->parserResult = $parser->parse();
257
258 526
        $queryCache->save($hash, $this->parserResult, $this->queryCacheTTL);
259
260 526
        return $this->parserResult;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266 465
    protected function doExecute()
267
    {
268 465
        $executor = $this->parse()->getSqlExecutor();
269
270 460
        if ($this->queryCacheProfile) {
271 8
            $executor->setQueryCacheProfile($this->queryCacheProfile);
272
        } else {
273 454
            $executor->removeQueryCacheProfile();
274
        }
275
276 460
        if ($this->resultSetMapping === null) {
277 418
            $this->resultSetMapping = $this->parserResult->getResultSetMapping();
278
        }
279
280
        // Prepare parameters
281 460
        $paramMappings = $this->parserResult->getParameterMappings();
282 460
        $paramCount    = count($this->parameters);
283 460
        $mappingCount  = count($paramMappings);
284
285 460
        if ($paramCount > $mappingCount) {
286 2
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
287
        }
288
289 459
        if ($paramCount < $mappingCount) {
290 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
291
        }
292
293
        // evict all cache for the entity region
294 458
        if ($this->hasCache && isset($this->hints[self::HINT_CACHE_EVICT]) && $this->hints[self::HINT_CACHE_EVICT]) {
295 2
            $this->evictEntityCacheRegion();
296
        }
297
298 458
        [$sqlParams, $types] = $this->processParameterMappings($paramMappings);
299
300 457
        $this->evictResultSetCache(
301 457
            $executor,
302
            $sqlParams,
303
            $types,
304 457
            $this->em->getConnection()->getParams()
305
        );
306
307 457
        return $executor->execute($this->em->getConnection(), $sqlParams, $types);
308
    }
309
310
    /**
311
     * @param mixed[] $sqlParams
312
     * @param mixed[] $types
313
     * @param mixed[] $connectionParams
314
     */
315 457
    private function evictResultSetCache(
316
        AbstractSqlExecutor $executor,
317
        array $sqlParams,
318
        array $types,
319
        array $connectionParams
320
    ) {
321 457
        if ($this->queryCacheProfile === null || ! $this->getExpireResultCache()) {
322 457
            return;
323
        }
324
325 2
        $cacheDriver = $this->queryCacheProfile->getResultCacheDriver();
326 2
        $statements  = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
327
328 2
        foreach ($statements as $statement) {
329 2
            $cacheKeys = $this->queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
330
331 2
            $cacheDriver->delete(reset($cacheKeys));
332
        }
333 2
    }
334
335
    /**
336
     * Evict entity cache region
337
     */
338 2
    private function evictEntityCacheRegion()
339
    {
340 2
        $AST = $this->getAST();
341
342 2
        if ($AST instanceof SelectStatement) {
343
            throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
344
        }
345
346 2
        $className = $AST instanceof DeleteStatement
347 1
            ? $AST->deleteClause->abstractSchemaName
348 2
            : $AST->updateClause->abstractSchemaName;
349
350 2
        $this->em->getCache()->evictEntityRegion($className);
351 2
    }
352
353
    /**
354
     * Processes query parameter mappings.
355
     *
356
     * @param mixed[] $paramMappings
357
     *
358
     * @return mixed[][]
359
     *
360
     * @throws Query\QueryException
361
     */
362 458
    private function processParameterMappings($paramMappings)
363
    {
364 458
        $sqlParams = [];
365 458
        $types     = [];
366
367 458
        foreach ($this->parameters as $parameter) {
368 165
            $key = $parameter->getName();
369
370 165
            if (! isset($paramMappings[$key])) {
371 1
                throw QueryException::unknownParameter($key);
372
            }
373
374 164
            [$value, $type] = $this->resolveParameterValue($parameter);
375
376 164
            foreach ($paramMappings[$key] as $position) {
377 164
                $types[$position] = $type;
378
            }
379
380 164
            $sqlPositions      = $paramMappings[$key];
381 164
            $sqlPositionsCount = count($sqlPositions);
382
383
            // optimized multi value sql positions away for now,
384
            // they are not allowed in DQL anyways.
385 164
            $value      = [$value];
386 164
            $countValue = count($value);
387
388 164
            for ($i = 0, $l = $sqlPositionsCount; $i < $l; $i++) {
389 164
                $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
390
            }
391
        }
392
393 457
        if (count($sqlParams) !== count($types)) {
394
            throw QueryException::parameterTypeMismatch();
395
        }
396
397 457
        if ($sqlParams) {
398 164
            ksort($sqlParams);
399 164
            $sqlParams = array_values($sqlParams);
400
401 164
            ksort($types);
402 164
            $types = array_values($types);
403
        }
404
405 457
        return [$sqlParams, $types];
406
    }
407
408
    /** @return mixed[] tuple of (value, type) */
409 164
    private function resolveParameterValue(Parameter $parameter) : array
410
    {
411 164
        if ($parameter->typeWasSpecified()) {
412 5
            return [$parameter->getValue(), $parameter->getType()];
413
        }
414
415 160
        $key           = $parameter->getName();
416 160
        $originalValue = $parameter->getValue();
417 160
        $value         = $originalValue;
418 160
        $rsm           = $this->getResultSetMapping();
419
420 160
        assert($rsm !== null);
421
422 160
        if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) {
423
            $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

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