Completed
Push — master ( f4ef0a...d00cab )
by Marco
16s
created

Query::getQueryCacheLifetime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
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\Exec\AbstractSqlExecutor;
15
use Doctrine\ORM\Query\Parameter;
16
use Doctrine\ORM\Query\ParameterTypeInferer;
17
use Doctrine\ORM\Query\Parser;
18
use Doctrine\ORM\Query\ParserResult;
19
use Doctrine\ORM\Query\QueryException;
20
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
21
use function array_keys;
22
use function array_values;
23
use function count;
24
use function in_array;
25
use function ksort;
26
use function md5;
27
use function reset;
28
use function serialize;
29
use function sha1;
30
use function stripos;
31
32
/**
33
 * A Query object represents a DQL query.
34
 */
35
final class Query extends AbstractQuery
36
{
37
    /**
38
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
39
     */
40
    public const STATE_CLEAN = 1;
41
42
    /**
43
     * A query object is in state DIRTY when it has DQL parts that have not yet been
44
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
45
     * is called.
46
     */
47
    public const STATE_DIRTY = 2;
48
49
    /* Query HINTS */
50
51
    /**
52
     * The refresh hint turns any query into a refresh query with the result that
53
     * any local changes in entities are overridden with the fetched values.
54
     *
55
     * @var string
56
     */
57
    public const HINT_REFRESH = 'doctrine.refresh';
58
59
    /**
60
     * @var string
61
     */
62
    public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
63
64
    /**
65
     * @var string
66
     */
67
    public const HINT_CACHE_EVICT = 'doctrine.cache.evict';
68
69
    /**
70
     * Internal hint: is set to the proxy entity that is currently triggered for loading
71
     *
72
     * @var string
73
     */
74
    public const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
75
76
    /**
77
     * The forcePartialLoad query hint forces a particular query to return
78
     * partial objects.
79
     *
80
     * @var string
81
     * @todo Rename: HINT_OPTIMIZE
82
     */
83
    public const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
84
85
    /**
86
     * The includeMetaColumns query hint causes meta columns like foreign keys and
87
     * discriminator columns to be selected and returned as part of the query result.
88
     *
89
     * This hint does only apply to non-object queries.
90
     *
91
     * @var string
92
     */
93
    public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
94
95
    /**
96
     * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
97
     * are iterated and executed after the DQL has been parsed into an AST.
98
     *
99
     * @var string
100
     */
101
    public const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
102
103
    /**
104
     * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
105
     * and is used for generating the target SQL from any DQL AST tree.
106
     *
107
     * @var string
108
     */
109
    public const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
110
111
    //const HINT_READ_ONLY = 'doctrine.readOnly';
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
112
113
    /**
114
     * @var string
115
     */
116
    public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
117
118
    /**
119
     * @var string
120
     */
121
    public const HINT_LOCK_MODE = 'doctrine.lockMode';
122
123
    /**
124
     * The current state of this query.
125
     *
126
     * @var int
127
     */
128
    private $state = self::STATE_CLEAN;
129
130
    /**
131
     * A snapshot of the parameter types the query was parsed with.
132
     *
133
     * @var mixed[]
134
     */
135
    private $parsedTypes = [];
136
137
    /**
138
     * Cached DQL query.
139
     *
140
     * @var string
141
     */
142
    private $dql;
143
144
    /**
145
     * The parser result that holds DQL => SQL information.
146
     *
147
     * @var ParserResult
148
     */
149
    private $parserResult;
150
151
    /**
152
     * The first result to return (the "offset").
153
     *
154
     * @var int
155
     */
156
    private $firstResult;
157
158
    /**
159
     * The maximum number of results to return (the "limit").
160
     *
161
     * @var int|null
162
     */
163
    private $maxResults;
164
165
    /**
166
     * The cache driver used for caching queries.
167
     *
168
     * @var Cache|null
169
     */
170
    private $queryCache;
171
172
    /**
173
     * Whether or not expire the query cache.
174
     *
175
     * @var bool
176
     */
177
    private $expireQueryCache = false;
178
179
    /**
180
     * The query cache lifetime.
181
     *
182
     * @var int
183
     */
184
    private $queryCacheTTL;
185
186
    /**
187
     * Whether to use a query cache, if available. Defaults to TRUE.
188
     *
189
     * @var bool
190
     */
191
    private $useQueryCache = true;
192
193
    /**
194
     * Gets the SQL query/queries that correspond to this DQL query.
195
     *
196
     * @return mixed The built sql query or an array of all sql queries.
197
     *
198
     * @override
199
     */
200 342
    public function getSQL()
201
    {
202 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...
203
    }
204
205
    /**
206
     * Returns the corresponding AST for this DQL query.
207
     *
208
     * @return SelectStatement |
209
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
210
     *         \Doctrine\ORM\Query\AST\DeleteStatement
211
     */
212 2
    public function getAST()
213
    {
214 2
        $parser = new Parser($this);
215
216 2
        return $parser->getAST();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $parser->getAST() also could return the type Doctrine\ORM\Query\AST\U...ery\AST\DeleteStatement which is incompatible with the documented return type Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222 446
    protected function getResultSetMapping()
223
    {
224
        // parse query or load from cache
225 446
        if ($this->resultSetMapping === null) {
226 38
            $this->resultSetMapping = $this->parse()->getResultSetMapping();
227
        }
228
229 443
        return $this->resultSetMapping;
230
    }
231
232
    /**
233
     * Parses the DQL query, if necessary, and stores the parser result.
234
     *
235
     * Note: Populates $this->parserResult as a side-effect.
236
     *
237
     * @return ParserResult
238
     */
239 771
    private function parse()
240
    {
241 771
        $types = [];
242
243 771
        foreach ($this->parameters as $parameter) {
244
            /** @var Query\Parameter $parameter */
245 174
            $types[$parameter->getName()] = $parameter->getType();
246
        }
247
248
        // Return previous parser result if the query and the filter collection are both clean
249 771
        if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->em->isFiltersStateClean()) {
250 39
            return $this->parserResult;
251
        }
252
253 771
        $this->state       = self::STATE_CLEAN;
254 771
        $this->parsedTypes = $types;
255
256
        // Check query cache.
257 771
        $queryCache = $this->getQueryCacheDriver();
258 771
        if (! ($this->useQueryCache && $queryCache)) {
259 181
            $parser = new Parser($this);
260
261 181
            $this->parserResult = $parser->parse();
262
263 177
            return $this->parserResult;
264
        }
265
266 590
        $hash   = $this->getQueryCacheId();
267 590
        $cached = $this->expireQueryCache ? false : $queryCache->fetch($hash);
268
269 590
        if ($cached instanceof ParserResult) {
270
            // Cache hit.
271 118
            $this->parserResult = $cached;
272
273 118
            return $this->parserResult;
274
        }
275
276
        // Cache miss.
277 539
        $parser = new Parser($this);
278
279 539
        $this->parserResult = $parser->parse();
280
281 520
        $queryCache->save($hash, $this->parserResult, $this->queryCacheTTL);
282
283 520
        return $this->parserResult;
284
    }
285
286
    /**
287
     * {@inheritdoc}
288
     */
289 461
    protected function doExecute()
290
    {
291 461
        $executor = $this->parse()->getSqlExecutor();
292
293 454
        if ($this->queryCacheProfile) {
294 8
            $executor->setQueryCacheProfile($this->queryCacheProfile);
295
        } else {
296 448
            $executor->removeQueryCacheProfile();
297
        }
298
299 454
        if ($this->resultSetMapping === null) {
300 412
            $this->resultSetMapping = $this->parserResult->getResultSetMapping();
301
        }
302
303
        // Prepare parameters
304 454
        $paramMappings = $this->parserResult->getParameterMappings();
305 454
        $paramCount    = count($this->parameters);
306 454
        $mappingCount  = count($paramMappings);
307
308 454
        if ($paramCount > $mappingCount) {
309 1
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
310
        }
311
312 453
        if ($paramCount < $mappingCount) {
313 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
314
        }
315
316
        // evict all cache for the entity region
317 452
        if ($this->hasCache && isset($this->hints[self::HINT_CACHE_EVICT]) && $this->hints[self::HINT_CACHE_EVICT]) {
318 2
            $this->evictEntityCacheRegion();
319
        }
320
321 452
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
322
323 451
        $this->evictResultSetCache(
324 451
            $executor,
325 451
            $sqlParams,
326 451
            $types,
327 451
            $this->em->getConnection()->getParams()
328
        );
329
330 451
        return $executor->execute($this->em->getConnection(), $sqlParams, $types);
331
    }
332
333
    /**
334
     * @param mixed[] $sqlParams
335
     * @param mixed[] $types
336
     * @param mixed[] $connectionParams
337
     */
338 451
    private function evictResultSetCache(
339
        AbstractSqlExecutor $executor,
340
        array $sqlParams,
341
        array $types,
342
        array $connectionParams
343
    ) {
344 451
        if ($this->queryCacheProfile === null || ! $this->getExpireResultCache()) {
345 451
            return;
346
        }
347
348 2
        $cacheDriver = $this->queryCacheProfile->getResultCacheDriver();
349 2
        $statements  = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
350
351 2
        foreach ($statements as $statement) {
352 2
            $cacheKeys = $this->queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
353
354 2
            $cacheDriver->delete(reset($cacheKeys));
355
        }
356 2
    }
357
358
    /**
359
     * Evict entity cache region
360
     */
361 2
    private function evictEntityCacheRegion()
362
    {
363 2
        $AST = $this->getAST();
364
365 2
        if ($AST instanceof SelectStatement) {
0 ignored issues
show
introduced by
The condition $AST instanceof Doctrine...ery\AST\SelectStatement can never be false since $AST is always a sub-type of Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
366
            throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
367
        }
368
369 2
        $className = ($AST instanceof DeleteStatement)
370 1
            ? $AST->deleteClause->abstractSchemaName
371 2
            : $AST->updateClause->abstractSchemaName;
372
373 2
        $this->em->getCache()->evictEntityRegion($className);
374 2
    }
375
376
    /**
377
     * Processes query parameter mappings.
378
     *
379
     * @param mixed[] $paramMappings
380
     *
381
     * @return mixed[][]
382
     *
383
     * @throws Query\QueryException
384
     */
385 452
    private function processParameterMappings($paramMappings)
386
    {
387 452
        $sqlParams = [];
388 452
        $types     = [];
389
390 452
        foreach ($this->parameters as $parameter) {
391 162
            $key   = $parameter->getName();
392 162
            $value = $parameter->getValue();
393 162
            $rsm   = $this->getResultSetMapping();
394
395 162
            if (! isset($paramMappings[$key])) {
396 1
                throw QueryException::unknownParameter($key);
397
            }
398
399 161
            if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
400
                $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

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