Failed Conditions
Pull Request — master (#6735)
by Matthias
11:21
created

Query   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 739
Duplicated Lines 0 %

Test Coverage

Coverage 87.82%

Importance

Changes 0
Metric Value
eloc 185
dl 0
loc 739
ccs 173
cts 197
cp 0.8782
rs 2.48
c 0
b 0
f 0
wmc 74

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getSQL() 0 3 1
B parse() 0 45 9
A getResultSetMapping() 0 8 2
A getAST() 0 5 1
A evictEntityCacheRegion() 0 13 3
A evictResultSetCache() 0 17 4
B doExecute() 0 42 8
A getDQL() 0 3 1
A contains() 0 3 1
A setLockMode() 0 11 3
A setFirstResult() 0 6 1
A setDQL() 0 8 2
A setQueryCacheLifetime() 0 9 2
A getFirstResult() 0 3 1
A getQueryCacheId() 0 15 2
A getMaxResults() 0 3 1
A getHash() 0 3 1
A useQueryCache() 0 5 1
A setHydrationMode() 0 5 1
A getQueryCacheLifetime() 0 3 1
A getExpireQueryCache() 0 3 1
A setQueryCacheDriver() 0 5 1
A getLockMode() 0 9 2
A setMaxResults() 0 6 1
A iterate() 0 5 1
A expireQueryCache() 0 5 1
A setHint() 0 5 1
A free() 0 6 1
A getState() 0 3 1
F processParameterMappings() 0 65 15
A getQueryCacheDriver() 0 7 2
A __clone() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Query often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Query, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Cache\Cache;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Doctrine\ORM\Cache. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\DBAL\LockMode;
10
use Doctrine\ORM\Internal\Hydration\IterableResult;
11
use Doctrine\ORM\Mapping\ClassMetadata;
12
use Doctrine\ORM\Query\AST\DeleteStatement;
13
use Doctrine\ORM\Query\AST\SelectStatement;
14
use Doctrine\ORM\Query\AST\UpdateStatement;
15
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
16
use Doctrine\ORM\Query\Parameter;
17
use Doctrine\ORM\Query\ParameterTypeInferer;
18
use Doctrine\ORM\Query\Parser;
19
use Doctrine\ORM\Query\ParserResult;
20
use Doctrine\ORM\Query\QueryException;
21
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
22
use function array_keys;
23
use function array_values;
24
use function count;
25
use function in_array;
26
use function ksort;
27
use function md5;
28
use function reset;
29
use function serialize;
30
use function sha1;
31
use function stripos;
32
33
/**
34
 * A Query object represents a DQL query.
35
 */
36
final class Query extends AbstractQuery
37
{
38
    /**
39
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
40
     */
41
    public const STATE_CLEAN = 1;
42
43
    /**
44
     * A query object is in state DIRTY when it has DQL parts that have not yet been
45
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
46
     * is called.
47
     */
48
    public const STATE_DIRTY = 2;
49
50
    /* Query HINTS */
51
52
    /**
53
     * The refresh hint turns any query into a refresh query with the result that
54
     * any local changes in entities are overridden with the fetched values.
55
     *
56
     * @var string
57
     */
58
    public const HINT_REFRESH = 'doctrine.refresh';
59
60
    /**
61
     * @var string
62
     */
63
    public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
64
65
    /**
66
     * @var string
67
     */
68
    public const HINT_CACHE_EVICT = 'doctrine.cache.evict';
69
70
    /**
71
     * Internal hint: is set to the proxy entity that is currently triggered for loading
72
     *
73
     * @var string
74
     */
75
    public const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
76
77
    /**
78
     * The forcePartialLoad query hint forces a particular query to return
79
     * partial objects.
80
     *
81
     * @var string
82
     * @todo Rename: HINT_OPTIMIZE
83
     */
84
    public const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
85
86
    /**
87
     * The includeMetaColumns query hint causes meta columns like foreign keys and
88
     * discriminator columns to be selected and returned as part of the query result.
89
     *
90
     * This hint does only apply to non-object queries.
91
     *
92
     * @var string
93
     */
94
    public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
95
96
    /**
97
     * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
98
     * are iterated and executed after the DQL has been parsed into an AST.
99
     *
100
     * @var string
101
     */
102
    public const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
103
104
    /**
105
     * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
106
     * and is used for generating the target SQL from any DQL AST tree.
107
     *
108
     * @var string
109
     */
110
    public const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
111
112
    //const HINT_READ_ONLY = 'doctrine.readOnly';
113
114
    /**
115
     * @var string
116
     */
117
    public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
118
119
    /**
120
     * @var string
121
     */
122
    public const HINT_LOCK_MODE = 'doctrine.lockMode';
123
124
    /**
125
     * The current state of this query.
126
     *
127
     * @var int
128
     */
129
    private $state = self::STATE_CLEAN;
130
131
    /**
132
     * A snapshot of the parameter types the query was parsed with.
133
     *
134
     * @var mixed[]
135
     */
136
    private $parsedTypes = [];
137
138
    /**
139
     * Cached DQL query.
140
     *
141
     * @var string
142
     */
143
    private $dql;
144
145
    /**
146
     * The parser result that holds DQL => SQL information.
147
     *
148
     * @var ParserResult
149
     */
150
    private $parserResult;
151
152
    /**
153
     * The first result to return (the "offset").
154
     *
155
     * @var int
156
     */
157
    private $firstResult;
158
159
    /**
160
     * The maximum number of results to return (the "limit").
161
     *
162
     * @var int|null
163
     */
164
    private $maxResults;
165
166
    /**
167
     * The cache driver used for caching queries.
168
     *
169
     * @var Cache|null
170
     */
171
    private $queryCache;
172
173
    /**
174
     * Whether or not expire the query cache.
175
     *
176
     * @var bool
177
     */
178
    private $expireQueryCache = false;
179
180
    /**
181
     * The query cache lifetime.
182
     *
183
     * @var int
184
     */
185
    private $queryCacheTTL;
186
187
    /**
188
     * Whether to use a query cache, if available. Defaults to TRUE.
189
     *
190
     * @var bool
191
     */
192
    private $useQueryCache = true;
193
194
    /**
195
     * Gets the SQL query/queries that correspond to this DQL query.
196
     *
197
     * @return mixed The built sql query or an array of all sql queries.
198
     *
199
     * @override
200
     */
201 342
    public function getSQL()
202
    {
203 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...
204
    }
205
206
    /**
207
     * Returns the corresponding AST for this DQL query.
208
     *
209
     * @return SelectStatement|UpdateStatement|DeleteStatement
210
     */
211 2
    public function getAST()
212
    {
213 2
        $parser = new Parser($this);
214
215 2
        return $parser->getAST();
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 436
    protected function getResultSetMapping()
222
    {
223
        // parse query or load from cache
224 436
        if ($this->resultSetMapping === null) {
225 38
            $this->resultSetMapping = $this->parse()->getResultSetMapping();
226
        }
227
228 433
        return $this->resultSetMapping;
229
    }
230
231
    /**
232
     * Parses the DQL query, if necessary, and stores the parser result.
233
     *
234
     * Note: Populates $this->parserResult as a side-effect.
235
     *
236
     * @return ParserResult
237
     */
238 759
    private function parse()
239
    {
240 759
        $types = [];
241
242 759
        foreach ($this->parameters as $parameter) {
243
            /** @var Query\Parameter $parameter */
244 162
            $types[$parameter->getName()] = $parameter->getType();
245
        }
246
247
        // Return previous parser result if the query and the filter collection are both clean
248 759
        if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->em->isFiltersStateClean()) {
249 39
            return $this->parserResult;
250
        }
251
252 759
        $this->state       = self::STATE_CLEAN;
253 759
        $this->parsedTypes = $types;
254
255
        // Check query cache.
256 759
        $queryCache = $this->getQueryCacheDriver();
257 759
        if (! ($this->useQueryCache && $queryCache)) {
258 181
            $parser = new Parser($this);
259
260 181
            $this->parserResult = $parser->parse();
261
262 177
            return $this->parserResult;
263
        }
264
265 578
        $hash   = $this->getQueryCacheId();
266 578
        $cached = $this->expireQueryCache ? false : $queryCache->fetch($hash);
267
268 578
        if ($cached instanceof ParserResult) {
269
            // Cache hit.
270 116
            $this->parserResult = $cached;
271
272 116
            return $this->parserResult;
273
        }
274
275
        // Cache miss.
276 527
        $parser = new Parser($this);
277
278 527
        $this->parserResult = $parser->parse();
279
280 511
        $queryCache->save($hash, $this->parserResult, $this->queryCacheTTL);
281
282 511
        return $this->parserResult;
283
    }
284
285
    /**
286
     * {@inheritdoc}
287
     */
288 449
    protected function doExecute()
289
    {
290 449
        $executor = $this->parse()->getSqlExecutor();
291
292 444
        if ($this->queryCacheProfile) {
293 7
            $executor->setQueryCacheProfile($this->queryCacheProfile);
294
        } else {
295 439
            $executor->removeQueryCacheProfile();
296
        }
297
298 444
        if ($this->resultSetMapping === null) {
299 402
            $this->resultSetMapping = $this->parserResult->getResultSetMapping();
300
        }
301
302
        // Prepare parameters
303 444
        $paramMappings = $this->parserResult->getParameterMappings();
304 444
        $paramCount    = count($this->parameters);
305 444
        $mappingCount  = count($paramMappings);
306
307 444
        if ($paramCount > $mappingCount) {
308 2
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
309
        }
310
311 443
        if ($paramCount < $mappingCount) {
312 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
313
        }
314
315
        // evict all cache for the entity region
316 442
        if ($this->hasCache && isset($this->hints[self::HINT_CACHE_EVICT]) && $this->hints[self::HINT_CACHE_EVICT]) {
317 2
            $this->evictEntityCacheRegion();
318
        }
319
320 442
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
321
322 440
        $this->evictResultSetCache(
323 440
            $executor,
324 440
            $sqlParams,
325 440
            $types,
326 440
            $this->em->getConnection()->getParams()
327
        );
328
329 440
        return $executor->execute($this->em->getConnection(), $sqlParams, $types);
330
    }
331
332
    /**
333
     * @param mixed[] $sqlParams
334
     * @param mixed[] $types
335
     * @param mixed[] $connectionParams
336
     */
337 440
    private function evictResultSetCache(
338
        AbstractSqlExecutor $executor,
339
        array $sqlParams,
340
        array $types,
341
        array $connectionParams
342
    ) {
343 440
        if ($this->queryCacheProfile === null || ! $this->getExpireResultCache()) {
344 440
            return;
345
        }
346
347 2
        $cacheDriver = $this->queryCacheProfile->getResultCacheDriver();
348 2
        $statements  = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
349
350 2
        foreach ($statements as $statement) {
351 2
            $cacheKeys = $this->queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
352
353 2
            $cacheDriver->delete(reset($cacheKeys));
354
        }
355 2
    }
356
357
    /**
358
     * Evict entity cache region
359
     */
360 2
    private function evictEntityCacheRegion()
361
    {
362 2
        $AST = $this->getAST();
363
364 2
        if ($AST instanceof SelectStatement) {
365
            throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
366
        }
367
368 2
        $className = ($AST instanceof DeleteStatement)
369 1
            ? $AST->deleteClause->abstractSchemaName
370 2
            : $AST->updateClause->abstractSchemaName;
371
372 2
        $this->em->getCache()->evictEntityRegion($className);
373 2
    }
374
375
    /**
376
     * Processes query parameter mappings.
377
     *
378
     * @param mixed[] $paramMappings
379
     *
380
     * @return mixed[][]
381
     *
382
     * @throws Query\QueryException
383
     */
384 442
    private function processParameterMappings($paramMappings)
385
    {
386 442
        $sqlParams = [];
387 442
        $types     = [];
388
389 442
        foreach ($this->parameters as $parameter) {
390 149
            $key   = $parameter->getName();
391 149
            $value = $parameter->getValue();
392 149
            $rsm   = $this->getResultSetMapping();
393 149
            $type  = null;
394
395 149
            if (! isset($paramMappings[$key])) {
396 1
                throw QueryException::unknownParameter($key);
397
            }
398
399 148
            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 148
            if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
404 3
                $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->em));
405
            }
406
407 148
            if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
0 ignored issues
show
Bug Best Practice introduced by
The property _em does not exist on Doctrine\ORM\Query. Did you maybe forget to declare it?
Loading history...
introduced by
Function is_object() should not be referenced via a fallback global name, but via a use statement.
Loading history...
introduced by
Function get_class() should not be referenced via a fallback global name, but via a use statement.
Loading history...
408
                $metadata = $this->_em->getClassMetadata(get_class($value));
0 ignored issues
show
introduced by
Function get_class() should not be referenced via a fallback global name, but via a use statement.
Loading history...
409
                $type = $metadata->getTypeOfField($metadata->getIdentifier()[0]);
0 ignored issues
show
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...
410
            }
411
412 147
            $value = $this->processParameterValue($value);
413 147
            if (null === $type) {
0 ignored issues
show
introduced by
Yoda comparisons are disallowed.
Loading history...
414 147
                $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...
415 143
                    ? $parameter->getType()
416 147
                    : ParameterTypeInferer::inferType($value);
417
            }
418
419 147
            foreach ($paramMappings[$key] as $position) {
420 147
                $types[$position] = $type;
421
            }
422
423 147
            $sqlPositions      = $paramMappings[$key];
424 147
            $sqlPositionsCount = count($sqlPositions);
425
426
            // optimized multi value sql positions away for now,
427
            // they are not allowed in DQL anyways.
428 147
            $value      = [$value];
429 147
            $countValue = count($value);
430
431 147
            for ($i = 0, $l = $sqlPositionsCount; $i < $l; $i++) {
432 147
                $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
433
            }
434
        }
435
436 440
        if (count($sqlParams) !== count($types)) {
437
            throw QueryException::parameterTypeMismatch();
438
        }
439
440 440
        if ($sqlParams) {
441 147
            ksort($sqlParams);
442 147
            $sqlParams = array_values($sqlParams);
443
444 147
            ksort($types);
445 147
            $types = array_values($types);
446
        }
447
448 440
        return [$sqlParams, $types];
449
    }
450
451
    /**
452
     * Defines a cache driver to be used for caching queries.
453
     *
454
     * @param Cache|null $queryCache Cache driver.
455
     *
456
     * @return Query This query instance.
457
     */
458 6
    public function setQueryCacheDriver($queryCache)
459
    {
460 6
        $this->queryCache = $queryCache;
461
462 6
        return $this;
463
    }
464
465
    /**
466
     * Defines whether the query should make use of a query cache, if available.
467
     *
468
     * @param bool $bool
469
     *
470
     * @return Query This query instance.
471
     */
472 183
    public function useQueryCache($bool)
473
    {
474 183
        $this->useQueryCache = $bool;
475
476 183
        return $this;
477
    }
478
479
    /**
480
     * Returns the cache driver used for query caching.
481
     *
482
     * @return Cache|null The cache driver used for query caching or NULL, if
483
     *                                           this Query does not use query caching.
484
     */
485 759
    public function getQueryCacheDriver()
486
    {
487 759
        if ($this->queryCache) {
488 9
            return $this->queryCache;
489
        }
490
491 750
        return $this->em->getConfiguration()->getQueryCacheImpl();
492
    }
493
494
    /**
495
     * Defines how long the query cache will be active before expire.
496
     *
497
     * @param int $timeToLive How long the cache entry is valid.
498
     *
499
     * @return Query This query instance.
500
     */
501 1
    public function setQueryCacheLifetime($timeToLive)
502
    {
503 1
        if ($timeToLive !== null) {
0 ignored issues
show
introduced by
The condition $timeToLive !== null is always true.
Loading history...
504 1
            $timeToLive = (int) $timeToLive;
505
        }
506
507 1
        $this->queryCacheTTL = $timeToLive;
508
509 1
        return $this;
510
    }
511
512
    /**
513
     * Retrieves the lifetime of resultset cache.
514
     *
515
     * @return int
516
     */
517
    public function getQueryCacheLifetime()
518
    {
519
        return $this->queryCacheTTL;
520
    }
521
522
    /**
523
     * Defines if the query cache is active or not.
524
     *
525
     * @param bool $expire Whether or not to force query cache expiration.
526
     *
527
     * @return Query This query instance.
528
     */
529 7
    public function expireQueryCache($expire = true)
530
    {
531 7
        $this->expireQueryCache = $expire;
532
533 7
        return $this;
534
    }
535
536
    /**
537
     * Retrieves if the query cache is active or not.
538
     *
539
     * @return bool
540
     */
541
    public function getExpireQueryCache()
542
    {
543
        return $this->expireQueryCache;
544
    }
545
546 220
    public function free()
547
    {
548 220
        parent::free();
549
550 220
        $this->dql   = null;
551 220
        $this->state = self::STATE_CLEAN;
552 220
    }
553
554
    /**
555
     * Sets a DQL query string.
556
     *
557
     * @param string $dqlQuery DQL Query.
558
     *
559
     * @return AbstractQuery
560
     */
561 942
    public function setDQL($dqlQuery)
562
    {
563 942
        if ($dqlQuery !== null) {
0 ignored issues
show
introduced by
The condition $dqlQuery !== null is always true.
Loading history...
564 942
            $this->dql   = $dqlQuery;
565 942
            $this->state = self::STATE_DIRTY;
566
        }
567
568 942
        return $this;
569
    }
570
571
    /**
572
     * Returns the DQL query that is represented by this query object.
573
     *
574
     * @return string DQL query.
575
     */
576 889
    public function getDQL()
577
    {
578 889
        return $this->dql;
579
    }
580
581
    /**
582
     * Returns the state of this query object
583
     * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
584
     * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
585
     *
586
     * @see AbstractQuery::STATE_CLEAN
587
     * @see AbstractQuery::STATE_DIRTY
588
     *
589
     * @return int The query state.
590
     */
591
    public function getState()
592
    {
593
        return $this->state;
594
    }
595
596
    /**
597
     * Method to check if an arbitrary piece of DQL exists
598
     *
599
     * @param string $dql Arbitrary piece of DQL to check for.
600
     *
601
     * @return bool
602
     */
603
    public function contains($dql)
604
    {
605
        return stripos($this->getDQL(), $dql) !== false;
606
    }
607
608
    /**
609
     * Sets the position of the first result to retrieve (the "offset").
610
     *
611
     * @param int $firstResult The first result to return.
612
     *
613
     * @return Query This query object.
614
     */
615 219
    public function setFirstResult($firstResult)
616
    {
617 219
        $this->firstResult = $firstResult;
618 219
        $this->state       = self::STATE_DIRTY;
619
620 219
        return $this;
621
    }
622
623
    /**
624
     * Gets the position of the first result the query object was set to retrieve (the "offset").
625
     * Returns NULL if {@link setFirstResult} was not applied to this query.
626
     *
627
     * @return int The position of the first result.
628
     */
629 652
    public function getFirstResult()
630
    {
631 652
        return $this->firstResult;
632
    }
633
634
    /**
635
     * Sets the maximum number of results to retrieve (the "limit").
636
     *
637
     * @param int|null $maxResults
638
     *
639
     * @return Query This query object.
640
     */
641 243
    public function setMaxResults($maxResults)
642
    {
643 243
        $this->maxResults = $maxResults;
644 243
        $this->state      = self::STATE_DIRTY;
645
646 243
        return $this;
647
    }
648
649
    /**
650
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
651
     * Returns NULL if {@link setMaxResults} was not applied to this query.
652
     *
653
     * @return int|null Maximum number of results.
654
     */
655 652
    public function getMaxResults()
656
    {
657 652
        return $this->maxResults;
658
    }
659
660
    /**
661
     * Executes the query and returns an IterableResult that can be used to incrementally
662
     * iterated over the result.
663
     *
664
     * @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters    The query parameters.
665
     * @param int                                            $hydrationMode The hydration mode to use.
666
     *
667
     * @return IterableResult
668
     */
669 10
    public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT)
670
    {
671 10
        $this->setHint(self::HINT_INTERNAL_ITERATION, true);
672
673 10
        return parent::iterate($parameters, $hydrationMode);
674
    }
675
676
    /**
677
     * {@inheritdoc}
678
     */
679 465
    public function setHint($name, $value)
680
    {
681 465
        $this->state = self::STATE_DIRTY;
682
683 465
        return parent::setHint($name, $value);
684
    }
685
686
    /**
687
     * {@inheritdoc}
688
     */
689 355
    public function setHydrationMode($hydrationMode)
690
    {
691 355
        $this->state = self::STATE_DIRTY;
692
693 355
        return parent::setHydrationMode($hydrationMode);
694
    }
695
696
    /**
697
     * Set the lock mode for this Query.
698
     *
699
     * @see \Doctrine\DBAL\LockMode
700
     *
701
     * @param int $lockMode
702
     *
703
     * @return Query
704
     *
705
     * @throws TransactionRequiredException
706
     */
707
    public function setLockMode($lockMode)
708
    {
709
        if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) {
710
            if (! $this->em->getConnection()->isTransactionActive()) {
711
                throw TransactionRequiredException::transactionRequired();
712
            }
713
        }
714
715
        $this->setHint(self::HINT_LOCK_MODE, $lockMode);
716
717
        return $this;
718
    }
719
720
    /**
721
     * Get the current lock mode for this query.
722
     *
723
     * @return int|null The current lock mode of this query or NULL if no specific lock mode is set.
724
     */
725
    public function getLockMode()
726
    {
727
        $lockMode = $this->getHint(self::HINT_LOCK_MODE);
728
729
        if ($lockMode === false) {
730
            return null;
731
        }
732
733
        return $lockMode;
734
    }
735
736
    /**
737
     * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
738
     *
739
     * @return string
740
     */
741 578
    protected function getQueryCacheId()
742
    {
743 578
        ksort($this->hints);
744
745 578
        $platform = $this->getEntityManager()
746 578
            ->getConnection()
747 578
            ->getDatabasePlatform()
748 578
            ->getName();
749
750 578
        return md5(
751 578
            $this->getDQL() . serialize($this->hints) .
752 578
            '&platform=' . $platform .
753 578
            ($this->em->hasFilters() ? $this->em->getFilters()->getHash() : '') .
754 578
            '&firstResult=' . $this->firstResult . '&maxResult=' . $this->maxResults .
755 578
            '&hydrationMode=' . $this->hydrationMode . '&types=' . serialize($this->parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT'
756
        );
757
    }
758
759
    /**
760
     * {@inheritdoc}
761
     */
762 28
    protected function getHash()
763
    {
764 28
        return sha1(parent::getHash() . '-' . $this->firstResult . '-' . $this->maxResults);
765
    }
766
767
    /**
768
     * Cleanup Query resource when clone is called.
769
     */
770 142
    public function __clone()
771
    {
772 142
        parent::__clone();
773
774 142
        $this->state = self::STATE_DIRTY;
775 142
    }
776
}
777