Completed
Pull Request — master (#6436)
by Luís
12:53 queued 06:03
created

Query::setDQL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
c 0
b 0
f 0
ccs 5
cts 5
cp 1
rs 9.6666
cc 2
eloc 5
nc 2
nop 1
crap 2
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\Driver\Connection;
23
use Doctrine\DBAL\LockMode;
24
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
25
use Doctrine\ORM\Query\Parser;
26
use Doctrine\ORM\Query\ParserResult;
27
use Doctrine\ORM\Query\QueryException;
28
use Doctrine\ORM\Mapping\ClassMetadata;
29
use Doctrine\ORM\Query\ParameterTypeInferer;
30
use Doctrine\Common\Collections\ArrayCollection;
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';
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 332
    public function getSQL()
206
    {
207 332
        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 421
    protected function getResultSetMapping()
228
    {
229
        // parse query or load from cache
230 421
        if ($this->_resultSetMapping === null) {
231 37
            $this->_resultSetMapping = $this->_parse()->getResultSetMapping();
232
        }
233
234 418
        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 739
    private function _parse()
245
    {
246 739
        $types = [];
247
248 739
        foreach ($this->parameters as $parameter) {
249
            /** @var Query\Parameter $parameter */
250 164
            $types[$parameter->getName()] = $parameter->getType();
251
        }
252
253
        // Return previous parser result if the query and the filter collection are both clean
254 739
        if ($this->_state === self::STATE_CLEAN && $this->_parsedTypes === $types && $this->_em->isFiltersStateClean()) {
255 39
            return $this->_parserResult;
256
        }
257
258 739
        $this->_state = self::STATE_CLEAN;
259 739
        $this->_parsedTypes = $types;
260
261
        // Check query cache.
262 739
        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 567
        $hash   = $this->_getQueryCacheId();
271 567
        $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 567
        if ($cached instanceof ParserResult) {
274
            // Cache hit.
275 116
            $this->_parserResult = $cached;
276
277 116
            return $this->_parserResult;
278
        }
279
280
        // Cache miss.
281 519
        $parser = new Parser($this);
282
283 519
        $this->_parserResult = $parser->parse();
284
285 500
        $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
286
287 500
        return $this->_parserResult;
288
    }
289
290
    /**
291
     * {@inheritdoc}
292
     */
293 438
    protected function _doExecute()
294
    {
295 438
        $executor = $this->_parse()->getSqlExecutor();
296
297 429
        if ($this->_queryCacheProfile) {
298 8
            $executor->setQueryCacheProfile($this->_queryCacheProfile);
299
        } else {
300 423
            $executor->removeQueryCacheProfile();
301
        }
302
303 429
        if ($this->_resultSetMapping === null) {
304 388
            $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
305
        }
306
307
        // Prepare parameters
308 429
        $paramMappings = $this->_parserResult->getParameterMappings();
309 429
        $paramCount = count($this->parameters);
310 429
        $mappingCount = count($paramMappings);
311
312 429
        if ($paramCount > $mappingCount) {
313 1
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
314
        }
315
316 428
        if ($paramCount < $mappingCount) {
317 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
318
        }
319
320
        // evict all cache for the entity region
321 427
        if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) {
322 2
            $this->evictEntityCacheRegion();
323
        }
324
325 427
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
326
327 426
        $this->evictResultSetCache(
328
            $executor,
329
            $sqlParams,
330
            $types,
331 426
            $this->_em->getConnection()->getParams()
332
        );
333
334 426
        return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
335
    }
336
337 426
    private function evictResultSetCache(
338
        AbstractSqlExecutor $executor,
339
        array $sqlParams,
340
        array $types,
341
        array $connectionParams
342
    ) {
343 426
        if (null === $this->_queryCacheProfile || ! $this->getExpireResultCache()) {
344 426
            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);
0 ignored issues
show
Unused Code introduced by
The call to QueryCacheProfile::generateCacheKeys() has too many arguments starting with $connectionParams.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

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