Failed Conditions
Pull Request — master (#6392)
by Alessandro
19:58
created

Query   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 733
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Test Coverage

Coverage 88.48%

Importance

Changes 0
Metric Value
wmc 71
lcom 1
cbo 19
dl 0
loc 733
ccs 169
cts 191
cp 0.8848
rs 1.9849
c 0
b 0
f 0

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