Completed
Pull Request — master (#6735)
by Matthias
11:52
created

Query::setMaxResults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

402
                /** @scrutinizer ignore-call */ 
403
                $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...
403
            }
404
405 166
            if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
406 3
                $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->em));
407
            }
408
409 166
            if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(StaticClassNameConverter::getClass($value))) {
410 13
                $metadata = $this->em->getClassMetadata(StaticClassNameConverter::getClass($value));
411 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

411
                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...
412 1
                    ORMInvalidArgumentException::invalidCompositeIdentifier();
413
                }
414 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

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