Failed Conditions
Pull Request — develop (#6947)
by Filippo
10:01
created

Query::doExecute()   C

Complexity

Conditions 8
Paths 16

Size

Total Lines 42
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 23
nc 16
nop 0
dl 0
loc 42
ccs 23
cts 23
cp 1
crap 8
rs 5.3846
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\DBAL\LockMode;
8
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
9
use Doctrine\ORM\Query\Parser;
10
use Doctrine\ORM\Query\ParserResult;
11
use Doctrine\ORM\Query\QueryException;
12
use Doctrine\ORM\Mapping\ClassMetadata;
13
use Doctrine\ORM\Query\ParameterTypeInferer;
14
use Doctrine\Common\Collections\ArrayCollection;
15
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
16
17
/**
18
 * A Query object represents a DQL query.
19
 *
20
 * @since   1.0
21
 * @author  Guilherme Blanco <[email protected]>
22
 * @author  Konsta Vesterinen <[email protected]>
23
 * @author  Roman Borschel <[email protected]>
24
 */
25
final class Query extends AbstractQuery
26
{
27
    /**
28
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
29
     */
30
    const STATE_CLEAN  = 1;
31
32
    /**
33
     * A query object is in state DIRTY when it has DQL parts that have not yet been
34
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
35
     * is called.
36
     */
37
    const STATE_DIRTY = 2;
38
39
    /* Query HINTS */
40
41
    /**
42
     * The refresh hint turns any query into a refresh query with the result that
43
     * any local changes in entities are overridden with the fetched values.
44
     *
45
     * @var string
46
     */
47
    const HINT_REFRESH = 'doctrine.refresh';
48
49
    /**
50
     * @var string
51
     */
52
    const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
53
54
    /**
55
     * @var string
56
     */
57
    const HINT_CACHE_EVICT = 'doctrine.cache.evict';
58
59
    /**
60
     * Internal hint: is set to the proxy entity that is currently triggered for loading
61
     *
62
     * @var string
63
     */
64
    const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
65
66
    /**
67
     * The forcePartialLoad query hint forces a particular query to return
68
     * partial objects.
69
     *
70
     * @var string
71
     * @todo Rename: HINT_OPTIMIZE
72
     */
73
    const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
74
75
    /**
76
     * The includeMetaColumns query hint causes meta columns like foreign keys and
77
     * discriminator columns to be selected and returned as part of the query result.
78
     *
79
     * This hint does only apply to non-object queries.
80
     *
81
     * @var string
82
     */
83
    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
     * @var string
90
     */
91
    const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
92
93
    /**
94
     * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
95
     * and is used for generating the target SQL from any DQL AST tree.
96
     *
97
     * @var string
98
     */
99
    const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
100
101
    //const HINT_READ_ONLY = 'doctrine.readOnly';
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

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

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