Completed
Pull Request — master (#6417)
by Luís
09:55
created

Query::_doExecute()   D

Complexity

Conditions 10
Paths 24

Size

Total Lines 42
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 10

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 0
loc 42
ccs 21
cts 21
cp 1
rs 4.8196
c 2
b 0
f 0
cc 10
eloc 22
nc 24
nop 0
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Parser;
24
use Doctrine\ORM\Query\ParserResult;
25
use Doctrine\ORM\Query\QueryException;
26
use Doctrine\ORM\Mapping\ClassMetadata;
27
use Doctrine\ORM\Query\ParameterTypeInferer;
28
use Doctrine\Common\Collections\ArrayCollection;
29
30
/**
31
 * A Query object represents a DQL query.
32
 *
33
 * @since   1.0
34
 * @author  Guilherme Blanco <[email protected]>
35
 * @author  Konsta Vesterinen <[email protected]>
36
 * @author  Roman Borschel <[email protected]>
37
 */
38
final class Query extends AbstractQuery
39
{
40
    /**
41
     * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
42
     */
43
    const STATE_CLEAN  = 1;
44
45
    /**
46
     * A query object is in state DIRTY when it has DQL parts that have not yet been
47
     * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
48
     * is called.
49
     */
50
    const STATE_DIRTY = 2;
51
52
    /* Query HINTS */
53
54
    /**
55
     * The refresh hint turns any query into a refresh query with the result that
56
     * any local changes in entities are overridden with the fetched values.
57
     *
58
     * @var string
59
     */
60
    const HINT_REFRESH = 'doctrine.refresh';
61
62
    /**
63
     * @var string
64
     */
65
    const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
66
67
    /**
68
     * @var string
69
     */
70
    const HINT_CACHE_EVICT = 'doctrine.cache.evict';
71
72
    /**
73
     * Internal hint: is set to the proxy entity that is currently triggered for loading
74
     *
75
     * @var string
76
     */
77
    const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
78
79
    /**
80
     * The forcePartialLoad query hint forces a particular query to return
81
     * partial objects.
82
     *
83
     * @var string
84
     * @todo Rename: HINT_OPTIMIZE
85
     */
86
    const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
87
88
    /**
89
     * The includeMetaColumns query hint causes meta columns like foreign keys and
90
     * discriminator columns to be selected and returned as part of the query result.
91
     *
92
     * This hint does only apply to non-object queries.
93
     *
94
     * @var string
95
     */
96
    const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
97
98
    /**
99
     * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
100
     * are iterated and executed after the DQL has been parsed into an AST.
101
     *
102
     * @var string
103
     */
104
    const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
105
106
    /**
107
     * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
108
     * and is used for generating the target SQL from any DQL AST tree.
109
     *
110
     * @var string
111
     */
112
    const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
113
114
    //const HINT_READ_ONLY = 'doctrine.readOnly';
115
116
    /**
117
     * @var string
118
     */
119
    const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
120
121
    /**
122
     * @var string
123
     */
124
    const HINT_LOCK_MODE = 'doctrine.lockMode';
125
126
    /**
127
     * The current state of this query.
128
     *
129
     * @var integer
130
     */
131
    private $_state = self::STATE_CLEAN;
132
133
    /**
134
     * A snapshot of the parameter types the query was parsed with.
135
     *
136
     * @var array
137
     */
138
    private $_parsedTypes = [];
139
140
    /**
141
     * Cached DQL query.
142
     *
143
     * @var string
144
     */
145
    private $_dql = null;
146
147
    /**
148
     * The parser result that holds DQL => SQL information.
149
     *
150
     * @var \Doctrine\ORM\Query\ParserResult
151
     */
152
    private $_parserResult;
153
154
    /**
155
     * The first result to return (the "offset").
156
     *
157
     * @var integer
158
     */
159
    private $_firstResult = null;
160
161
    /**
162
     * The maximum number of results to return (the "limit").
163
     *
164
     * @var integer
165
     */
166
    private $_maxResults = null;
167
168
    /**
169
     * The cache driver used for caching queries.
170
     *
171
     * @var \Doctrine\Common\Cache\Cache|null
172
     */
173
    private $_queryCache;
174
175
    /**
176
     * Whether or not expire the query cache.
177
     *
178
     * @var boolean
179
     */
180
    private $_expireQueryCache = false;
181
182
    /**
183
     * The query cache lifetime.
184
     *
185
     * @var int
186
     */
187
    private $_queryCacheTTL;
188
189
    /**
190
     * Whether to use a query cache, if available. Defaults to TRUE.
191
     *
192
     * @var boolean
193
     */
194
    private $_useQueryCache = true;
195
196
    /**
197
     * Gets the SQL query/queries that correspond to this DQL query.
198
     *
199
     * @return mixed The built sql query or an array of all sql queries.
200
     *
201
     * @override
202
     */
203 332
    public function getSQL()
204
    {
205 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...
206
    }
207
208
    /**
209
     * Returns the corresponding AST for this DQL query.
210
     *
211
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
212
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
213
     *         \Doctrine\ORM\Query\AST\DeleteStatement
214
     */
215 2
    public function getAST()
216
    {
217 2
        $parser = new Parser($this);
218
219 2
        return $parser->getAST();
220
    }
221
222
    /**
223
     * {@inheritdoc}
224
     */
225 419
    protected function getResultSetMapping()
226
    {
227
        // parse query or load from cache
228 419
        if ($this->_resultSetMapping === null) {
229 37
            $this->_resultSetMapping = $this->_parse()->getResultSetMapping();
230
        }
231
232 416
        return $this->_resultSetMapping;
233
    }
234
235
    /**
236
     * Parses the DQL query, if necessary, and stores the parser result.
237
     *
238
     * Note: Populates $this->_parserResult as a side-effect.
239
     *
240
     * @return \Doctrine\ORM\Query\ParserResult
241
     */
242 737
    private function _parse()
243
    {
244 737
        $types = [];
245
246 737
        foreach ($this->parameters as $parameter) {
247
            /** @var Query\Parameter $parameter */
248 163
            $types[$parameter->getName()] = $parameter->getType();
249
        }
250
251
        // Return previous parser result if the query and the filter collection are both clean
252 737
        if ($this->_state === self::STATE_CLEAN && $this->_parsedTypes === $types && $this->_em->isFiltersStateClean()) {
253 39
            return $this->_parserResult;
254
        }
255
256 737
        $this->_state = self::STATE_CLEAN;
257 737
        $this->_parsedTypes = $types;
258
259
        // Check query cache.
260 737
        if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) {
261 172
            $parser = new Parser($this);
262
263 172
            $this->_parserResult = $parser->parse();
264
265 168
            return $this->_parserResult;
266
        }
267
268 565
        $hash   = $this->_getQueryCacheId();
269 565
        $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...
270
271 565
        if ($cached instanceof ParserResult) {
272
            // Cache hit.
273 115
            $this->_parserResult = $cached;
274
275 115
            return $this->_parserResult;
276
        }
277
278
        // Cache miss.
279 517
        $parser = new Parser($this);
280
281 517
        $this->_parserResult = $parser->parse();
282
283 498
        $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL);
284
285 498
        return $this->_parserResult;
286
    }
287
288
    /**
289
     * {@inheritdoc}
290
     */
291 436
    protected function _doExecute()
292
    {
293 436
        $executor = $this->_parse()->getSqlExecutor();
294
295 427
        if ($this->_queryCacheProfile) {
296 7
            $executor->setQueryCacheProfile($this->_queryCacheProfile);
297
        } else {
298 422
            $executor->removeQueryCacheProfile();
299
        }
300
301 427
        if ($this->_resultSetMapping === null) {
302 386
            $this->_resultSetMapping = $this->_parserResult->getResultSetMapping();
303
        }
304
305
        // Prepare parameters
306 427
        $paramMappings = $this->_parserResult->getParameterMappings();
307 427
        $paramCount = count($this->parameters);
308 427
        $mappingCount = count($paramMappings);
309
310 427
        if ($paramCount > $mappingCount) {
311 1
            throw QueryException::tooManyParameters($mappingCount, $paramCount);
312
        }
313
314 426
        if ($paramCount < $mappingCount) {
315 1
            throw QueryException::tooFewParameters($mappingCount, $paramCount);
316
        }
317
318
        // evict all cache for the entity region
319 425
        if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) {
320 2
            $this->evictEntityCacheRegion();
321
        }
322
323 425
        list($sqlParams, $types) = $this->processParameterMappings($paramMappings);
324
325 424
        if ($this->_queryCacheProfile && $this->getExpireResultCache()) {
326 1
            $this->_queryCacheProfile->getResultCacheDriver()->delete(
327 1
                $this->_queryCacheProfile->generateCacheKeys($executor->getSqlStatements(), $sqlParams, $types)[0]
0 ignored issues
show
Documentation introduced by
$executor->getSqlStatements() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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