Completed
Pull Request — master (#6735)
by Matthias
11:44 queued 53s
created

Query::processParameterMappings()   F

Complexity

Conditions 16
Paths 292

Size

Total Lines 68
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 38
CRAP Score 16.032

Importance

Changes 0
Metric Value
cc 16
eloc 39
nc 292
nop 1
dl 0
loc 68
ccs 38
cts 40
cp 0.95
crap 16.032
rs 3.5833
c 0
b 0
f 0

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

403
                /** @scrutinizer ignore-call */ 
404
                $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...
404
            }
405
406 165
            if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
407 3
                $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->em));
408
            }
409
410 165
            if (is_object($value) && (
411 18
                    $this->em->getMetadataFactory()->hasMetadataFor(get_class($value))
412 165
                    || $value instanceof GhostObjectInterface
413
                )) {
414 12
                $metadata = $this->em->getClassMetadata(get_class($value));
415 12
                $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

415
                $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...
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

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