Failed Conditions
Pull Request — master (#6959)
by Matthew
19:32
created

Query::useQueryCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\DBAL\LockMode;
9
use Doctrine\DBAL\Types\Type;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
use Doctrine\ORM\Query\AST\DeleteStatement;
12
use Doctrine\ORM\Query\AST\SelectStatement;
13
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
14
use Doctrine\ORM\Query\Parameter;
15
use Doctrine\ORM\Query\ParameterTypeInferer;
16
use Doctrine\ORM\Query\Parser;
17
use Doctrine\ORM\Query\ParserResult;
18
use Doctrine\ORM\Query\QueryException;
19
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
20
21
/**
22
 * A Query object represents a DQL query.
23
 *
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
    public 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
    public 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
    public const HINT_REFRESH = 'doctrine.refresh';
48
49
    /**
50
     * @var string
51
     */
52
    public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
53
54
    /**
55
     * @var string
56
     */
57
    public 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
    public 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
    public 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
    public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
84
85
    /**
86
     * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
87
     * are iterated and executed after the DQL has been parsed into an AST.
88
     *
89
     * @var string
90
     */
91
    public 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
    public 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
    public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
107
108
    /**
109
     * @var string
110
     */
111
    public const HINT_LOCK_MODE = 'doctrine.lockMode';
112
113
    /**
114
     * The current state of this query.
115
     *
116
     * @var int
117
     */
118
    private $state = self::STATE_CLEAN;
119
120
    /**
121
     * A snapshot of the parameter types the query was parsed with.
122
     *
123
     * @var mixed[]
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 int
145
     */
146
    private $firstResult;
147
148
    /**
149
     * The maximum number of results to return (the "limit").
150
     *
151
     * @var int|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 bool
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 bool
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 294
    public function getSQL()
191
    {
192 294
        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...
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
    public function getAST()
203
    {
204
        $parser = new Parser($this);
205
206
        return $parser->getAST();
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212 430
    protected function getResultSetMapping()
213
    {
214
        // parse query or load from cache
215 430
        if ($this->resultSetMapping === null) {
216 38
            $this->resultSetMapping = $this->parse()->getResultSetMapping();
217
        }
218
219 425
        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 704
    private function parse()
230
    {
231 704
        $types = [];
232
233 704
        foreach ($this->parameters as $parameter) {
234
            /** @var Query\Parameter $parameter */
235 165
            $types[$parameter->getName()] = $parameter->getType();
236
        }
237
238
        // Return previous parser result if the query and the filter collection are both clean
239 704
        if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->em->isFiltersStateClean()) {
240 35
            return $this->parserResult;
241
        }
242
243 704
        $this->state       = self::STATE_CLEAN;
244 704
        $this->parsedTypes = $types;
245
246
        // Check query cache.
247 704
        $queryCache = $this->getQueryCacheDriver();
248 704
        if (! ($this->useQueryCache && $queryCache)) {
249 181
            $parser = new Parser($this);
250
251 181
            $this->parserResult = $parser->parse();
252
253 177
            return $this->parserResult;
254
        }
255
256 523
        $hash   = $this->getQueryCacheId();
257 523
        $cached = $this->expireQueryCache ? false : $queryCache->fetch($hash);
258
259 523
        if ($cached instanceof ParserResult) {
260
            // Cache hit.
261 106
            $this->parserResult = $cached;
262
263 106
            return $this->parserResult;
264
        }
265
266
        // Cache miss.
267 474
        $parser = new Parser($this);
268
269 474
        $this->parserResult = $parser->parse();
270
271 451
        $queryCache->save($hash, $this->parserResult, $this->queryCacheTTL);
272
273 451
        return $this->parserResult;
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     */
279 438
    protected function doExecute()
280
    {
281 438
        $executor = $this->parse()->getSqlExecutor();
282
283 428
        if ($this->queryCacheProfile) {
284 8
            $executor->setQueryCacheProfile($this->queryCacheProfile);
285
        } else {
286 421
            $executor->removeQueryCacheProfile();
287
        }
288
289 428
        if ($this->resultSetMapping === null) {
290 388
            $this->resultSetMapping = $this->parserResult->getResultSetMapping();
291
        }
292
293
        // Prepare parameters
294 428
        $paramMappings = $this->parserResult->getParameterMappings();
295 428
        $paramCount    = count($this->parameters);
296 428
        $mappingCount  = count($paramMappings);
297
298 428
        if ($paramCount > $mappingCount) {
299 1
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
300
        }
301
302 427
        if ($paramCount < $mappingCount) {
303 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
304
        }
305
306
        // evict all cache for the entity region
307 426
        if ($this->hasCache && isset($this->hints[self::HINT_CACHE_EVICT]) && $this->hints[self::HINT_CACHE_EVICT]) {
308
            $this->evictEntityCacheRegion();
309
        }
310
311 426
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
312
313 425
        $this->evictResultSetCache(
314 425
            $executor,
315 425
            $sqlParams,
316 425
            $types,
317 425
            $this->em->getConnection()->getParams()
318
        );
319
320 425
        return $executor->execute($this->em->getConnection(), $sqlParams, $types);
321
    }
322
323
    /**
324
     * @param mixed[] $sqlParams
325
     * @param mixed[] $types
326
     * @param mixed[] $connectionParams
327
     */
328 425
    private function evictResultSetCache(
329
        AbstractSqlExecutor $executor,
330
        array $sqlParams,
331
        array $types,
332
        array $connectionParams
333
    ) {
334 425
        if ($this->queryCacheProfile === null || ! $this->getExpireResultCache()) {
335 425
            return;
336
        }
337
338 1
        $cacheDriver = $this->queryCacheProfile->getResultCacheDriver();
339 1
        $statements  = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
340
341 1
        foreach ($statements as $statement) {
342 1
            $cacheKeys = $this->queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
343
344 1
            $cacheDriver->delete(reset($cacheKeys));
345
        }
346 1
    }
347
348
    /**
349
     * Evict entity cache region
350
     */
351
    private function evictEntityCacheRegion()
352
    {
353
        $AST = $this->getAST();
354
355
        if ($AST instanceof SelectStatement) {
356
            throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
357
        }
358
359
        $className = ($AST instanceof DeleteStatement)
360
            ? $AST->deleteClause->abstractSchemaName
361
            : $AST->updateClause->abstractSchemaName;
362
363
        $this->em->getCache()->evictEntityRegion($className);
364
    }
365
366
    /**
367
     * Processes query parameter mappings.
368
     *
369
     * @param mixed[] $paramMappings
370
     *
371
     * @return mixed[][]
372
     *
373
     * @throws Query\QueryException
374
     */
375 426
    private function processParameterMappings($paramMappings)
376
    {
377 426
        $sqlParams = [];
378 426
        $types     = [];
379
380 426
        foreach ($this->parameters as $parameter) {
381 151
            $key   = $parameter->getName();
382 151
            $value = $parameter->getValue();
383 151
            $rsm   = $this->getResultSetMapping();
384
385 151
            if (! isset($paramMappings[$key])) {
386 1
                throw QueryException::unknownParameter($key);
387
            }
388
389 150
            if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
390
                $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

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