Failed Conditions
Pull Request — master (#6735)
by Matthias
19:41
created

Query::_parse()   D

Complexity

Conditions 9
Paths 12

Size

Total Lines 45
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 45
c 0
b 0
f 0
ccs 21
cts 21
cp 1
rs 4.909
cc 9
eloc 21
nc 12
nop 0
crap 9
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|null
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 441
    protected function getResultSetMapping()
228
    {
229
        // parse query or load from cache
230 441
        if ($this->_resultSetMapping === null) {
231 38
            $this->_resultSetMapping = $this->_parse()->getResultSetMapping();
232
        }
233
234 438
        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 762
    private function _parse()
245
    {
246 762
        $types = [];
247
248 762
        foreach ($this->parameters as $parameter) {
249
            /** @var Query\Parameter $parameter */
250 175
            $types[$parameter->getName()] = $parameter->getType();
251
        }
252
253
        // Return previous parser result if the query and the filter collection are both clean
254 762
        if ($this->_state === self::STATE_CLEAN && $this->_parsedTypes === $types && $this->_em->isFiltersStateClean()) {
255 39
            return $this->_parserResult;
256
        }
257
258 762
        $this->_state = self::STATE_CLEAN;
259 762
        $this->_parsedTypes = $types;
260
261
        // Check query cache.
262 762
        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 590
        $hash   = $this->_getQueryCacheId();
271 590
        $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 590
        if ($cached instanceof ParserResult) {
274
            // Cache hit.
275 121
            $this->_parserResult = $cached;
276
277 121
            return $this->_parserResult;
278
        }
279
280
        // Cache miss.
281 539
        $parser = new Parser($this);
282
283 539
        $this->_parserResult = $parser->parse();
284
285 518
        $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
286
287 518
        return $this->_parserResult;
288
    }
289
290
    /**
291
     * {@inheritdoc}
292
     */
293 458
    protected function _doExecute()
294
    {
295 458
        $executor = $this->_parse()->getSqlExecutor();
296
297 449
        if ($this->_queryCacheProfile) {
298 8
            $executor->setQueryCacheProfile($this->_queryCacheProfile);
299
        } else {
300 443
            $executor->removeQueryCacheProfile();
301
        }
302
303 449
        if ($this->_resultSetMapping === null) {
304 407
            $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
305
        }
306
307
        // Prepare parameters
308 449
        $paramMappings = $this->_parserResult->getParameterMappings();
309 449
        $paramCount = count($this->parameters);
310 449
        $mappingCount = count($paramMappings);
311
312 449
        if ($paramCount > $mappingCount) {
313 1
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
314
        }
315
316 448
        if ($paramCount < $mappingCount) {
317 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
318
        }
319
320
        // evict all cache for the entity region
321 447
        if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) {
322 2
            $this->evictEntityCacheRegion();
323
        }
324
325 447
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
326
327 446
        $this->evictResultSetCache(
328 446
            $executor,
329 446
            $sqlParams,
330 446
            $types,
331 446
            $this->_em->getConnection()->getParams()
332
        );
333
334 446
        return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
335
    }
336
337 446
    private function evictResultSetCache(
338
        AbstractSqlExecutor $executor,
339
        array $sqlParams,
340
        array $types,
341
        array $connectionParams
342
    ) {
343 446
        if (null === $this->_queryCacheProfile || ! $this->getExpireResultCache()) {
344 446
            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 447
    private function processParameterMappings($paramMappings)
385
    {
386 447
        $sqlParams = [];
387 447
        $types     = [];
388
389 447
        foreach ($this->parameters as $parameter) {
390 163
            $key    = $parameter->getName();
391 163
            $value  = $parameter->getValue();
392 163
            $rsm    = $this->getResultSetMapping();
393 163
            $type   = null;
394
395 163
            if ( ! isset($paramMappings[$key])) {
396 1
                throw QueryException::unknownParameter($key);
397
            }
398
399 162
            if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) {
400
                $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
401
            }
402
403 162
            if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) {
404 3
                $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
405
            }
406
407 162
            if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(get_class($value))) {
408 11
                $metadata = $this->_em->getClassMetadata(get_class($value));
409 11
                $type = $metadata->getTypeOfField($metadata->getIdentifier()[0]);
410
            }
411
412 162
            $value = $this->processParameterValue($value);
413 162
            if (null === $type) {
414 155
                $type  = ($parameter->getValue() === $value)
415 149
                    ? $parameter->getType()
416 155
                    : ParameterTypeInferer::inferType($value);
417
            }
418
419 162
            foreach ($paramMappings[$key] as $position) {
420 162
                $types[$position] = $type;
421
            }
422
423 162
            $sqlPositions = $paramMappings[$key];
424
425
            // optimized multi value sql positions away for now,
426
            // they are not allowed in DQL anyways.
427 162
            $value = [$value];
428 162
            $countValue = count($value);
429
430 162
            for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
431 162
                $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)];
432
            }
433
        }
434
435 446
        if (count($sqlParams) != count($types)) {
436
            throw QueryException::parameterTypeMismatch();
437
        }
438
439 446
        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...
440 162
            ksort($sqlParams);
441 162
            $sqlParams = array_values($sqlParams);
442
443 162
            ksort($types);
444 162
            $types = array_values($types);
445
        }
446
447 446
        return [$sqlParams, $types];
448
    }
449
450
    /**
451
     * Defines a cache driver to be used for caching queries.
452
     *
453
     * @param \Doctrine\Common\Cache\Cache|null $queryCache Cache driver.
454
     *
455
     * @return Query This query instance.
456
     */
457 6
    public function setQueryCacheDriver($queryCache)
458
    {
459 6
        $this->_queryCache = $queryCache;
460
461 6
        return $this;
462
    }
463
464
    /**
465
     * Defines whether the query should make use of a query cache, if available.
466
     *
467
     * @param boolean $bool
468
     *
469
     * @return Query This query instance.
470
     */
471 175
    public function useQueryCache($bool)
472
    {
473 175
        $this->_useQueryCache = $bool;
474
475 175
        return $this;
476
    }
477
478
    /**
479
     * Returns the cache driver used for query caching.
480
     *
481
     * @return \Doctrine\Common\Cache\Cache|null The cache driver used for query caching or NULL, if
482
     *                                           this Query does not use query caching.
483
     */
484 590
    public function getQueryCacheDriver()
485
    {
486 590
        if ($this->_queryCache) {
487 9
            return $this->_queryCache;
488
        }
489
490 581
        return $this->_em->getConfiguration()->getQueryCacheImpl();
491
    }
492
493
    /**
494
     * Defines how long the query cache will be active before expire.
495
     *
496
     * @param integer $timeToLive How long the cache entry is valid.
497
     *
498
     * @return Query This query instance.
499
     */
500 1
    public function setQueryCacheLifetime($timeToLive)
501
    {
502 1
        if ($timeToLive !== null) {
503 1
            $timeToLive = (int) $timeToLive;
504
        }
505
506 1
        $this->_queryCacheTTL = $timeToLive;
507
508 1
        return $this;
509
    }
510
511
    /**
512
     * Retrieves the lifetime of resultset cache.
513
     *
514
     * @return int
515
     */
516
    public function getQueryCacheLifetime()
517
    {
518
        return $this->_queryCacheTTL;
519
    }
520
521
    /**
522
     * Defines if the query cache is active or not.
523
     *
524
     * @param boolean $expire Whether or not to force query cache expiration.
525
     *
526
     * @return Query This query instance.
527
     */
528 7
    public function expireQueryCache($expire = true)
529
    {
530 7
        $this->_expireQueryCache = $expire;
531
532 7
        return $this;
533
    }
534
535
    /**
536
     * Retrieves if the query cache is active or not.
537
     *
538
     * @return bool
539
     */
540
    public function getExpireQueryCache()
541
    {
542
        return $this->_expireQueryCache;
543
    }
544
545
    /**
546
     * @override
547
     */
548 211
    public function free()
549
    {
550 211
        parent::free();
551
552 211
        $this->_dql = null;
553 211
        $this->_state = self::STATE_CLEAN;
554 211
    }
555
556
    /**
557
     * Sets a DQL query string.
558
     *
559
     * @param string $dqlQuery DQL Query.
560
     *
561
     * @return \Doctrine\ORM\AbstractQuery
562
     */
563 945
    public function setDQL($dqlQuery)
564
    {
565 945
        if ($dqlQuery !== null) {
566 945
            $this->_dql = $dqlQuery;
567 945
            $this->_state = self::STATE_DIRTY;
568
        }
569
570 945
        return $this;
571
    }
572
573
    /**
574
     * Returns the DQL query that is represented by this query object.
575
     *
576
     * @return string DQL query.
577
     */
578 897
    public function getDQL()
579
    {
580 897
        return $this->_dql;
581
    }
582
583
    /**
584
     * Returns the state of this query object
585
     * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
586
     * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
587
     *
588
     * @see AbstractQuery::STATE_CLEAN
589
     * @see AbstractQuery::STATE_DIRTY
590
     *
591
     * @return integer The query state.
592
     */
593
    public function getState()
594
    {
595
        return $this->_state;
596
    }
597
598
    /**
599
     * Method to check if an arbitrary piece of DQL exists
600
     *
601
     * @param string $dql Arbitrary piece of DQL to check for.
602
     *
603
     * @return boolean
604
     */
605
    public function contains($dql)
606
    {
607
        return stripos($this->getDQL(), $dql) !== false;
608
    }
609
610
    /**
611
     * Sets the position of the first result to retrieve (the "offset").
612
     *
613
     * @param integer $firstResult The first result to return.
614
     *
615
     * @return Query This query object.
616
     */
617 220
    public function setFirstResult($firstResult)
618
    {
619 220
        $this->_firstResult = $firstResult;
620 220
        $this->_state       = self::STATE_DIRTY;
621
622 220
        return $this;
623
    }
624
625
    /**
626
     * Gets the position of the first result the query object was set to retrieve (the "offset").
627
     * Returns NULL if {@link setFirstResult} was not applied to this query.
628
     *
629
     * @return integer The position of the first result.
630
     */
631 647
    public function getFirstResult()
632
    {
633 647
        return $this->_firstResult;
634
    }
635
636
    /**
637
     * Sets the maximum number of results to retrieve (the "limit").
638
     *
639
     * @param integer|null $maxResults
640
     *
641
     * @return Query This query object.
642
     */
643 229
    public function setMaxResults($maxResults)
644
    {
645 229
        $this->_maxResults = $maxResults;
646 229
        $this->_state      = self::STATE_DIRTY;
647
648 229
        return $this;
649
    }
650
651
    /**
652
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
653
     * Returns NULL if {@link setMaxResults} was not applied to this query.
654
     *
655
     * @return integer|null Maximum number of results.
656
     */
657 647
    public function getMaxResults()
658
    {
659 647
        return $this->_maxResults;
660
    }
661
662
    /**
663
     * Executes the query and returns an IterableResult that can be used to incrementally
664
     * iterated over the result.
665
     *
666
     * @param ArrayCollection|array|null $parameters    The query parameters.
667
     * @param integer                    $hydrationMode The hydration mode to use.
668
     *
669
     * @return \Doctrine\ORM\Internal\Hydration\IterableResult
670
     */
671 10
    public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT)
672
    {
673 10
        $this->setHint(self::HINT_INTERNAL_ITERATION, true);
674
675 10
        return parent::iterate($parameters, $hydrationMode);
676
    }
677
678
    /**
679
     * {@inheritdoc}
680
     */
681 461
    public function setHint($name, $value)
682
    {
683 461
        $this->_state = self::STATE_DIRTY;
684
685 461
        return parent::setHint($name, $value);
686
    }
687
688
    /**
689
     * {@inheritdoc}
690
     */
691 355
    public function setHydrationMode($hydrationMode)
692
    {
693 355
        $this->_state = self::STATE_DIRTY;
694
695 355
        return parent::setHydrationMode($hydrationMode);
696
    }
697
698
    /**
699
     * Set the lock mode for this Query.
700
     *
701
     * @see \Doctrine\DBAL\LockMode
702
     *
703
     * @param int $lockMode
704
     *
705
     * @return Query
706
     *
707
     * @throws TransactionRequiredException
708
     */
709
    public function setLockMode($lockMode)
710
    {
711
        if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) {
712
            if ( ! $this->_em->getConnection()->isTransactionActive()) {
713
                throw TransactionRequiredException::transactionRequired();
714
            }
715
        }
716
717
        $this->setHint(self::HINT_LOCK_MODE, $lockMode);
718
719
        return $this;
720
    }
721
722
    /**
723
     * Get the current lock mode for this query.
724
     *
725
     * @return int|null The current lock mode of this query or NULL if no specific lock mode is set.
726
     */
727
    public function getLockMode()
728
    {
729
        $lockMode = $this->getHint(self::HINT_LOCK_MODE);
730
731
        if (false === $lockMode) {
732
            return null;
733
        }
734
735
        return $lockMode;
736
    }
737
738
    /**
739
     * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
740
     *
741
     * @return string
742
     */
743 590
    protected function _getQueryCacheId()
744
    {
745 590
        ksort($this->_hints);
746
747 590
        $platform = $this->getEntityManager()
748 590
            ->getConnection()
749 590
            ->getDatabasePlatform()
750 590
            ->getName();
751
752 590
        return md5(
753 590
            $this->getDQL() . serialize($this->_hints) .
754 590
            '&platform=' . $platform .
755 590
            ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
756 590
            '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults .
757 590
            '&hydrationMode=' . $this->_hydrationMode . '&types=' . serialize($this->_parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT'
758
        );
759
    }
760
761
     /**
762
     * {@inheritdoc}
763
     */
764 28
    protected function getHash()
765
    {
766 28
        return sha1(parent::getHash(). '-'. $this->_firstResult . '-' . $this->_maxResults);
767
    }
768
769
    /**
770
     * Cleanup Query resource when clone is called.
771
     *
772
     * @return void
773
     */
774 136
    public function __clone()
775
    {
776 136
        parent::__clone();
777
778 136
        $this->_state = self::STATE_DIRTY;
779 136
    }
780
}
781