Failed Conditions
Pull Request — master (#6735)
by Matthias
09:54
created

Query::setDQL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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

379
                /** @scrutinizer ignore-call */ 
380
                $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...
380
            }
381
382 166
            if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
383 3
                $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->em));
384
            }
385
386 166
            if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(StaticClassNameConverter::getClass($value))) {
387 13
                $metadata = $this->em->getClassMetadata(StaticClassNameConverter::getClass($value));
388 13
                if ($metadata->isIdentifierComposite()) {
0 ignored issues
show
Bug introduced by
The method isIdentifierComposite() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean isIdentifier()? ( Ignorable by Annotation )

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

388
                if ($metadata->/** @scrutinizer ignore-call */ isIdentifierComposite()) {

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...
389 1
                    ORMInvalidArgumentException::invalidCompositeIdentifier();
390
                }
391 13
                $type = $metadata->getColumn($metadata->getIdentifier()[0])->getTypeName();
0 ignored issues
show
Bug introduced by
The method getColumn() does not exist on Doctrine\Common\Persistence\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

391
                $type = $metadata->/** @scrutinizer ignore-call */ getColumn($metadata->getIdentifier()[0])->getTypeName();

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