Completed
Pull Request — master (#6359)
by Sebastiaan
22:14
created

QueryBuilder::getCacheRegion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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\Common\Collections\ArrayCollection;
23
use Doctrine\Common\Collections\Criteria;
24
25
use Doctrine\ORM\Query\Expr;
26
use Doctrine\ORM\Query\QueryExpressionVisitor;
27
28
/**
29
 * This class is responsible for building DQL query strings via an object oriented
30
 * PHP interface.
31
 *
32
 * @since 2.0
33
 * @author Guilherme Blanco <[email protected]>
34
 * @author Jonathan Wage <[email protected]>
35
 * @author Roman Borschel <[email protected]>
36
 */
37
class QueryBuilder
38
{
39
    /* The query types. */
40
    const SELECT = 0;
41
    const DELETE = 1;
42
    const UPDATE = 2;
43
44
    /* The builder states. */
45
    const STATE_DIRTY = 0;
46
    const STATE_CLEAN = 1;
47
48
    /**
49
     * The EntityManager used by this QueryBuilder.
50
     *
51
     * @var EntityManagerInterface
52
     */
53
    private $_em;
54
55
    /**
56
     * The array of DQL parts collected.
57
     *
58
     * @var array
59
     */
60
    private $_dqlParts = [
61
        'distinct' => false,
62
        'select'  => [],
63
        'from'    => [],
64
        'join'    => [],
65
        'set'     => [],
66
        'where'   => null,
67
        'groupBy' => [],
68
        'having'  => null,
69
        'orderBy' => []
70
    ];
71
72
    /**
73
     * The type of query this is. Can be select, update or delete.
74
     *
75
     * @var integer
76
     */
77
    private $_type = self::SELECT;
78
79
    /**
80
     * The state of the query object. Can be dirty or clean.
81
     *
82
     * @var integer
83
     */
84
    private $_state = self::STATE_CLEAN;
85
86
    /**
87
     * The complete DQL string for this query.
88
     *
89
     * @var string
90
     */
91
    private $_dql;
92
93
    /**
94
     * The query parameters.
95
     *
96
     * @var \Doctrine\Common\Collections\ArrayCollection
97
     */
98
    private $parameters;
99
100
    /**
101
     * The index of the first result to retrieve.
102
     *
103
     * @var integer
104
     */
105
    private $_firstResult = null;
106
107
    /**
108
     * The maximum number of results to retrieve.
109
     *
110
     * @var integer
111
     */
112
    private $_maxResults = null;
113
114
    /**
115
     * Keeps root entity alias names for join entities.
116
     *
117
     * @var array
118
     */
119
    private $joinRootAliases = [];
120
121
    /**
122
     * The map of query hints.
123
     *
124
     * @var array
125
     */
126
    private $_hints = [];
127
128
     /**
129
     * Whether to use second level cache, if available.
130
     *
131
     * @var boolean
132
     */
133
    protected $cacheable = false;
134
135
    /**
136
     * Second level cache region name.
137
     *
138
     * @var string|null
139
     */
140
    protected $cacheRegion;
141
142
    /**
143
     * Second level query cache mode.
144
     *
145
     * @var integer|null
146
     */
147
    protected $cacheMode;
148
149
    /**
150
     * @var integer
151
     */
152
    protected $lifetime = 0;
153
154
    /**
155
     * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
156
     *
157
     * @param EntityManagerInterface $em The EntityManager to use.
158
     */
159 117
    public function __construct(EntityManagerInterface $em)
160
    {
161 117
        $this->_em = $em;
162 117
        $this->parameters = new ArrayCollection();
163 117
    }
164
165
    /**
166
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
167
     * This producer method is intended for convenient inline usage. Example:
168
     *
169
     * <code>
170
     *     $qb = $em->createQueryBuilder();
171
     *     $qb
172
     *         ->select('u')
173
     *         ->from('User', 'u')
174
     *         ->where($qb->expr()->eq('u.id', 1));
175
     * </code>
176
     *
177
     * For more complex expression construction, consider storing the expression
178
     * builder object in a local variable.
179
     *
180
     * @return Query\Expr
181
     */
182 11
    public function expr()
183
    {
184 11
        return $this->_em->getExpressionBuilder();
185
    }
186
187
    /**
188
     *
189
     * Enable/disable second level query (result) caching for this query.
190
     *
191
     * @param boolean $cacheable
192
     *
193
     * @return self
194
     */
195 1
    public function setCacheable($cacheable)
196
    {
197 1
        $this->cacheable = (boolean) $cacheable;
198
199 1
        return $this;
200
    }
201
202
    /**
203
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
204
     */
205 1
    public function isCacheable()
206
    {
207 1
        return $this->cacheable;
208
    }
209
210
    /**
211
     * @param string $cacheRegion
212
     *
213
     * @return self
214
     */
215 1
    public function setCacheRegion($cacheRegion)
216
    {
217 1
        $this->cacheRegion = (string) $cacheRegion;
218
219 1
        return $this;
220
    }
221
222
    /**
223
    * Obtain the name of the second level query cache region in which query results will be stored
224
    *
225
    * @return string|null The cache region name; NULL indicates the default region.
226
    */
227 1
    public function getCacheRegion()
228
    {
229 1
        return $this->cacheRegion;
230
    }
231
232
    /**
233
     * @return integer
234
     */
235 1
    public function getLifetime()
236
    {
237 1
        return $this->lifetime;
238
    }
239
240
    /**
241
     * Sets the life-time for this query into second level cache.
242
     *
243
     * @param integer $lifetime
244
     *
245
     * @return self
246
     */
247 1
    public function setLifetime($lifetime)
248
    {
249 1
        $this->lifetime = (integer) $lifetime;
250
251 1
        return $this;
252
    }
253
254
    /**
255
     * @return integer
256
     */
257 1
    public function getCacheMode()
258
    {
259 1
        return $this->cacheMode;
260
    }
261
262
    /**
263
     * @param integer $cacheMode
264
     *
265
     * @return self
266
     */
267 1
    public function setCacheMode($cacheMode)
268
    {
269 1
        $this->cacheMode = (integer) $cacheMode;
270
271 1
        return $this;
272
    }
273
274
    /**
275
     * Sets a query hint.
276
     *
277
     * @param string $name  The name of the hint.
278
     * @param mixed  $value The value of the hint.
279
     *
280
     * @return self
281
     */
282 1
    public function setHint($name, $value)
283
    {
284 1
        $this->_hints[$name] = $value;
285
286 1
        return $this;
287
    }
288
289
    /**
290
     * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
291
     *
292
     * @param string $name The name of the hint.
293
     *
294
     * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
295
     */
296 1
    public function getHint($name)
297
    {
298 1
        return $this->_hints[$name] ?? false;
299
    }
300
301
    /**
302
     * Check if the query has a hint
303
     *
304
     * @param string $name The name of the hint
305
     *
306
     * @return bool False if the query does not have any hint
307
     */
308 1
    public function hasHint($name)
309
    {
310 1
        return isset($this->_hints[$name]);
311
    }
312
313
    /**
314
     * Return the key value map of query hints that are currently set.
315
     *
316
     * @return array
317
     */
318 1
    public function getHints()
319
    {
320 1
        return $this->_hints;
321
    }
322
323
    /**
324
     * Gets the type of the currently built query.
325
     *
326
     * @return integer
327
     */
328 4
    public function getType()
329
    {
330 4
        return $this->_type;
331
    }
332
333
    /**
334
     * Gets the associated EntityManager for this query builder.
335
     *
336
     * @return EntityManager
337
     */
338 1
    public function getEntityManager()
339
    {
340 1
        return $this->_em;
341
    }
342
343
    /**
344
     * Gets the state of this query builder instance.
345
     *
346
     * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
347
     */
348 2
    public function getState()
349
    {
350 2
        return $this->_state;
351
    }
352
353
    /**
354
     * Gets the complete DQL string formed by the current specifications of this QueryBuilder.
355
     *
356
     * <code>
357
     *     $qb = $em->createQueryBuilder()
358
     *         ->select('u')
359
     *         ->from('User', 'u');
360
     *     echo $qb->getDql(); // SELECT u FROM User u
361
     * </code>
362
     *
363
     * @return string The DQL query string.
364
     */
365 81
    public function getDQL()
366
    {
367 81
        if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
368 44
            return $this->_dql;
369
        }
370
371 81
        switch ($this->_type) {
372 81
            case self::DELETE:
373 1
                $dql = $this->_getDQLForDelete();
374 1
                break;
375
376 80
            case self::UPDATE:
377 2
                $dql = $this->_getDQLForUpdate();
378 2
                break;
379
380 78
            case self::SELECT:
381
            default:
382 78
                $dql = $this->_getDQLForSelect();
383 78
                break;
384
        }
385
386 81
        $this->_state = self::STATE_CLEAN;
387 81
        $this->_dql   = $dql;
388
389 81
        return $dql;
390
    }
391
392
    /**
393
     * Constructs a Query instance from the current specifications of the builder.
394
     *
395
     * <code>
396
     *     $qb = $em->createQueryBuilder()
397
     *         ->select('u')
398
     *         ->from('User', 'u');
399
     *     $q = $qb->getQuery();
400
     *     $results = $q->execute();
401
     * </code>
402
     *
403
     * @return Query
404
     */
405 70
    public function getQuery()
406
    {
407 70
        $parameters = clone $this->parameters;
408 70
        $query      = $this->_em->createQuery($this->getDQL())
409 70
            ->setParameters($parameters)
410 70
            ->setFirstResult($this->_firstResult)
411 70
            ->setMaxResults($this->_maxResults);
412
413 70
        if ($this->lifetime) {
414 1
            $query->setLifetime($this->lifetime);
415
        }
416
417 70
        if ($this->cacheMode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheMode of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
418 1
            $query->setCacheMode($this->cacheMode);
419
        }
420
421 70
        if ($this->cacheable) {
422 1
            $query->setCacheable($this->cacheable);
423
        }
424
425 70
        if ($this->cacheRegion) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheRegion of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
426 1
            $query->setCacheRegion($this->cacheRegion);
427
        }
428
429 70
        if ($this->_hints) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_hints 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...
430 1
            foreach ($this->_hints as $name => $value) {
431 1
                $query->setHint($name, $value);
432
            }
433
        }
434
435 70
        return $query;
436
    }
437
438
    /**
439
     * Finds the root entity alias of the joined entity.
440
     *
441
     * @param string $alias       The alias of the new join entity
442
     * @param string $parentAlias The parent entity alias of the join relationship
443
     *
444
     * @return string
445
     */
446 29
    private function findRootAlias($alias, $parentAlias)
447
    {
448 29
        $rootAlias = null;
0 ignored issues
show
Unused Code introduced by
$rootAlias is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
449
450 29
        if (in_array($parentAlias, $this->getRootAliases())) {
451 29
            $rootAlias = $parentAlias;
452 6
        } elseif (isset($this->joinRootAliases[$parentAlias])) {
453 6
            $rootAlias = $this->joinRootAliases[$parentAlias];
454
        } else {
455
            // Should never happen with correct joining order. Might be
456
            // thoughtful to throw exception instead.
457
            $rootAlias = $this->getRootAlias();
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated with message: Please use $qb->getRootAliases() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
458
        }
459
460 29
        $this->joinRootAliases[$alias] = $rootAlias;
461
462 29
        return $rootAlias;
463
    }
464
465
    /**
466
     * Gets the FIRST root alias of the query. This is the first entity alias involved
467
     * in the construction of the query.
468
     *
469
     * <code>
470
     * $qb = $em->createQueryBuilder()
471
     *     ->select('u')
472
     *     ->from('User', 'u');
473
     *
474
     * echo $qb->getRootAlias(); // u
475
     * </code>
476
     *
477
     * @deprecated Please use $qb->getRootAliases() instead.
478
     * @throws \RuntimeException
479
     *
480
     * @return string
481
     */
482 3
    public function getRootAlias()
483
    {
484 3
        $aliases = $this->getRootAliases();
485
486 3
        if ( ! isset($aliases[0])) {
487
            throw new \RuntimeException('No alias was set before invoking getRootAlias().');
488
        }
489
490 3
        return $aliases[0];
491
    }
492
493
    /**
494
     * Gets the root aliases of the query. This is the entity aliases involved
495
     * in the construction of the query.
496
     *
497
     * <code>
498
     *     $qb = $em->createQueryBuilder()
499
     *         ->select('u')
500
     *         ->from('User', 'u');
501
     *
502
     *     $qb->getRootAliases(); // array('u')
503
     * </code>
504
     *
505
     * @return array
506
     */
507 45
    public function getRootAliases()
508
    {
509 45
        $aliases = [];
510
511 45
        foreach ($this->_dqlParts['from'] as &$fromClause) {
512 45
            if (is_string($fromClause)) {
513
                $spacePos = strrpos($fromClause, ' ');
514
                $from     = substr($fromClause, 0, $spacePos);
515
                $alias    = substr($fromClause, $spacePos + 1);
516
517
                $fromClause = new Query\Expr\From($from, $alias);
518
            }
519
520 45
            $aliases[] = $fromClause->getAlias();
521
        }
522
523 45
        return $aliases;
524
    }
525
526
    /**
527
     * Gets all the aliases that have been used in the query.
528
     * Including all select root aliases and join aliases
529
     *
530
     * <code>
531
     *     $qb = $em->createQueryBuilder()
532
     *         ->select('u')
533
     *         ->from('User', 'u')
534
     *         ->join('u.articles','a');
535
     *
536
     *     $qb->getAllAliases(); // array('u','a')
537
     * </code>
538
     * @return array
539
     */
540 15
    public function getAllAliases()
541
    {
542 15
        return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases));
543
    }
544
545
    /**
546
     * Gets the root entities of the query. This is the entity aliases involved
547
     * in the construction of the query.
548
     *
549
     * <code>
550
     *     $qb = $em->createQueryBuilder()
551
     *         ->select('u')
552
     *         ->from('User', 'u');
553
     *
554
     *     $qb->getRootEntities(); // array('User')
555
     * </code>
556
     *
557
     * @return array
558
     */
559 1
    public function getRootEntities()
560
    {
561 1
        $entities = [];
562
563 1
        foreach ($this->_dqlParts['from'] as &$fromClause) {
564 1
            if (is_string($fromClause)) {
565
                $spacePos = strrpos($fromClause, ' ');
566
                $from     = substr($fromClause, 0, $spacePos);
567
                $alias    = substr($fromClause, $spacePos + 1);
568
569
                $fromClause = new Query\Expr\From($from, $alias);
570
            }
571
572 1
            $entities[] = $fromClause->getFrom();
573
        }
574
575 1
        return $entities;
576
    }
577
578
    /**
579
     * Sets a query parameter for the query being constructed.
580
     *
581
     * <code>
582
     *     $qb = $em->createQueryBuilder()
583
     *         ->select('u')
584
     *         ->from('User', 'u')
585
     *         ->where('u.id = :user_id')
586
     *         ->setParameter('user_id', 1);
587
     * </code>
588
     *
589
     * @param string|integer $key   The parameter position or name.
590
     * @param mixed          $value The parameter value.
591
     * @param string|null    $type  PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
592
     *
593
     * @return self
594
     */
595 8
    public function setParameter($key, $value, $type = null)
596
    {
597 8
        $filteredParameters = $this->parameters->filter(
598
            function ($parameter) use ($key)
599
            {
600
                /* @var Query\Parameter $parameter */
601
                // Must not be identical because of string to integer conversion
602 2
                return ($key == $parameter->getName());
603 8
            }
604
        );
605
606 8
        if (count($filteredParameters)) {
607
            /* @var Query\Parameter $parameter */
608
            $parameter = $filteredParameters->first();
609
            $parameter->setValue($value, $type);
610
611
            return $this;
612
        }
613
614 8
        $parameter = new Query\Parameter($key, $value, $type);
615
616 8
        $this->parameters->add($parameter);
617
618 8
        return $this;
619
    }
620
621
    /**
622
     * Sets a collection of query parameters for the query being constructed.
623
     *
624
     * <code>
625
     *     $qb = $em->createQueryBuilder()
626
     *         ->select('u')
627
     *         ->from('User', 'u')
628
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
629
     *         ->setParameters(new ArrayCollection(array(
630
     *             new Parameter('user_id1', 1),
631
     *             new Parameter('user_id2', 2)
632
     *        )));
633
     * </code>
634
     *
635
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters to set.
636
     *
637
     * @return self
638
     */
639 4
    public function setParameters($parameters)
640
    {
641
        // BC compatibility with 2.3-
642 4
        if (is_array($parameters)) {
643 1
            $parameterCollection = new ArrayCollection();
644
645 1
            foreach ($parameters as $key => $value) {
646 1
                $parameter = new Query\Parameter($key, $value);
647
648 1
                $parameterCollection->add($parameter);
649
            }
650
651 1
            $parameters = $parameterCollection;
652
        }
653
654 4
        $this->parameters = $parameters;
655
656 4
        return $this;
657
    }
658
659
    /**
660
     * Gets all defined query parameters for the query being constructed.
661
     *
662
     * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
663
     */
664 2
    public function getParameters()
665
    {
666 2
        return $this->parameters;
667
    }
668
669
    /**
670
     * Gets a (previously set) query parameter of the query being constructed.
671
     *
672
     * @param mixed $key The key (index or name) of the bound parameter.
673
     *
674
     * @return Query\Parameter|null The value of the bound parameter.
675
     */
676 12
    public function getParameter($key)
677
    {
678 12
        $filteredParameters = $this->parameters->filter(
679 12
            function ($parameter) use ($key)
680
            {
681
                /* @var Query\Parameter $parameter */
682
                // Must not be identical because of string to integer conversion
683 12
                return ($key == $parameter->getName());
684 12
            }
685
        );
686
687 12
        return count($filteredParameters) ? $filteredParameters->first() : null;
688
    }
689
690
    /**
691
     * Sets the position of the first result to retrieve (the "offset").
692
     *
693
     * @param integer $firstResult The first result to return.
694
     *
695
     * @return self
696
     */
697 2
    public function setFirstResult($firstResult)
698
    {
699 2
        $this->_firstResult = $firstResult;
700
701 2
        return $this;
702
    }
703
704
    /**
705
     * Gets the position of the first result the query object was set to retrieve (the "offset").
706
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
707
     *
708
     * @return integer The position of the first result.
709
     */
710 2
    public function getFirstResult()
711
    {
712 2
        return $this->_firstResult;
713
    }
714
715
    /**
716
     * Sets the maximum number of results to retrieve (the "limit").
717
     *
718
     * @param integer $maxResults The maximum number of results to retrieve.
719
     *
720
     * @return self
721
     */
722 3
    public function setMaxResults($maxResults)
723
    {
724 3
        $this->_maxResults = $maxResults;
725
726 3
        return $this;
727
    }
728
729
    /**
730
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
731
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
732
     *
733
     * @return integer Maximum number of results.
734
     */
735 2
    public function getMaxResults()
736
    {
737 2
        return $this->_maxResults;
738
    }
739
740
    /**
741
     * Either appends to or replaces a single, generic query part.
742
     *
743
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
744
     * 'groupBy', 'having' and 'orderBy'.
745
     *
746
     * @param string       $dqlPartName The DQL part name.
747
     * @param object|array $dqlPart     An Expr object.
748
     * @param bool         $append      Whether to append (true) or replace (false).
749
     *
750
     * @return self
751
     */
752 113
    public function add($dqlPartName, $dqlPart, $append = false)
753
    {
754 113
        if ($append && ($dqlPartName === "where" || $dqlPartName === "having")) {
755 1
            throw new \InvalidArgumentException(
756
                "Using \$append = true does not have an effect with 'where' or 'having' ".
757 1
                "parts. See QueryBuilder#andWhere() for an example for correct usage."
758
            );
759
        }
760
761 113
        $isMultiple = is_array($this->_dqlParts[$dqlPartName])
762 113
            && !($dqlPartName == 'join' && !$append);
763
764
        // Allow adding any part retrieved from self::getDQLParts().
765 113
        if (is_array($dqlPart) && $dqlPartName != 'join') {
766 1
            $dqlPart = reset($dqlPart);
767
        }
768
769
        // This is introduced for backwards compatibility reasons.
770
        // TODO: Remove for 3.0
771 113
        if ($dqlPartName == 'join') {
772 30
            $newDqlPart = [];
773
774 30
            foreach ($dqlPart as $k => $v) {
775 30
                $k = is_numeric($k) ? $this->getRootAlias() : $k;
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated with message: Please use $qb->getRootAliases() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
776
777 30
                $newDqlPart[$k] = $v;
778
            }
779
780 30
            $dqlPart = $newDqlPart;
781
        }
782
783 113
        if ($append && $isMultiple) {
784 107
            if (is_array($dqlPart)) {
785 30
                $key = key($dqlPart);
786
787 30
                $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
788
            } else {
789 107
                $this->_dqlParts[$dqlPartName][] = $dqlPart;
790
            }
791
        } else {
792 111
            $this->_dqlParts[$dqlPartName] = ($isMultiple) ? [$dqlPart] : $dqlPart;
793
        }
794
795 113
        $this->_state = self::STATE_DIRTY;
796
797 113
        return $this;
798
    }
799
800
    /**
801
     * Specifies an item that is to be returned in the query result.
802
     * Replaces any previously specified selections, if any.
803
     *
804
     * <code>
805
     *     $qb = $em->createQueryBuilder()
806
     *         ->select('u', 'p')
807
     *         ->from('User', 'u')
808
     *         ->leftJoin('u.Phonenumbers', 'p');
809
     * </code>
810
     *
811
     * @param mixed $select The selection expressions.
812
     *
813
     * @return self
814
     */
815 106
    public function select($select = null)
816
    {
817 106
        $this->_type = self::SELECT;
818
819 106
        if (empty($select)) {
820 1
            return $this;
821
        }
822
823 105
        $selects = is_array($select) ? $select : func_get_args();
824
825 105
        return $this->add('select', new Expr\Select($selects), false);
826
    }
827
828
    /**
829
     * Adds a DISTINCT flag to this query.
830
     *
831
     * <code>
832
     *     $qb = $em->createQueryBuilder()
833
     *         ->select('u')
834
     *         ->distinct()
835
     *         ->from('User', 'u');
836
     * </code>
837
     *
838
     * @param bool $flag
839
     *
840
     * @return self
841
     */
842 1
    public function distinct($flag = true)
843
    {
844 1
        $this->_dqlParts['distinct'] = (bool) $flag;
845
846 1
        return $this;
847
    }
848
849
    /**
850
     * Adds an item that is to be returned in the query result.
851
     *
852
     * <code>
853
     *     $qb = $em->createQueryBuilder()
854
     *         ->select('u')
855
     *         ->addSelect('p')
856
     *         ->from('User', 'u')
857
     *         ->leftJoin('u.Phonenumbers', 'p');
858
     * </code>
859
     *
860
     * @param mixed $select The selection expression.
861
     *
862
     * @return self
863
     */
864 1
    public function addSelect($select = null)
865
    {
866 1
        $this->_type = self::SELECT;
867
868 1
        if (empty($select)) {
869
            return $this;
870
        }
871
872 1
        $selects = is_array($select) ? $select : func_get_args();
873
874 1
        return $this->add('select', new Expr\Select($selects), true);
875
    }
876
877
    /**
878
     * Turns the query being built into a bulk delete query that ranges over
879
     * a certain entity type.
880
     *
881
     * <code>
882
     *     $qb = $em->createQueryBuilder()
883
     *         ->delete('User', 'u')
884
     *         ->where('u.id = :user_id')
885
     *         ->setParameter('user_id', 1);
886
     * </code>
887
     *
888
     * @param string $delete The class/type whose instances are subject to the deletion.
889
     * @param string $alias  The class/type alias used in the constructed query.
890
     *
891
     * @return self
892
     */
893 4
    public function delete($delete = null, $alias = null)
894
    {
895 4
        $this->_type = self::DELETE;
896
897 4
        if ( ! $delete) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $delete of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
898 1
            return $this;
899
        }
900
901 3
        return $this->add('from', new Expr\From($delete, $alias));
902
    }
903
904
    /**
905
     * Turns the query being built into a bulk update query that ranges over
906
     * a certain entity type.
907
     *
908
     * <code>
909
     *     $qb = $em->createQueryBuilder()
910
     *         ->update('User', 'u')
911
     *         ->set('u.password', '?1')
912
     *         ->where('u.id = ?2');
913
     * </code>
914
     *
915
     * @param string $update The class/type whose instances are subject to the update.
916
     * @param string $alias  The class/type alias used in the constructed query.
917
     *
918
     * @return self
919
     */
920 3
    public function update($update = null, $alias = null)
921
    {
922 3
        $this->_type = self::UPDATE;
923
924 3
        if ( ! $update) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $update of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
925 1
            return $this;
926
        }
927
928 2
        return $this->add('from', new Expr\From($update, $alias));
929
    }
930
931
    /**
932
     * Creates and adds a query root corresponding to the entity identified by the given alias,
933
     * forming a cartesian product with any existing query roots.
934
     *
935
     * <code>
936
     *     $qb = $em->createQueryBuilder()
937
     *         ->select('u')
938
     *         ->from('User', 'u');
939
     * </code>
940
     *
941
     * @param string $from    The class name.
942
     * @param string $alias   The alias of the class.
943
     * @param string $indexBy The index for the from.
944
     *
945
     * @return self
946
     */
947 105
    public function from($from, $alias, $indexBy = null)
948
    {
949 105
        return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
950
    }
951
952
    /**
953
     * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
954
     * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
955
     * setting an index by.
956
     *
957
     * <code>
958
     *     $qb = $userRepository->createQueryBuilder('u')
959
     *         ->indexBy('u', 'u.id');
960
     *
961
     *     // Is equivalent to...
962
     *
963
     *     $qb = $em->createQueryBuilder()
964
     *         ->select('u')
965
     *         ->from('User', 'u', 'u.id');
966
     * </code>
967
     *
968
     * @param string $alias   The root alias of the class.
969
     * @param string $indexBy The index for the from.
970
     *
971
     * @return self
972
     *
973
     * @throws Query\QueryException
974
     */
975 2
    public function indexBy($alias, $indexBy)
976
    {
977 2
        $rootAliases = $this->getRootAliases();
978
979 2
        if (!in_array($alias, $rootAliases)) {
980
            throw new Query\QueryException(
981
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
982
            );
983
        }
984
985 2
        foreach ($this->_dqlParts['from'] as &$fromClause) {
986
            /* @var Expr\From $fromClause */
987 2
            if ($fromClause->getAlias() !== $alias) {
988 1
                continue;
989
            }
990
991 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
992
        }
993
994 2
        return $this;
995
    }
996
997
    /**
998
     * Creates and adds a join over an entity association to the query.
999
     *
1000
     * The entities in the joined association will be fetched as part of the query
1001
     * result if the alias used for the joined association is placed in the select
1002
     * expressions.
1003
     *
1004
     * <code>
1005
     *     $qb = $em->createQueryBuilder()
1006
     *         ->select('u')
1007
     *         ->from('User', 'u')
1008
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
1009
     * </code>
1010
     *
1011
     * @param string      $join          The relationship to join.
1012
     * @param string      $alias         The alias of the join.
1013
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1014
     * @param string|null $condition     The condition for the join.
1015
     * @param string|null $indexBy       The index for the join.
1016
     *
1017
     * @return self
1018
     */
1019 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1020
    {
1021 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
1022
    }
1023
1024
    /**
1025
     * Creates and adds a join over an entity association to the query.
1026
     *
1027
     * The entities in the joined association will be fetched as part of the query
1028
     * result if the alias used for the joined association is placed in the select
1029
     * expressions.
1030
     *
1031
     *     [php]
1032
     *     $qb = $em->createQueryBuilder()
1033
     *         ->select('u')
1034
     *         ->from('User', 'u')
1035
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
1036
     *
1037
     * @param string      $join          The relationship to join.
1038
     * @param string      $alias         The alias of the join.
1039
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1040
     * @param string|null $condition     The condition for the join.
1041
     * @param string|null $indexBy       The index for the join.
1042
     *
1043
     * @return self
1044
     */
1045 15
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1046
    {
1047 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
1048
1049 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
1050
1051 15
        $join = new Expr\Join(
1052 15
            Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
1053
        );
1054
1055 15
        return $this->add('join', [$rootAlias => $join], true);
1056
    }
1057
1058
    /**
1059
     * Creates and adds a left join over an entity association to the query.
1060
     *
1061
     * The entities in the joined association will be fetched as part of the query
1062
     * result if the alias used for the joined association is placed in the select
1063
     * expressions.
1064
     *
1065
     * <code>
1066
     *     $qb = $em->createQueryBuilder()
1067
     *         ->select('u')
1068
     *         ->from('User', 'u')
1069
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
1070
     * </code>
1071
     *
1072
     * @param string      $join          The relationship to join.
1073
     * @param string      $alias         The alias of the join.
1074
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1075
     * @param string|null $condition     The condition for the join.
1076
     * @param string|null $indexBy       The index for the join.
1077
     *
1078
     * @return self
1079
     */
1080 15
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1081
    {
1082 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
1083
1084 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
1085
1086 15
        $join = new Expr\Join(
1087 15
            Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
1088
        );
1089
1090 15
        return $this->add('join', [$rootAlias => $join], true);
1091
    }
1092
1093
    /**
1094
     * Sets a new value for a field in a bulk update query.
1095
     *
1096
     * <code>
1097
     *     $qb = $em->createQueryBuilder()
1098
     *         ->update('User', 'u')
1099
     *         ->set('u.password', '?1')
1100
     *         ->where('u.id = ?2');
1101
     * </code>
1102
     *
1103
     * @param string $key   The key/field to set.
1104
     * @param string $value The value, expression, placeholder, etc.
1105
     *
1106
     * @return self
1107
     */
1108 2
    public function set($key, $value)
1109
    {
1110 2
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1111
    }
1112
1113
    /**
1114
     * Specifies one or more restrictions to the query result.
1115
     * Replaces any previously specified restrictions, if any.
1116
     *
1117
     * <code>
1118
     *     $qb = $em->createQueryBuilder()
1119
     *         ->select('u')
1120
     *         ->from('User', 'u')
1121
     *         ->where('u.id = ?');
1122
     *
1123
     *     // You can optionally programmatically build and/or expressions
1124
     *     $qb = $em->createQueryBuilder();
1125
     *
1126
     *     $or = $qb->expr()->orX();
1127
     *     $or->add($qb->expr()->eq('u.id', 1));
1128
     *     $or->add($qb->expr()->eq('u.id', 2));
1129
     *
1130
     *     $qb->update('User', 'u')
1131
     *         ->set('u.password', '?')
1132
     *         ->where($or);
1133
     * </code>
1134
     *
1135
     * @param mixed $predicates The restriction predicates.
1136
     *
1137
     * @return self
1138
     */
1139 40
    public function where($predicates)
1140
    {
1141 40
        if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
1142 37
            $predicates = new Expr\Andx(func_get_args());
1143
        }
1144
1145 40
        return $this->add('where', $predicates);
1146
    }
1147
1148
    /**
1149
     * Adds one or more restrictions to the query results, forming a logical
1150
     * conjunction with any previously specified restrictions.
1151
     *
1152
     * <code>
1153
     *     $qb = $em->createQueryBuilder()
1154
     *         ->select('u')
1155
     *         ->from('User', 'u')
1156
     *         ->where('u.username LIKE ?')
1157
     *         ->andWhere('u.is_active = 1');
1158
     * </code>
1159
     *
1160
     * @param mixed $where The query restrictions.
0 ignored issues
show
Bug introduced by
There is no parameter named $where. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1161
     *
1162
     * @return self
1163
     *
1164
     * @see where()
1165
     */
1166 17
    public function andWhere()
1167
    {
1168 17
        $args  = func_get_args();
1169 17
        $where = $this->getDQLPart('where');
1170
1171 17
        if ($where instanceof Expr\Andx) {
1172 6
            $where->addMultiple($args);
1173
        } else {
1174 12
            array_unshift($args, $where);
1175 12
            $where = new Expr\Andx($args);
1176
        }
1177
1178 17
        return $this->add('where', $where);
1179
    }
1180
1181
    /**
1182
     * Adds one or more restrictions to the query results, forming a logical
1183
     * disjunction with any previously specified restrictions.
1184
     *
1185
     * <code>
1186
     *     $qb = $em->createQueryBuilder()
1187
     *         ->select('u')
1188
     *         ->from('User', 'u')
1189
     *         ->where('u.id = 1')
1190
     *         ->orWhere('u.id = 2');
1191
     * </code>
1192
     *
1193
     * @param mixed $where The WHERE statement.
0 ignored issues
show
Bug introduced by
There is no parameter named $where. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1194
     *
1195
     * @return self
1196
     *
1197
     * @see where()
1198
     */
1199 5
    public function orWhere()
1200
    {
1201 5
        $args  = func_get_args();
1202 5
        $where = $this->getDQLPart('where');
1203
1204 5
        if ($where instanceof Expr\Orx) {
1205
            $where->addMultiple($args);
1206
        } else {
1207 5
            array_unshift($args, $where);
1208 5
            $where = new Expr\Orx($args);
1209
        }
1210
1211 5
        return $this->add('where', $where);
1212
    }
1213
1214
    /**
1215
     * Specifies a grouping over the results of the query.
1216
     * Replaces any previously specified groupings, if any.
1217
     *
1218
     * <code>
1219
     *     $qb = $em->createQueryBuilder()
1220
     *         ->select('u')
1221
     *         ->from('User', 'u')
1222
     *         ->groupBy('u.id');
1223
     * </code>
1224
     *
1225
     * @param string $groupBy The grouping expression.
1226
     *
1227
     * @return self
1228
     */
1229 7
    public function groupBy($groupBy)
0 ignored issues
show
Unused Code introduced by
The parameter $groupBy is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1230
    {
1231 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1232
    }
1233
1234
    /**
1235
     * Adds a grouping expression to the query.
1236
     *
1237
     * <code>
1238
     *     $qb = $em->createQueryBuilder()
1239
     *         ->select('u')
1240
     *         ->from('User', 'u')
1241
     *         ->groupBy('u.lastLogin')
1242
     *         ->addGroupBy('u.createdAt');
1243
     * </code>
1244
     *
1245
     * @param string $groupBy The grouping expression.
1246
     *
1247
     * @return self
1248
     */
1249 1
    public function addGroupBy($groupBy)
0 ignored issues
show
Unused Code introduced by
The parameter $groupBy is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1250
    {
1251 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1252
    }
1253
1254
    /**
1255
     * Specifies a restriction over the groups of the query.
1256
     * Replaces any previous having restrictions, if any.
1257
     *
1258
     * @param mixed $having The restriction over the groups.
1259
     *
1260
     * @return self
1261
     */
1262 3
    public function having($having)
1263
    {
1264 3
        if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1265 3
            $having = new Expr\Andx(func_get_args());
1266
        }
1267
1268 3
        return $this->add('having', $having);
1269
    }
1270
1271
    /**
1272
     * Adds a restriction over the groups of the query, forming a logical
1273
     * conjunction with any existing having restrictions.
1274
     *
1275
     * @param mixed $having The restriction to append.
1276
     *
1277
     * @return self
1278
     */
1279 2
    public function andHaving($having)
0 ignored issues
show
Unused Code introduced by
The parameter $having is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1280
    {
1281 2
        $args   = func_get_args();
1282 2
        $having = $this->getDQLPart('having');
1283
1284 2
        if ($having instanceof Expr\Andx) {
1285 2
            $having->addMultiple($args);
1286
        } else {
1287
            array_unshift($args, $having);
1288
            $having = new Expr\Andx($args);
1289
        }
1290
1291 2
        return $this->add('having', $having);
1292
    }
1293
1294
    /**
1295
     * Adds a restriction over the groups of the query, forming a logical
1296
     * disjunction with any existing having restrictions.
1297
     *
1298
     * @param mixed $having The restriction to add.
1299
     *
1300
     * @return self
1301
     */
1302 1
    public function orHaving($having)
0 ignored issues
show
Unused Code introduced by
The parameter $having is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1303
    {
1304 1
        $args   = func_get_args();
1305 1
        $having = $this->getDQLPart('having');
1306
1307 1
        if ($having instanceof Expr\Orx) {
1308
            $having->addMultiple($args);
1309
        } else {
1310 1
            array_unshift($args, $having);
1311 1
            $having = new Expr\Orx($args);
1312
        }
1313
1314 1
        return $this->add('having', $having);
1315
    }
1316
1317
    /**
1318
     * Specifies an ordering for the query results.
1319
     * Replaces any previously specified orderings, if any.
1320
     *
1321
     * @param string|Expr\OrderBy $sort  The ordering expression.
1322
     * @param string              $order The ordering direction.
1323
     *
1324
     * @return self
1325
     */
1326 10
    public function orderBy($sort, $order = null)
1327
    {
1328 10
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1329
1330 10
        return $this->add('orderBy', $orderBy);
1331
    }
1332
1333
    /**
1334
     * Adds an ordering to the query results.
1335
     *
1336
     * @param string|Expr\OrderBy $sort  The ordering expression.
1337
     * @param string              $order The ordering direction.
1338
     *
1339
     * @return self
1340
     */
1341 4
    public function addOrderBy($sort, $order = null)
1342
    {
1343 4
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1344
1345 4
        return $this->add('orderBy', $orderBy, true);
1346
    }
1347
1348
    /**
1349
     * Adds criteria to the query.
1350
     *
1351
     * Adds where expressions with AND operator.
1352
     * Adds orderings.
1353
     * Overrides firstResult and maxResults if they're set.
1354
     *
1355
     * @param Criteria $criteria
1356
     *
1357
     * @return self
1358
     *
1359
     * @throws Query\QueryException
1360
     */
1361 13
    public function addCriteria(Criteria $criteria)
1362
    {
1363 13
        $allAliases = $this->getAllAliases();
1364 13
        if ( ! isset($allAliases[0])) {
1365
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1366
        }
1367
1368 13
        $visitor = new QueryExpressionVisitor($this->getAllAliases());
1369
1370 13
        if ($whereExpression = $criteria->getWhereExpression()) {
1371 9
            $this->andWhere($visitor->dispatch($whereExpression));
1372 9
            foreach ($visitor->getParameters() as $parameter) {
1373 9
                $this->parameters->add($parameter);
1374
            }
1375
        }
1376
1377 13
        if ($criteria->getOrderings()) {
1378 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1379
1380 2
                $hasValidAlias = false;
1381 2
                foreach($allAliases as $alias) {
1382 2
                    if(strpos($sort . '.', $alias . '.') === 0) {
1383 1
                        $hasValidAlias = true;
1384 2
                        break;
1385
                    }
1386
                }
1387
1388 2
                if(!$hasValidAlias) {
1389 1
                    $sort = $allAliases[0] . '.' . $sort;
1390
                }
1391
1392 2
                $this->addOrderBy($sort, $order);
1393
            }
1394
        }
1395
1396
        // Overwrite limits only if they was set in criteria
1397 13
        if (($firstResult = $criteria->getFirstResult()) !== null) {
1398 1
            $this->setFirstResult($firstResult);
1399
        }
1400 13
        if (($maxResults = $criteria->getMaxResults()) !== null) {
1401 1
            $this->setMaxResults($maxResults);
1402
        }
1403
1404 13
        return $this;
1405
    }
1406
1407
    /**
1408
     * Gets a query part by its name.
1409
     *
1410
     * @param string $queryPartName
1411
     *
1412
     * @return mixed $queryPart
1413
     *
1414
     * @todo Rename: getQueryPart (or remove?)
1415
     */
1416 96
    public function getDQLPart($queryPartName)
1417
    {
1418 96
        return $this->_dqlParts[$queryPartName];
1419
    }
1420
1421
    /**
1422
     * Gets all query parts.
1423
     *
1424
     * @return array $dqlParts
1425
     *
1426
     * @todo Rename: getQueryParts (or remove?)
1427
     */
1428 1
    public function getDQLParts()
1429
    {
1430 1
        return $this->_dqlParts;
1431
    }
1432
1433
    /**
1434
     * @return string
1435
     */
1436 1
    private function _getDQLForDelete()
1437
    {
1438
         return 'DELETE'
1439 1
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1440 1
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1441 1
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1442
    }
1443
1444
    /**
1445
     * @return string
1446
     */
1447 2
    private function _getDQLForUpdate()
1448
    {
1449
         return 'UPDATE'
1450 2
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1451 2
              . $this->_getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
1452 2
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1453 2
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1454
    }
1455
1456
    /**
1457
     * @return string
1458
     */
1459 78
    private function _getDQLForSelect()
1460
    {
1461
        $dql = 'SELECT'
1462 78
             . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
1463 78
             . $this->_getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
1464
1465 78
        $fromParts   = $this->getDQLPart('from');
1466 78
        $joinParts   = $this->getDQLPart('join');
1467 78
        $fromClauses = [];
1468
1469
        // Loop through all FROM clauses
1470 78
        if ( ! empty($fromParts)) {
1471 77
            $dql .= ' FROM ';
1472
1473 77
            foreach ($fromParts as $from) {
1474 77
                $fromClause = (string) $from;
1475
1476 77
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1477 25
                    foreach ($joinParts[$from->getAlias()] as $join) {
1478 25
                        $fromClause .= ' ' . ((string) $join);
1479
                    }
1480
                }
1481
1482 77
                $fromClauses[] = $fromClause;
1483
            }
1484
        }
1485
1486 78
        $dql .= implode(', ', $fromClauses)
1487 78
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1488 78
              . $this->_getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
1489 78
              . $this->_getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
1490 78
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1491
1492 78
        return $dql;
1493
    }
1494
1495
    /**
1496
     * @param string $queryPartName
1497
     * @param array  $options
1498
     *
1499
     * @return string
1500
     */
1501 81
    private function _getReducedDQLQueryPart($queryPartName, $options = [])
1502
    {
1503 81
        $queryPart = $this->getDQLPart($queryPartName);
1504
1505 81
        if (empty($queryPart)) {
1506 81
            return (isset($options['empty']) ? $options['empty'] : '');
1507
        }
1508
1509 81
        return (isset($options['pre']) ? $options['pre'] : '')
1510 81
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1511 81
             . (isset($options['post']) ? $options['post'] : '');
1512
    }
1513
1514
    /**
1515
     * Resets DQL parts.
1516
     *
1517
     * @param array|null $parts
1518
     *
1519
     * @return self
1520
     */
1521 2
    public function resetDQLParts($parts = null)
1522
    {
1523 2
        if (null === $parts) {
1524 1
            $parts = array_keys($this->_dqlParts);
1525
        }
1526
1527 2
        foreach ($parts as $part) {
1528 2
            $this->resetDQLPart($part);
1529
        }
1530
1531 2
        return $this;
1532
    }
1533
1534
    /**
1535
     * Resets single DQL part.
1536
     *
1537
     * @param string $part
1538
     *
1539
     * @return self
1540
     */
1541 3
    public function resetDQLPart($part)
1542
    {
1543 3
        $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? [] : null;
1544 3
        $this->_state           = self::STATE_DIRTY;
1545
1546 3
        return $this;
1547
    }
1548
1549
    /**
1550
     * Gets a string representation of this QueryBuilder which corresponds to
1551
     * the final DQL query being constructed.
1552
     *
1553
     * @return string The string representation of this QueryBuilder.
1554
     */
1555 5
    public function __toString()
1556
    {
1557 5
        return $this->getDQL();
1558
    }
1559
1560
    /**
1561
     * Deep clones all expression objects in the DQL parts.
1562
     *
1563
     * @return void
1564
     */
1565 3
    public function __clone()
1566
    {
1567 3
        foreach ($this->_dqlParts as $part => $elements) {
1568 3
            if (is_array($this->_dqlParts[$part])) {
1569 3
                foreach ($this->_dqlParts[$part] as $idx => $element) {
1570 2
                    if (is_object($element)) {
1571 3
                        $this->_dqlParts[$part][$idx] = clone $element;
1572
                    }
1573
                }
1574 3
            } else if (is_object($elements)) {
1575 3
                $this->_dqlParts[$part] = clone $elements;
1576
            }
1577
        }
1578
1579 3
        $parameters = [];
1580
1581 3
        foreach ($this->parameters as $parameter) {
1582 1
            $parameters[] = clone $parameter;
1583
        }
1584
1585 3
        $this->parameters = new ArrayCollection($parameters);
1586 3
    }
1587
}
1588