Failed Conditions
Pull Request — master (#6392)
by Alessandro
11:37
created

Query   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 752
Duplicated Lines 2.39 %

Coupling/Cohesion

Components 1
Dependencies 18

Test Coverage

Coverage 89.05%

Importance

Changes 0
Metric Value
wmc 74
lcom 1
cbo 18
dl 18
loc 752
ccs 179
cts 201
cp 0.8905
rs 1.9047
c 0
b 0
f 0

33 Methods

Rating   Name   Duplication   Size   Complexity  
A getSQL() 0 4 1
A getAST() 0 6 1
A getResultSetMapping() 0 9 2
D _parse() 0 45 9
A evictResultSetCache() 0 19 4
A evictEntityCacheRegion() 0 14 3
C _doExecute() 0 43 8
A getAllDiscriminators() 18 18 3
C processParameterMappings() 0 57 12
A setQueryCacheDriver() 0 6 1
A useQueryCache() 0 6 1
A getQueryCacheDriver() 0 8 2
A setQueryCacheLifetime() 0 10 2
A getQueryCacheLifetime() 0 4 1
A expireQueryCache() 0 6 1
A getExpireQueryCache() 0 4 1
A free() 0 7 1
A setDQL() 0 9 2
A getDQL() 0 4 1
A getState() 0 4 1
A contains() 0 4 1
A setFirstResult() 0 7 1
A getFirstResult() 0 4 1
A setMaxResults() 0 7 1
A getMaxResults() 0 4 1
A iterate() 0 6 1
A setHint() 0 6 1
A setHydrationMode() 0 6 1
A setLockMode() 0 12 3
A getLockMode() 0 10 2
A _getQueryCacheId() 0 17 2
A getHash() 0 4 1
A __clone() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM;
21
22
use Doctrine\DBAL\LockMode;
23
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
24
use Doctrine\ORM\Query\Parser;
25
use Doctrine\ORM\Query\ParserResult;
26
use Doctrine\ORM\Query\QueryException;
27
use Doctrine\ORM\Mapping\ClassMetadata;
28
use Doctrine\ORM\Query\ParameterTypeInferer;
29
use Doctrine\Common\Collections\ArrayCollection;
30
31
/**
32
 * A Query object represents a DQL query.
33
 *
34
 * @since   1.0
35
 * @author  Guilherme Blanco <[email protected]>
36
 * @author  Konsta Vesterinen <[email protected]>
37
 * @author  Roman Borschel <[email protected]>
38
 */
39
final class Query extends AbstractQuery
40
{
41
    /**
42
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
43
     */
44
    const STATE_CLEAN  = 1;
45
46
    /**
47
     * A query object is in state DIRTY when it has DQL parts that have not yet been
48
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
49
     * is called.
50
     */
51
    const STATE_DIRTY = 2;
52
53
    /* Query HINTS */
54
55
    /**
56
     * The refresh hint turns any query into a refresh query with the result that
57
     * any local changes in entities are overridden with the fetched values.
58
     *
59
     * @var string
60
     */
61
    const HINT_REFRESH = 'doctrine.refresh';
62
63
    /**
64
     * @var string
65
     */
66
    const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
67
68
    /**
69
     * @var string
70
     */
71
    const HINT_CACHE_EVICT = 'doctrine.cache.evict';
72
73
    /**
74
     * Internal hint: is set to the proxy entity that is currently triggered for loading
75
     *
76
     * @var string
77
     */
78
    const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
79
80
    /**
81
     * The forcePartialLoad query hint forces a particular query to return
82
     * partial objects.
83
     *
84
     * @var string
85
     * @todo Rename: HINT_OPTIMIZE
86
     */
87
    const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
88
89
    /**
90
     * The includeMetaColumns query hint causes meta columns like foreign keys and
91
     * discriminator columns to be selected and returned as part of the query result.
92
     *
93
     * This hint does only apply to non-object queries.
94
     *
95
     * @var string
96
     */
97
    const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
98
99
    /**
100
     * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
101
     * are iterated and executed after the DQL has been parsed into an AST.
102
     *
103
     * @var string
104
     */
105
    const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
106
107
    /**
108
     * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
109
     * and is used for generating the target SQL from any DQL AST tree.
110
     *
111
     * @var string
112
     */
113
    const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
114
115
    //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...
116
117
    /**
118
     * @var string
119
     */
120
    const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
121
122
    /**
123
     * @var string
124
     */
125
    const HINT_LOCK_MODE = 'doctrine.lockMode';
126
127
    /**
128
     * The current state of this query.
129
     *
130
     * @var integer
131
     */
132
    private $_state = self::STATE_CLEAN;
133
134
    /**
135
     * A snapshot of the parameter types the query was parsed with.
136
     *
137
     * @var array
138
     */
139
    private $_parsedTypes = [];
140
141
    /**
142
     * Cached DQL query.
143
     *
144
     * @var string
145
     */
146
    private $_dql = null;
147
148
    /**
149
     * The parser result that holds DQL => SQL information.
150
     *
151
     * @var \Doctrine\ORM\Query\ParserResult
152
     */
153
    private $_parserResult;
154
155
    /**
156
     * The first result to return (the "offset").
157
     *
158
     * @var integer
159
     */
160
    private $_firstResult = null;
161
162
    /**
163
     * The maximum number of results to return (the "limit").
164
     *
165
     * @var integer
166
     */
167
    private $_maxResults = null;
168
169
    /**
170
     * The cache driver used for caching queries.
171
     *
172
     * @var \Doctrine\Common\Cache\Cache|null
173
     */
174
    private $_queryCache;
175
176
    /**
177
     * Whether or not expire the query cache.
178
     *
179
     * @var boolean
180
     */
181
    private $_expireQueryCache = false;
182
183
    /**
184
     * The query cache lifetime.
185
     *
186
     * @var int
187
     */
188
    private $_queryCacheTTL;
189
190
    /**
191
     * Whether to use a query cache, if available. Defaults to TRUE.
192
     *
193
     * @var boolean
194
     */
195
    private $_useQueryCache = true;
196
197
    /**
198
     * Gets the SQL query/queries that correspond to this DQL query.
199
     *
200
     * @return mixed The built sql query or an array of all sql queries.
201
     *
202
     * @override
203
     */
204 335
    public function getSQL()
205
    {
206 335
        return $this->_parse()->getSqlExecutor()->getSqlStatements();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->_parse()->...()->getSqlStatements(); (array) is incompatible with the return type declared by the abstract method Doctrine\ORM\AbstractQuery::getSQL of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
207
    }
208
209
    /**
210
     * Returns the corresponding AST for this DQL query.
211
     *
212
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
213
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
214
     *         \Doctrine\ORM\Query\AST\DeleteStatement
215
     */
216 2
    public function getAST()
217
    {
218 2
        $parser = new Parser($this);
219
220 2
        return $parser->getAST();
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226 434
    protected function getResultSetMapping()
227
    {
228
        // parse query or load from cache
229 434
        if ($this->_resultSetMapping === null) {
230 38
            $this->_resultSetMapping = $this->_parse()->getResultSetMapping();
231
        }
232
233 431
        return $this->_resultSetMapping;
234
    }
235
236
    /**
237
     * Parses the DQL query, if necessary, and stores the parser result.
238
     *
239
     * Note: Populates $this->_parserResult as a side-effect.
240
     *
241
     * @return \Doctrine\ORM\Query\ParserResult
242
     */
243 755
    private function _parse()
244
    {
245 755
        $types = [];
246
247 755
        foreach ($this->parameters as $parameter) {
248
            /** @var Query\Parameter $parameter */
249 171
            $types[$parameter->getName()] = $parameter->getType();
250
        }
251
252
        // Return previous parser result if the query and the filter collection are both clean
253 755
        if ($this->_state === self::STATE_CLEAN && $this->_parsedTypes === $types && $this->_em->isFiltersStateClean()) {
254 39
            return $this->_parserResult;
255
        }
256
257 755
        $this->_state = self::STATE_CLEAN;
258 755
        $this->_parsedTypes = $types;
259
260
        // Check query cache.
261 755
        if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) {
262 172
            $parser = new Parser($this);
263
264 172
            $this->_parserResult = $parser->parse();
265
266 168
            return $this->_parserResult;
267
        }
268
269 583
        $hash   = $this->_getQueryCacheId();
270 583
        $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash);
0 ignored issues
show
Bug introduced by
The variable $queryCache does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
271
272 583
        if ($cached instanceof ParserResult) {
273
            // Cache hit.
274 118
            $this->_parserResult = $cached;
275
276 118
            return $this->_parserResult;
277
        }
278
279
        // Cache miss.
280 533
        $parser = new Parser($this);
281
282 533
        $this->_parserResult = $parser->parse();
283
284 512
        $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
285
286 512
        return $this->_parserResult;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292 451
    protected function _doExecute()
293
    {
294 451
        $executor = $this->_parse()->getSqlExecutor();
295
296 442
        if ($this->_queryCacheProfile) {
297 8
            $executor->setQueryCacheProfile($this->_queryCacheProfile);
298
        } else {
299 436
            $executor->removeQueryCacheProfile();
300
        }
301
302 442
        if ($this->_resultSetMapping === null) {
303 400
            $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
304
        }
305
306
        // Prepare parameters
307 442
        $paramMappings = $this->_parserResult->getParameterMappings();
308 442
        $paramCount = count($this->parameters);
309 442
        $mappingCount = count($paramMappings);
310
311 442
        if ($paramCount > $mappingCount) {
312 1
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
313
        }
314
315 441
        if ($paramCount < $mappingCount) {
316 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
317
        }
318
319
        // evict all cache for the entity region
320 440
        if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) {
321 2
            $this->evictEntityCacheRegion();
322
        }
323
324 440
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
325
326 439
        $this->evictResultSetCache(
327 439
            $executor,
328 439
            $sqlParams,
329 439
            $types,
330 439
            $this->_em->getConnection()->getParams()
331
        );
332
333 439
        return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
334
    }
335
336 439
    private function evictResultSetCache(
337
        AbstractSqlExecutor $executor,
338
        array $sqlParams,
339
        array $types,
340
        array $connectionParams
341
    ) {
342 439
        if (null === $this->_queryCacheProfile || ! $this->getExpireResultCache()) {
343 439
            return;
344
        }
345
346 2
        $cacheDriver = $this->_queryCacheProfile->getResultCacheDriver();
347 2
        $statements  = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
348
349 2
        foreach ($statements as $statement) {
350 2
            $cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
351
352 2
            $cacheDriver->delete(reset($cacheKeys));
0 ignored issues
show
Security Bug introduced by
It seems like reset($cacheKeys) targeting reset() can also be of type false; however, Doctrine\Common\Cache\Cache::delete() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
353
        }
354 2
    }
355
356
    /**
357
     * Evict entity cache region
358
     */
359 2
    private function evictEntityCacheRegion()
360
    {
361 2
        $AST = $this->getAST();
362
363 2
        if ($AST instanceof \Doctrine\ORM\Query\AST\SelectStatement) {
364
            throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
365
        }
366
367 2
        $className = ($AST instanceof \Doctrine\ORM\Query\AST\DeleteStatement)
368 1
            ? $AST->deleteClause->abstractSchemaName
369 2
            : $AST->updateClause->abstractSchemaName;
370
371 2
        $this->_em->getCache()->evictEntityRegion($className);
372 2
    }
373
374 3 View Code Duplication
    private function getAllDiscriminators(ClassMetadata $classMetadata)
375
    {
376
        // FIXME: this code is copied from SqlWalker->getAllDiscriminators()
377 3
        $hierarchyClasses = $classMetadata->subClasses;
378 3
        $hierarchyClasses[] = $classMetadata->name;
379
380 3
        $discriminators = [];
381 3
        foreach ($hierarchyClasses as $class) {
382 3
            $currentMetadata = $this->getEntityManager()->getClassMetadata($class);
383 3
            $currentDiscriminator = $currentMetadata->discriminatorValue;
384
385 3
            if (null !== $currentDiscriminator) {
386 3
                $discriminators[$currentDiscriminator] = null;
387
            }
388
        }
389
390 3
        return $discriminators;
391
    }
392
393
    /**
394
     * Processes query parameter mappings.
395
     *
396
     * @param array $paramMappings
397
     *
398
     * @return array
399
     *
400
     * @throws Query\QueryException
401
     */
402 440
    private function processParameterMappings($paramMappings)
403
    {
404 440
        $sqlParams = [];
405 440
        $types     = [];
406
407 440
        foreach ($this->parameters as $parameter) {
408 159
            $key    = $parameter->getName();
409 159
            $value  = $parameter->getValue();
410 159
            $rsm    = $this->getResultSetMapping();
411
412 159
            if ( ! isset($paramMappings[$key])) {
413 1
                throw QueryException::unknownParameter($key);
414
            }
415
416 158
            if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
417
                $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
418
            }
419
420 158
            if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
421 3
                $value = array_keys($this->getAllDiscriminators($value));
422
            }
423
424 158
            $value = $this->processParameterValue($value);
425 158
            $type  = ($parameter->getValue() === $value)
426 146
                ? $parameter->getType()
427 158
                : ParameterTypeInferer::inferType($value);
428
429 158
            foreach ($paramMappings[$key] as $position) {
430 158
                $types[$position] = $type;
431
            }
432
433 158
            $sqlPositions = $paramMappings[$key];
434
435
            // optimized multi value sql positions away for now,
436
            // they are not allowed in DQL anyways.
437 158
            $value = [$value];
438 158
            $countValue = count($value);
439
440 158
            for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
441 158
                $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
442
            }
443
        }
444
445 439
        if (count($sqlParams) != count($types)) {
446
            throw QueryException::parameterTypeMismatch();
447
        }
448
449 439
        if ($sqlParams) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sqlParams of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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