Failed Conditions
Push — master ( 8b8169...80f782 )
by Marco
15s
created

QueryBuilder::_getReducedDQLQueryPart()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 2
crap 3
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|null
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
     * Whether to use second level cache, if available.
123
     *
124
     * @var boolean
125
     */
126
    protected $cacheable = false;
127
128
    /**
129
     * Second level cache region name.
130
     *
131
     * @var string|null
132
     */
133
    protected $cacheRegion;
134
135
    /**
136
     * Second level query cache mode.
137
     *
138
     * @var integer|null
139
     */
140
    protected $cacheMode;
141
142
    /**
143
     * @var integer
144
     */
145
    protected $lifetime = 0;
146
147
    /**
148
     * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
149
     *
150
     * @param EntityManagerInterface $em The EntityManager to use.
151
     */
152 121
    public function __construct(EntityManagerInterface $em)
153
    {
154 121
        $this->_em = $em;
155 121
        $this->parameters = new ArrayCollection();
156 121
    }
157
158
    /**
159
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
160
     * This producer method is intended for convenient inline usage. Example:
161
     *
162
     * <code>
163
     *     $qb = $em->createQueryBuilder();
164
     *     $qb
165
     *         ->select('u')
166
     *         ->from('User', 'u')
167
     *         ->where($qb->expr()->eq('u.id', 1));
168
     * </code>
169
     *
170
     * For more complex expression construction, consider storing the expression
171
     * builder object in a local variable.
172
     *
173
     * @return Query\Expr
174
     */
175 11
    public function expr()
176
    {
177 11
        return $this->_em->getExpressionBuilder();
178
    }
179
180
    /**
181
     *
182
     * Enable/disable second level query (result) caching for this query.
183
     *
184
     * @param boolean $cacheable
185
     *
186
     * @return self
187
     */
188 1
    public function setCacheable($cacheable)
189
    {
190 1
        $this->cacheable = (boolean) $cacheable;
191
192 1
        return $this;
193
    }
194
195
    /**
196
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
197
     */
198 1
    public function isCacheable()
199
    {
200 1
        return $this->cacheable;
201
    }
202
203
    /**
204
     * @param string $cacheRegion
205
     *
206
     * @return self
207
     */
208 1
    public function setCacheRegion($cacheRegion)
209
    {
210 1
        $this->cacheRegion = (string) $cacheRegion;
211
212 1
        return $this;
213
    }
214
215
    /**
216
    * Obtain the name of the second level query cache region in which query results will be stored
217
    *
218
    * @return string|null The cache region name; NULL indicates the default region.
219
    */
220 1
    public function getCacheRegion()
221
    {
222 1
        return $this->cacheRegion;
223
    }
224
225
    /**
226
     * @return integer
227
     */
228 1
    public function getLifetime()
229
    {
230 1
        return $this->lifetime;
231
    }
232
233
    /**
234
     * Sets the life-time for this query into second level cache.
235
     *
236
     * @param integer $lifetime
237
     *
238
     * @return self
239
     */
240 1
    public function setLifetime($lifetime)
241
    {
242 1
        $this->lifetime = (integer) $lifetime;
243
244 1
        return $this;
245
    }
246
247
    /**
248
     * @return integer
249
     */
250 1
    public function getCacheMode()
251
    {
252 1
        return $this->cacheMode;
253
    }
254
255
    /**
256
     * @param integer $cacheMode
257
     *
258
     * @return self
259
     */
260 1
    public function setCacheMode($cacheMode)
261
    {
262 1
        $this->cacheMode = (integer) $cacheMode;
263
264 1
        return $this;
265
    }
266
267
    /**
268
     * Gets the type of the currently built query.
269
     *
270
     * @return integer
271
     */
272 4
    public function getType()
273
    {
274 4
        return $this->_type;
275
    }
276
277
    /**
278
     * Gets the associated EntityManager for this query builder.
279
     *
280
     * @return EntityManager
281
     */
282 1
    public function getEntityManager()
283
    {
284 1
        return $this->_em;
285
    }
286
287
    /**
288
     * Gets the state of this query builder instance.
289
     *
290
     * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
291
     */
292 2
    public function getState()
293
    {
294 2
        return $this->_state;
295
    }
296
297
    /**
298
     * Gets the complete DQL string formed by the current specifications of this QueryBuilder.
299
     *
300
     * <code>
301
     *     $qb = $em->createQueryBuilder()
302
     *         ->select('u')
303
     *         ->from('User', 'u');
304
     *     echo $qb->getDql(); // SELECT u FROM User u
305
     * </code>
306
     *
307
     * @return string The DQL query string.
308
     */
309 85
    public function getDQL()
310
    {
311 85
        if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
312 44
            return $this->_dql;
313
        }
314
315 85
        switch ($this->_type) {
316 85
            case self::DELETE:
317 1
                $dql = $this->_getDQLForDelete();
318 1
                break;
319
320 84
            case self::UPDATE:
321 3
                $dql = $this->_getDQLForUpdate();
322 3
                break;
323
324 82
            case self::SELECT:
325
            default:
326 82
                $dql = $this->_getDQLForSelect();
327 82
                break;
328
        }
329
330 85
        $this->_state = self::STATE_CLEAN;
331 85
        $this->_dql   = $dql;
332
333 85
        return $dql;
334
    }
335
336
    /**
337
     * Constructs a Query instance from the current specifications of the builder.
338
     *
339
     * <code>
340
     *     $qb = $em->createQueryBuilder()
341
     *         ->select('u')
342
     *         ->from('User', 'u');
343
     *     $q = $qb->getQuery();
344
     *     $results = $q->execute();
345
     * </code>
346
     *
347
     * @return Query
348
     */
349 74
    public function getQuery()
350
    {
351 74
        $parameters = clone $this->parameters;
352 74
        $query      = $this->_em->createQuery($this->getDQL())
353 74
            ->setParameters($parameters)
354 74
            ->setFirstResult($this->_firstResult)
355 74
            ->setMaxResults($this->_maxResults);
356
357 74
        if ($this->lifetime) {
358 1
            $query->setLifetime($this->lifetime);
359
        }
360
361 74
        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...
362 1
            $query->setCacheMode($this->cacheMode);
363
        }
364
365 74
        if ($this->cacheable) {
366 1
            $query->setCacheable($this->cacheable);
367
        }
368
369 74
        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...
370 1
            $query->setCacheRegion($this->cacheRegion);
371
        }
372
373 74
        return $query;
374
    }
375
376
    /**
377
     * Finds the root entity alias of the joined entity.
378
     *
379
     * @param string $alias       The alias of the new join entity
380
     * @param string $parentAlias The parent entity alias of the join relationship
381
     *
382
     * @return string
383
     */
384 30
    private function findRootAlias($alias, $parentAlias)
385
    {
386 30
        $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...
387
388 30
        if (in_array($parentAlias, $this->getRootAliases())) {
389 29
            $rootAlias = $parentAlias;
390 7
        } elseif (isset($this->joinRootAliases[$parentAlias])) {
391 6
            $rootAlias = $this->joinRootAliases[$parentAlias];
392
        } else {
393
            // Should never happen with correct joining order. Might be
394
            // thoughtful to throw exception instead.
395 1
            $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...
396
        }
397
398 30
        $this->joinRootAliases[$alias] = $rootAlias;
399
400 30
        return $rootAlias;
401
    }
402
403
    /**
404
     * Gets the FIRST root alias of the query. This is the first entity alias involved
405
     * in the construction of the query.
406
     *
407
     * <code>
408
     * $qb = $em->createQueryBuilder()
409
     *     ->select('u')
410
     *     ->from('User', 'u');
411
     *
412
     * echo $qb->getRootAlias(); // u
413
     * </code>
414
     *
415
     * @deprecated Please use $qb->getRootAliases() instead.
416
     * @throws \RuntimeException
417
     *
418
     * @return string
419
     */
420 4
    public function getRootAlias()
421
    {
422 4
        $aliases = $this->getRootAliases();
423
424 4
        if ( ! isset($aliases[0])) {
425
            throw new \RuntimeException('No alias was set before invoking getRootAlias().');
426
        }
427
428 4
        return $aliases[0];
429
    }
430
431
    /**
432
     * Gets the root aliases of the query. This is the entity aliases involved
433
     * in the construction of the query.
434
     *
435
     * <code>
436
     *     $qb = $em->createQueryBuilder()
437
     *         ->select('u')
438
     *         ->from('User', 'u');
439
     *
440
     *     $qb->getRootAliases(); // array('u')
441
     * </code>
442
     *
443
     * @return array
444
     */
445 46 View Code Duplication
    public function getRootAliases()
446
    {
447 46
        $aliases = [];
448
449 46
        foreach ($this->_dqlParts['from'] as &$fromClause) {
450 46
            if (is_string($fromClause)) {
451
                $spacePos = strrpos($fromClause, ' ');
452
                $from     = substr($fromClause, 0, $spacePos);
453
                $alias    = substr($fromClause, $spacePos + 1);
454
455
                $fromClause = new Query\Expr\From($from, $alias);
456
            }
457
458 46
            $aliases[] = $fromClause->getAlias();
459
        }
460
461 46
        return $aliases;
462
    }
463
464
    /**
465
     * Gets all the aliases that have been used in the query.
466
     * Including all select root aliases and join aliases
467
     *
468
     * <code>
469
     *     $qb = $em->createQueryBuilder()
470
     *         ->select('u')
471
     *         ->from('User', 'u')
472
     *         ->join('u.articles','a');
473
     *
474
     *     $qb->getAllAliases(); // array('u','a')
475
     * </code>
476
     * @return array
477
     */
478 15
    public function getAllAliases()
479
    {
480 15
        return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases));
481
    }
482
483
    /**
484
     * Gets the root entities of the query. This is the entity aliases involved
485
     * in the construction of the query.
486
     *
487
     * <code>
488
     *     $qb = $em->createQueryBuilder()
489
     *         ->select('u')
490
     *         ->from('User', 'u');
491
     *
492
     *     $qb->getRootEntities(); // array('User')
493
     * </code>
494
     *
495
     * @return array
496
     */
497 1 View Code Duplication
    public function getRootEntities()
498
    {
499 1
        $entities = [];
500
501 1
        foreach ($this->_dqlParts['from'] as &$fromClause) {
502 1
            if (is_string($fromClause)) {
503
                $spacePos = strrpos($fromClause, ' ');
504
                $from     = substr($fromClause, 0, $spacePos);
505
                $alias    = substr($fromClause, $spacePos + 1);
506
507
                $fromClause = new Query\Expr\From($from, $alias);
508
            }
509
510 1
            $entities[] = $fromClause->getFrom();
511
        }
512
513 1
        return $entities;
514
    }
515
516
    /**
517
     * Sets a query parameter for the query being constructed.
518
     *
519
     * <code>
520
     *     $qb = $em->createQueryBuilder()
521
     *         ->select('u')
522
     *         ->from('User', 'u')
523
     *         ->where('u.id = :user_id')
524
     *         ->setParameter('user_id', 1);
525
     * </code>
526
     *
527
     * @param string|integer $key   The parameter position or name.
528
     * @param mixed          $value The parameter value.
529
     * @param string|integer|null    $type  PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
530
     *
531
     * @return self
532
     */
533 12 View Code Duplication
    public function setParameter($key, $value, $type = null)
534
    {
535 12
        $filteredParameters = $this->parameters->filter(
536 12
            function ($parameter) use ($key)
537
            {
538
                /* @var Query\Parameter $parameter */
539
                // Must not be identical because of string to integer conversion
540 3
                return ($key == $parameter->getName());
541 12
            }
542
        );
543
544 12
        if (count($filteredParameters)) {
545
            /* @var Query\Parameter $parameter */
546
            $parameter = $filteredParameters->first();
547
            $parameter->setValue($value, $type);
548
549
            return $this;
550
        }
551
552 12
        $parameter = new Query\Parameter($key, $value, $type);
553
554 12
        $this->parameters->add($parameter);
555
556 12
        return $this;
557
    }
558
559
    /**
560
     * Sets a collection of query parameters for the query being constructed.
561
     *
562
     * <code>
563
     *     $qb = $em->createQueryBuilder()
564
     *         ->select('u')
565
     *         ->from('User', 'u')
566
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
567
     *         ->setParameters(new ArrayCollection(array(
568
     *             new Parameter('user_id1', 1),
569
     *             new Parameter('user_id2', 2)
570
     *        )));
571
     * </code>
572
     *
573
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters to set.
574
     *
575
     * @return self
576
     */
577 4 View Code Duplication
    public function setParameters($parameters)
578
    {
579
        // BC compatibility with 2.3-
580 4
        if (is_array($parameters)) {
581 1
            $parameterCollection = new ArrayCollection();
582
583 1
            foreach ($parameters as $key => $value) {
584 1
                $parameter = new Query\Parameter($key, $value);
585
586 1
                $parameterCollection->add($parameter);
587
            }
588
589 1
            $parameters = $parameterCollection;
590
        }
591
592 4
        $this->parameters = $parameters;
593
594 4
        return $this;
595
    }
596
597
    /**
598
     * Gets all defined query parameters for the query being constructed.
599
     *
600
     * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
601
     */
602 2
    public function getParameters()
603
    {
604 2
        return $this->parameters;
605
    }
606
607
    /**
608
     * Gets a (previously set) query parameter of the query being constructed.
609
     *
610
     * @param mixed $key The key (index or name) of the bound parameter.
611
     *
612
     * @return Query\Parameter|null The value of the bound parameter.
613
     */
614 12 View Code Duplication
    public function getParameter($key)
615
    {
616 12
        $filteredParameters = $this->parameters->filter(
617 12
            function ($parameter) use ($key)
618
            {
619
                /* @var Query\Parameter $parameter */
620
                // Must not be identical because of string to integer conversion
621 12
                return ($key == $parameter->getName());
622 12
            }
623
        );
624
625 12
        return count($filteredParameters) ? $filteredParameters->first() : null;
626
    }
627
628
    /**
629
     * Sets the position of the first result to retrieve (the "offset").
630
     *
631
     * @param integer $firstResult The first result to return.
632
     *
633
     * @return self
634
     */
635 2
    public function setFirstResult($firstResult)
636
    {
637 2
        $this->_firstResult = $firstResult;
638
639 2
        return $this;
640
    }
641
642
    /**
643
     * Gets the position of the first result the query object was set to retrieve (the "offset").
644
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
645
     *
646
     * @return integer The position of the first result.
647
     */
648 2
    public function getFirstResult()
649
    {
650 2
        return $this->_firstResult;
651
    }
652
653
    /**
654
     * Sets the maximum number of results to retrieve (the "limit").
655
     *
656
     * @param integer|null $maxResults The maximum number of results to retrieve.
657
     *
658
     * @return self
659
     */
660 3
    public function setMaxResults($maxResults)
661
    {
662 3
        $this->_maxResults = $maxResults;
663
664 3
        return $this;
665
    }
666
667
    /**
668
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
669
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
670
     *
671
     * @return integer|null Maximum number of results.
672
     */
673 2
    public function getMaxResults()
674
    {
675 2
        return $this->_maxResults;
676
    }
677
678
    /**
679
     * Either appends to or replaces a single, generic query part.
680
     *
681
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
682
     * 'groupBy', 'having' and 'orderBy'.
683
     *
684
     * @param string       $dqlPartName The DQL part name.
685
     * @param object|array $dqlPart     An Expr object.
686
     * @param bool         $append      Whether to append (true) or replace (false).
687
     *
688
     * @return self
689
     */
690 117
    public function add($dqlPartName, $dqlPart, $append = false)
691
    {
692 117
        if ($append && ($dqlPartName === "where" || $dqlPartName === "having")) {
693 1
            throw new \InvalidArgumentException(
694
                "Using \$append = true does not have an effect with 'where' or 'having' ".
695 1
                "parts. See QueryBuilder#andWhere() for an example for correct usage."
696
            );
697
        }
698
699 117
        $isMultiple = is_array($this->_dqlParts[$dqlPartName])
700 117
            && !($dqlPartName == 'join' && !$append);
701
702
        // Allow adding any part retrieved from self::getDQLParts().
703 117
        if (is_array($dqlPart) && $dqlPartName != 'join') {
704 1
            $dqlPart = reset($dqlPart);
705
        }
706
707
        // This is introduced for backwards compatibility reasons.
708
        // TODO: Remove for 3.0
709 117
        if ($dqlPartName == 'join') {
710 31
            $newDqlPart = [];
711
712 31
            foreach ($dqlPart as $k => $v) {
713 31
                $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...
714
715 31
                $newDqlPart[$k] = $v;
716
            }
717
718 31
            $dqlPart = $newDqlPart;
719
        }
720
721 117
        if ($append && $isMultiple) {
722 111
            if (is_array($dqlPart)) {
723 31
                $key = key($dqlPart);
724
725 31
                $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
726
            } else {
727 111
                $this->_dqlParts[$dqlPartName][] = $dqlPart;
728
            }
729
        } else {
730 115
            $this->_dqlParts[$dqlPartName] = ($isMultiple) ? [$dqlPart] : $dqlPart;
731
        }
732
733 117
        $this->_state = self::STATE_DIRTY;
734
735 117
        return $this;
736
    }
737
738
    /**
739
     * Specifies an item that is to be returned in the query result.
740
     * Replaces any previously specified selections, if any.
741
     *
742
     * <code>
743
     *     $qb = $em->createQueryBuilder()
744
     *         ->select('u', 'p')
745
     *         ->from('User', 'u')
746
     *         ->leftJoin('u.Phonenumbers', 'p');
747
     * </code>
748
     *
749
     * @param mixed $select The selection expressions.
750
     *
751
     * @return self
752
     */
753 110 View Code Duplication
    public function select($select = null)
754
    {
755 110
        $this->_type = self::SELECT;
756
757 110
        if (empty($select)) {
758 1
            return $this;
759
        }
760
761 109
        $selects = is_array($select) ? $select : func_get_args();
762
763 109
        return $this->add('select', new Expr\Select($selects), false);
764
    }
765
766
    /**
767
     * Adds a DISTINCT flag to this query.
768
     *
769
     * <code>
770
     *     $qb = $em->createQueryBuilder()
771
     *         ->select('u')
772
     *         ->distinct()
773
     *         ->from('User', 'u');
774
     * </code>
775
     *
776
     * @param bool $flag
777
     *
778
     * @return self
779
     */
780 1
    public function distinct($flag = true)
781
    {
782 1
        $this->_dqlParts['distinct'] = (bool) $flag;
783
784 1
        return $this;
785
    }
786
787
    /**
788
     * Adds an item that is to be returned in the query result.
789
     *
790
     * <code>
791
     *     $qb = $em->createQueryBuilder()
792
     *         ->select('u')
793
     *         ->addSelect('p')
794
     *         ->from('User', 'u')
795
     *         ->leftJoin('u.Phonenumbers', 'p');
796
     * </code>
797
     *
798
     * @param mixed $select The selection expression.
799
     *
800
     * @return self
801
     */
802 1 View Code Duplication
    public function addSelect($select = null)
803
    {
804 1
        $this->_type = self::SELECT;
805
806 1
        if (empty($select)) {
807
            return $this;
808
        }
809
810 1
        $selects = is_array($select) ? $select : func_get_args();
811
812 1
        return $this->add('select', new Expr\Select($selects), true);
813
    }
814
815
    /**
816
     * Turns the query being built into a bulk delete query that ranges over
817
     * a certain entity type.
818
     *
819
     * <code>
820
     *     $qb = $em->createQueryBuilder()
821
     *         ->delete('User', 'u')
822
     *         ->where('u.id = :user_id')
823
     *         ->setParameter('user_id', 1);
824
     * </code>
825
     *
826
     * @param string $delete The class/type whose instances are subject to the deletion.
827
     * @param string $alias  The class/type alias used in the constructed query.
828
     *
829
     * @return self
830
     */
831 4 View Code Duplication
    public function delete($delete = null, $alias = null)
832
    {
833 4
        $this->_type = self::DELETE;
834
835 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...
836 1
            return $this;
837
        }
838
839 3
        return $this->add('from', new Expr\From($delete, $alias));
840
    }
841
842
    /**
843
     * Turns the query being built into a bulk update query that ranges over
844
     * a certain entity type.
845
     *
846
     * <code>
847
     *     $qb = $em->createQueryBuilder()
848
     *         ->update('User', 'u')
849
     *         ->set('u.password', '?1')
850
     *         ->where('u.id = ?2');
851
     * </code>
852
     *
853
     * @param string $update The class/type whose instances are subject to the update.
854
     * @param string $alias  The class/type alias used in the constructed query.
855
     *
856
     * @return self
857
     */
858 4 View Code Duplication
    public function update($update = null, $alias = null)
859
    {
860 4
        $this->_type = self::UPDATE;
861
862 4
        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...
863 1
            return $this;
864
        }
865
866 3
        return $this->add('from', new Expr\From($update, $alias));
867
    }
868
869
    /**
870
     * Creates and adds a query root corresponding to the entity identified by the given alias,
871
     * forming a cartesian product with any existing query roots.
872
     *
873
     * <code>
874
     *     $qb = $em->createQueryBuilder()
875
     *         ->select('u')
876
     *         ->from('User', 'u');
877
     * </code>
878
     *
879
     * @param string $from    The class name.
880
     * @param string $alias   The alias of the class.
881
     * @param string $indexBy The index for the from.
882
     *
883
     * @return self
884
     */
885 109
    public function from($from, $alias, $indexBy = null)
886
    {
887 109
        return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
888
    }
889
890
    /**
891
     * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
892
     * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
893
     * setting an index by.
894
     *
895
     * <code>
896
     *     $qb = $userRepository->createQueryBuilder('u')
897
     *         ->indexBy('u', 'u.id');
898
     *
899
     *     // Is equivalent to...
900
     *
901
     *     $qb = $em->createQueryBuilder()
902
     *         ->select('u')
903
     *         ->from('User', 'u', 'u.id');
904
     * </code>
905
     *
906
     * @param string $alias   The root alias of the class.
907
     * @param string $indexBy The index for the from.
908
     *
909
     * @return self
910
     *
911
     * @throws Query\QueryException
912
     */
913 2
    public function indexBy($alias, $indexBy)
914
    {
915 2
        $rootAliases = $this->getRootAliases();
916
917 2
        if (!in_array($alias, $rootAliases)) {
918
            throw new Query\QueryException(
919
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
920
            );
921
        }
922
923 2
        foreach ($this->_dqlParts['from'] as &$fromClause) {
924
            /* @var Expr\From $fromClause */
925 2
            if ($fromClause->getAlias() !== $alias) {
926 1
                continue;
927
            }
928
929 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
930
        }
931
932 2
        return $this;
933
    }
934
935
    /**
936
     * Creates and adds a join over an entity association to the query.
937
     *
938
     * The entities in the joined association will be fetched as part of the query
939
     * result if the alias used for the joined association is placed in the select
940
     * expressions.
941
     *
942
     * <code>
943
     *     $qb = $em->createQueryBuilder()
944
     *         ->select('u')
945
     *         ->from('User', 'u')
946
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
947
     * </code>
948
     *
949
     * @param string      $join          The relationship to join.
950
     * @param string      $alias         The alias of the join.
951
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
952
     * @param string|null $condition     The condition for the join.
953
     * @param string|null $indexBy       The index for the join.
954
     *
955
     * @return self
956
     */
957 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
958
    {
959 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
960
    }
961
962
    /**
963
     * Creates and adds a join over an entity association to the query.
964
     *
965
     * The entities in the joined association will be fetched as part of the query
966
     * result if the alias used for the joined association is placed in the select
967
     * expressions.
968
     *
969
     *     [php]
970
     *     $qb = $em->createQueryBuilder()
971
     *         ->select('u')
972
     *         ->from('User', 'u')
973
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
974
     *
975
     * @param string      $join          The relationship to join.
976
     * @param string      $alias         The alias of the join.
977
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
978
     * @param string|null $condition     The condition for the join.
979
     * @param string|null $indexBy       The index for the join.
980
     *
981
     * @return self
982
     */
983 16 View Code Duplication
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
984
    {
985 16
        $parentAlias = substr($join, 0, strpos($join, '.'));
986
987 16
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
988
989 16
        $join = new Expr\Join(
990 16
            Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
991
        );
992
993 16
        return $this->add('join', [$rootAlias => $join], true);
994
    }
995
996
    /**
997
     * Creates and adds a left join over an entity association to the query.
998
     *
999
     * The entities in the joined association will be fetched as part of the query
1000
     * result if the alias used for the joined association is placed in the select
1001
     * expressions.
1002
     *
1003
     * <code>
1004
     *     $qb = $em->createQueryBuilder()
1005
     *         ->select('u')
1006
     *         ->from('User', 'u')
1007
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
1008
     * </code>
1009
     *
1010
     * @param string      $join          The relationship to join.
1011
     * @param string      $alias         The alias of the join.
1012
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1013
     * @param string|null $condition     The condition for the join.
1014
     * @param string|null $indexBy       The index for the join.
1015
     *
1016
     * @return self
1017
     */
1018 15 View Code Duplication
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1019
    {
1020 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
1021
1022 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
1023
1024 15
        $join = new Expr\Join(
1025 15
            Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
1026
        );
1027
1028 15
        return $this->add('join', [$rootAlias => $join], true);
1029
    }
1030
1031
    /**
1032
     * Sets a new value for a field in a bulk update query.
1033
     *
1034
     * <code>
1035
     *     $qb = $em->createQueryBuilder()
1036
     *         ->update('User', 'u')
1037
     *         ->set('u.password', '?1')
1038
     *         ->where('u.id = ?2');
1039
     * </code>
1040
     *
1041
     * @param string $key   The key/field to set.
1042
     * @param string $value The value, expression, placeholder, etc.
1043
     *
1044
     * @return self
1045
     */
1046 3
    public function set($key, $value)
1047
    {
1048 3
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1049
    }
1050
1051
    /**
1052
     * Specifies one or more restrictions to the query result.
1053
     * Replaces any previously specified restrictions, if any.
1054
     *
1055
     * <code>
1056
     *     $qb = $em->createQueryBuilder()
1057
     *         ->select('u')
1058
     *         ->from('User', 'u')
1059
     *         ->where('u.id = ?');
1060
     *
1061
     *     // You can optionally programmatically build and/or expressions
1062
     *     $qb = $em->createQueryBuilder();
1063
     *
1064
     *     $or = $qb->expr()->orX();
1065
     *     $or->add($qb->expr()->eq('u.id', 1));
1066
     *     $or->add($qb->expr()->eq('u.id', 2));
1067
     *
1068
     *     $qb->update('User', 'u')
1069
     *         ->set('u.password', '?')
1070
     *         ->where($or);
1071
     * </code>
1072
     *
1073
     * @param mixed $predicates The restriction predicates.
1074
     *
1075
     * @return self
1076
     */
1077 44 View Code Duplication
    public function where($predicates)
1078
    {
1079 44
        if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
1080 41
            $predicates = new Expr\Andx(func_get_args());
1081
        }
1082
1083 44
        return $this->add('where', $predicates);
1084
    }
1085
1086
    /**
1087
     * Adds one or more restrictions to the query results, forming a logical
1088
     * conjunction with any previously specified restrictions.
1089
     *
1090
     * <code>
1091
     *     $qb = $em->createQueryBuilder()
1092
     *         ->select('u')
1093
     *         ->from('User', 'u')
1094
     *         ->where('u.username LIKE ?')
1095
     *         ->andWhere('u.is_active = 1');
1096
     * </code>
1097
     *
1098
     * @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...
1099
     *
1100
     * @return self
1101
     *
1102
     * @see where()
1103
     */
1104 17 View Code Duplication
    public function andWhere()
1105
    {
1106 17
        $args  = func_get_args();
1107 17
        $where = $this->getDQLPart('where');
1108
1109 17
        if ($where instanceof Expr\Andx) {
1110 6
            $where->addMultiple($args);
1111
        } else {
1112 12
            array_unshift($args, $where);
1113 12
            $where = new Expr\Andx($args);
1114
        }
1115
1116 17
        return $this->add('where', $where);
1117
    }
1118
1119
    /**
1120
     * Adds one or more restrictions to the query results, forming a logical
1121
     * disjunction with any previously specified restrictions.
1122
     *
1123
     * <code>
1124
     *     $qb = $em->createQueryBuilder()
1125
     *         ->select('u')
1126
     *         ->from('User', 'u')
1127
     *         ->where('u.id = 1')
1128
     *         ->orWhere('u.id = 2');
1129
     * </code>
1130
     *
1131
     * @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...
1132
     *
1133
     * @return self
1134
     *
1135
     * @see where()
1136
     */
1137 5 View Code Duplication
    public function orWhere()
1138
    {
1139 5
        $args  = func_get_args();
1140 5
        $where = $this->getDQLPart('where');
1141
1142 5
        if ($where instanceof Expr\Orx) {
1143
            $where->addMultiple($args);
1144
        } else {
1145 5
            array_unshift($args, $where);
1146 5
            $where = new Expr\Orx($args);
1147
        }
1148
1149 5
        return $this->add('where', $where);
1150
    }
1151
1152
    /**
1153
     * Specifies a grouping over the results of the query.
1154
     * Replaces any previously specified groupings, if any.
1155
     *
1156
     * <code>
1157
     *     $qb = $em->createQueryBuilder()
1158
     *         ->select('u')
1159
     *         ->from('User', 'u')
1160
     *         ->groupBy('u.id');
1161
     * </code>
1162
     *
1163
     * @param string $groupBy The grouping expression.
1164
     *
1165
     * @return self
1166
     */
1167 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...
1168
    {
1169 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1170
    }
1171
1172
    /**
1173
     * Adds a grouping expression to the query.
1174
     *
1175
     * <code>
1176
     *     $qb = $em->createQueryBuilder()
1177
     *         ->select('u')
1178
     *         ->from('User', 'u')
1179
     *         ->groupBy('u.lastLogin')
1180
     *         ->addGroupBy('u.createdAt');
1181
     * </code>
1182
     *
1183
     * @param string $groupBy The grouping expression.
1184
     *
1185
     * @return self
1186
     */
1187 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...
1188
    {
1189 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1190
    }
1191
1192
    /**
1193
     * Specifies a restriction over the groups of the query.
1194
     * Replaces any previous having restrictions, if any.
1195
     *
1196
     * @param mixed $having The restriction over the groups.
1197
     *
1198
     * @return self
1199
     */
1200 3 View Code Duplication
    public function having($having)
1201
    {
1202 3
        if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1203 3
            $having = new Expr\Andx(func_get_args());
1204
        }
1205
1206 3
        return $this->add('having', $having);
1207
    }
1208
1209
    /**
1210
     * Adds a restriction over the groups of the query, forming a logical
1211
     * conjunction with any existing having restrictions.
1212
     *
1213
     * @param mixed $having The restriction to append.
1214
     *
1215
     * @return self
1216
     */
1217 2 View Code Duplication
    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...
1218
    {
1219 2
        $args   = func_get_args();
1220 2
        $having = $this->getDQLPart('having');
1221
1222 2
        if ($having instanceof Expr\Andx) {
1223 2
            $having->addMultiple($args);
1224
        } else {
1225
            array_unshift($args, $having);
1226
            $having = new Expr\Andx($args);
1227
        }
1228
1229 2
        return $this->add('having', $having);
1230
    }
1231
1232
    /**
1233
     * Adds a restriction over the groups of the query, forming a logical
1234
     * disjunction with any existing having restrictions.
1235
     *
1236
     * @param mixed $having The restriction to add.
1237
     *
1238
     * @return self
1239
     */
1240 1 View Code Duplication
    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...
1241
    {
1242 1
        $args   = func_get_args();
1243 1
        $having = $this->getDQLPart('having');
1244
1245 1
        if ($having instanceof Expr\Orx) {
1246
            $having->addMultiple($args);
1247
        } else {
1248 1
            array_unshift($args, $having);
1249 1
            $having = new Expr\Orx($args);
1250
        }
1251
1252 1
        return $this->add('having', $having);
1253
    }
1254
1255
    /**
1256
     * Specifies an ordering for the query results.
1257
     * Replaces any previously specified orderings, if any.
1258
     *
1259
     * @param string|Expr\OrderBy $sort  The ordering expression.
1260
     * @param string              $order The ordering direction.
1261
     *
1262
     * @return self
1263
     */
1264 10
    public function orderBy($sort, $order = null)
1265
    {
1266 10
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1267
1268 10
        return $this->add('orderBy', $orderBy);
1269
    }
1270
1271
    /**
1272
     * Adds an ordering to the query results.
1273
     *
1274
     * @param string|Expr\OrderBy $sort  The ordering expression.
1275
     * @param string              $order The ordering direction.
1276
     *
1277
     * @return self
1278
     */
1279 4
    public function addOrderBy($sort, $order = null)
1280
    {
1281 4
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1282
1283 4
        return $this->add('orderBy', $orderBy, true);
1284
    }
1285
1286
    /**
1287
     * Adds criteria to the query.
1288
     *
1289
     * Adds where expressions with AND operator.
1290
     * Adds orderings.
1291
     * Overrides firstResult and maxResults if they're set.
1292
     *
1293
     * @param Criteria $criteria
1294
     *
1295
     * @return self
1296
     *
1297
     * @throws Query\QueryException
1298
     */
1299 13
    public function addCriteria(Criteria $criteria)
1300
    {
1301 13
        $allAliases = $this->getAllAliases();
1302 13
        if ( ! isset($allAliases[0])) {
1303
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1304
        }
1305
1306 13
        $visitor = new QueryExpressionVisitor($this->getAllAliases());
1307
1308 13
        if ($whereExpression = $criteria->getWhereExpression()) {
1309 9
            $this->andWhere($visitor->dispatch($whereExpression));
1310 9
            foreach ($visitor->getParameters() as $parameter) {
1311 9
                $this->parameters->add($parameter);
1312
            }
1313
        }
1314
1315 13
        if ($criteria->getOrderings()) {
1316 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1317
1318 2
                $hasValidAlias = false;
1319 2
                foreach($allAliases as $alias) {
1320 2
                    if(strpos($sort . '.', $alias . '.') === 0) {
1321 1
                        $hasValidAlias = true;
1322 2
                        break;
1323
                    }
1324
                }
1325
1326 2
                if(!$hasValidAlias) {
1327 1
                    $sort = $allAliases[0] . '.' . $sort;
1328
                }
1329
1330 2
                $this->addOrderBy($sort, $order);
1331
            }
1332
        }
1333
1334
        // Overwrite limits only if they was set in criteria
1335 13
        if (($firstResult = $criteria->getFirstResult()) !== null) {
1336 1
            $this->setFirstResult($firstResult);
1337
        }
1338 13
        if (($maxResults = $criteria->getMaxResults()) !== null) {
1339 1
            $this->setMaxResults($maxResults);
1340
        }
1341
1342 13
        return $this;
1343
    }
1344
1345
    /**
1346
     * Gets a query part by its name.
1347
     *
1348
     * @param string $queryPartName
1349
     *
1350
     * @return mixed $queryPart
1351
     *
1352
     * @todo Rename: getQueryPart (or remove?)
1353
     */
1354 100
    public function getDQLPart($queryPartName)
1355
    {
1356 100
        return $this->_dqlParts[$queryPartName];
1357
    }
1358
1359
    /**
1360
     * Gets all query parts.
1361
     *
1362
     * @return array $dqlParts
1363
     *
1364
     * @todo Rename: getQueryParts (or remove?)
1365
     */
1366 1
    public function getDQLParts()
1367
    {
1368 1
        return $this->_dqlParts;
1369
    }
1370
1371
    /**
1372
     * @return string
1373
     */
1374 1
    private function _getDQLForDelete()
1375
    {
1376
         return 'DELETE'
1377 1
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1378 1
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1379 1
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1380
    }
1381
1382
    /**
1383
     * @return string
1384
     */
1385 3
    private function _getDQLForUpdate()
1386
    {
1387
         return 'UPDATE'
1388 3
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1389 3
              . $this->_getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
1390 3
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1391 3
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1392
    }
1393
1394
    /**
1395
     * @return string
1396
     */
1397 82
    private function _getDQLForSelect()
1398
    {
1399
        $dql = 'SELECT'
1400 82
             . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
1401 82
             . $this->_getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
1402
1403 82
        $fromParts   = $this->getDQLPart('from');
1404 82
        $joinParts   = $this->getDQLPart('join');
1405 82
        $fromClauses = [];
1406
1407
        // Loop through all FROM clauses
1408 82
        if ( ! empty($fromParts)) {
1409 81
            $dql .= ' FROM ';
1410
1411 81
            foreach ($fromParts as $from) {
1412 81
                $fromClause = (string) $from;
1413
1414 81
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1415 26
                    foreach ($joinParts[$from->getAlias()] as $join) {
1416 26
                        $fromClause .= ' ' . ((string) $join);
1417
                    }
1418
                }
1419
1420 81
                $fromClauses[] = $fromClause;
1421
            }
1422
        }
1423
1424 82
        $dql .= implode(', ', $fromClauses)
1425 82
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1426 82
              . $this->_getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
1427 82
              . $this->_getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
1428 82
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1429
1430 82
        return $dql;
1431
    }
1432
1433
    /**
1434
     * @param string $queryPartName
1435
     * @param array  $options
1436
     *
1437
     * @return string
1438
     */
1439 85
    private function _getReducedDQLQueryPart($queryPartName, $options = [])
1440
    {
1441 85
        $queryPart = $this->getDQLPart($queryPartName);
1442
1443 85
        if (empty($queryPart)) {
1444 85
            return ($options['empty'] ?? '');
1445
        }
1446
1447 85
        return ($options['pre'] ?? '')
1448 85
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1449 85
             . ($options['post'] ?? '');
1450
    }
1451
1452
    /**
1453
     * Resets DQL parts.
1454
     *
1455
     * @param array|null $parts
1456
     *
1457
     * @return self
1458
     */
1459 2
    public function resetDQLParts($parts = null)
1460
    {
1461 2
        if (null === $parts) {
1462 1
            $parts = array_keys($this->_dqlParts);
1463
        }
1464
1465 2
        foreach ($parts as $part) {
1466 2
            $this->resetDQLPart($part);
1467
        }
1468
1469 2
        return $this;
1470
    }
1471
1472
    /**
1473
     * Resets single DQL part.
1474
     *
1475
     * @param string $part
1476
     *
1477
     * @return self
1478
     */
1479 3
    public function resetDQLPart($part)
1480
    {
1481 3
        $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? [] : null;
1482 3
        $this->_state           = self::STATE_DIRTY;
1483
1484 3
        return $this;
1485
    }
1486
1487
    /**
1488
     * Gets a string representation of this QueryBuilder which corresponds to
1489
     * the final DQL query being constructed.
1490
     *
1491
     * @return string The string representation of this QueryBuilder.
1492
     */
1493 5
    public function __toString()
1494
    {
1495 5
        return $this->getDQL();
1496
    }
1497
1498
    /**
1499
     * Deep clones all expression objects in the DQL parts.
1500
     *
1501
     * @return void
1502
     */
1503 3
    public function __clone()
1504
    {
1505 3
        foreach ($this->_dqlParts as $part => $elements) {
1506 3
            if (is_array($this->_dqlParts[$part])) {
1507 3
                foreach ($this->_dqlParts[$part] as $idx => $element) {
1508 2
                    if (is_object($element)) {
1509 3
                        $this->_dqlParts[$part][$idx] = clone $element;
1510
                    }
1511
                }
1512 3
            } else if (is_object($elements)) {
1513 3
                $this->_dqlParts[$part] = clone $elements;
1514
            }
1515
        }
1516
1517 3
        $parameters = [];
1518
1519 3
        foreach ($this->parameters as $parameter) {
1520 1
            $parameters[] = clone $parameter;
1521
        }
1522
1523 3
        $this->parameters = new ArrayCollection($parameters);
1524 3
    }
1525
}
1526