Failed Conditions
Pull Request — 2.6 (#7180)
by Ben
11:16
created

QueryBuilder::__clone()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 11
nc 8
nop 0
dl 0
loc 21
ccs 12
cts 12
cp 1
crap 7
rs 7.551
c 0
b 0
f 0
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 130
    public function __construct(EntityManagerInterface $em)
153
    {
154 130
        $this->_em = $em;
155 130
        $this->parameters = new ArrayCollection();
156 130
    }
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 90
    public function getDQL()
310
    {
311 90
        if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
312 44
            return $this->_dql;
313
        }
314
315 90
        switch ($this->_type) {
316 90
            case self::DELETE:
317 1
                $dql = $this->_getDQLForDelete();
318 1
                break;
319
320 89
            case self::UPDATE:
321 3
                $dql = $this->_getDQLForUpdate();
322 3
                break;
323
324 87
            case self::SELECT:
325
            default:
326 87
                $dql = $this->_getDQLForSelect();
327 87
                break;
328
        }
329
330 90
        $this->_state = self::STATE_CLEAN;
331 90
        $this->_dql   = $dql;
332
333 90
        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 79
    public function getQuery()
350
    {
351 79
        $parameters = clone $this->parameters;
352 79
        $query      = $this->_em->createQuery($this->getDQL())
353 79
            ->setParameters($parameters)
354 79
            ->setFirstResult($this->_firstResult)
355 79
            ->setMaxResults($this->_maxResults);
356
357 79
        if ($this->lifetime) {
358 1
            $query->setLifetime($this->lifetime);
359
        }
360
361 79
        if ($this->cacheMode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheMode of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. 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 79
        if ($this->cacheable) {
366 1
            $query->setCacheable($this->cacheable);
367
        }
368
369 79
        if ($this->cacheRegion) {
370 1
            $query->setCacheRegion($this->cacheRegion);
371
        }
372
373 79
        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;
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 function Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated: Please use $qb->getRootAliases() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

395
            $rootAlias = /** @scrutinizer ignore-deprecated */ $this->getRootAlias();

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

The explanatory message should give you some clue as to whether and when the function will be removed and what other function 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
    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
    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 20
    public function setParameter($key, $value, $type = null)
534
    {
535 20
        $existingParameter = $this->getParameter($key);
536
537 20
        if ($existingParameter !== null) {
538 1
            $existingParameter->setValue($value, $type);
539
540 1
            return $this;
541
        }
542
543 20
        $this->parameters->add(new Query\Parameter($key, $value, $type));
544
545 20
        return $this;
546
    }
547
548
    /**
549
     * Sets a collection of query parameters for the query being constructed.
550
     *
551
     * <code>
552
     *     $qb = $em->createQueryBuilder()
553
     *         ->select('u')
554
     *         ->from('User', 'u')
555
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
556
     *         ->setParameters(new ArrayCollection(array(
557
     *             new Parameter('user_id1', 1),
558
     *             new Parameter('user_id2', 2)
559
     *        )));
560
     * </code>
561
     *
562
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters to set.
563
     *
564
     * @return self
565
     */
566 4
    public function setParameters($parameters)
567
    {
568
        // BC compatibility with 2.3-
569 4
        if (is_array($parameters)) {
570 1
            $parameterCollection = new ArrayCollection();
571
572 1
            foreach ($parameters as $key => $value) {
573 1
                $parameter = new Query\Parameter($key, $value);
574
575 1
                $parameterCollection->add($parameter);
576
            }
577
578 1
            $parameters = $parameterCollection;
579
        }
580
581 4
        $this->parameters = $parameters;
582
583 4
        return $this;
584
    }
585
586
    /**
587
     * Gets all defined query parameters for the query being constructed.
588
     *
589
     * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
590
     */
591 6
    public function getParameters()
592
    {
593 6
        return $this->parameters;
594
    }
595
596
    /**
597
     * Gets a (previously set) query parameter of the query being constructed.
598
     *
599
     * @param mixed $key The key (index or name) of the bound parameter.
600
     *
601
     * @return Query\Parameter|null The value of the bound parameter.
602
     */
603 30
    public function getParameter($key)
604
    {
605 30
        $filteredParameters = $this->parameters->filter(
606 30
            function (Query\Parameter $parameter) use ($key) : bool {
607 19
                $parameterName = $parameter->getName();
608
609 19
                return $key === $parameterName || (string) $key === (string) $parameterName;
610 30
            }
611
        );
612
613 30
        return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
614
    }
615
616
    /**
617
     * Sets the position of the first result to retrieve (the "offset").
618
     *
619
     * @param integer $firstResult The first result to return.
620
     *
621
     * @return self
622
     */
623 2
    public function setFirstResult($firstResult)
624
    {
625 2
        $this->_firstResult = $firstResult;
626
627 2
        return $this;
628
    }
629
630
    /**
631
     * Gets the position of the first result the query object was set to retrieve (the "offset").
632
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
633
     *
634
     * @return integer The position of the first result.
635
     */
636 2
    public function getFirstResult()
637
    {
638 2
        return $this->_firstResult;
639
    }
640
641
    /**
642
     * Sets the maximum number of results to retrieve (the "limit").
643
     *
644
     * @param integer|null $maxResults The maximum number of results to retrieve.
645
     *
646
     * @return self
647
     */
648 3
    public function setMaxResults($maxResults)
649
    {
650 3
        $this->_maxResults = $maxResults;
651
652 3
        return $this;
653
    }
654
655
    /**
656
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
657
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
658
     *
659
     * @return integer|null Maximum number of results.
660
     */
661 2
    public function getMaxResults()
662
    {
663 2
        return $this->_maxResults;
664
    }
665
666
    /**
667
     * Either appends to or replaces a single, generic query part.
668
     *
669
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
670
     * 'groupBy', 'having' and 'orderBy'.
671
     *
672
     * @param string       $dqlPartName The DQL part name.
673
     * @param object|array $dqlPart     An Expr object.
674
     * @param bool         $append      Whether to append (true) or replace (false).
675
     *
676
     * @return self
677
     */
678 126
    public function add($dqlPartName, $dqlPart, $append = false)
679
    {
680 126
        if ($append && ($dqlPartName === "where" || $dqlPartName === "having")) {
681 1
            throw new \InvalidArgumentException(
682
                "Using \$append = true does not have an effect with 'where' or 'having' ".
683 1
                "parts. See QueryBuilder#andWhere() for an example for correct usage."
684
            );
685
        }
686
687 126
        $isMultiple = is_array($this->_dqlParts[$dqlPartName])
688 126
            && !($dqlPartName == 'join' && !$append);
689
690
        // Allow adding any part retrieved from self::getDQLParts().
691 126
        if (is_array($dqlPart) && $dqlPartName != 'join') {
692 1
            $dqlPart = reset($dqlPart);
693
        }
694
695
        // This is introduced for backwards compatibility reasons.
696
        // TODO: Remove for 3.0
697 126
        if ($dqlPartName == 'join') {
698 31
            $newDqlPart = [];
699
700 31
            foreach ($dqlPart as $k => $v) {
701 31
                $k = is_numeric($k) ? $this->getRootAlias() : $k;
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated: Please use $qb->getRootAliases() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

701
                $k = is_numeric($k) ? /** @scrutinizer ignore-deprecated */ $this->getRootAlias() : $k;

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

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

Loading history...
702
703 31
                $newDqlPart[$k] = $v;
704
            }
705
706 31
            $dqlPart = $newDqlPart;
707
        }
708
709 126
        if ($append && $isMultiple) {
710 120
            if (is_array($dqlPart)) {
711 31
                $key = key($dqlPart);
712
713 31
                $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
714
            } else {
715 120
                $this->_dqlParts[$dqlPartName][] = $dqlPart;
716
            }
717
        } else {
718 124
            $this->_dqlParts[$dqlPartName] = ($isMultiple) ? [$dqlPart] : $dqlPart;
719
        }
720
721 126
        $this->_state = self::STATE_DIRTY;
722
723 126
        return $this;
724
    }
725
726
    /**
727
     * Specifies an item that is to be returned in the query result.
728
     * Replaces any previously specified selections, if any.
729
     *
730
     * <code>
731
     *     $qb = $em->createQueryBuilder()
732
     *         ->select('u', 'p')
733
     *         ->from('User', 'u')
734
     *         ->leftJoin('u.Phonenumbers', 'p');
735
     * </code>
736
     *
737
     * @param mixed $select The selection expressions.
738
     *
739
     * @return self
740
     */
741 119
    public function select($select = null)
742
    {
743 119
        $this->_type = self::SELECT;
744
745 119
        if (empty($select)) {
746 1
            return $this;
747
        }
748
749 118
        $selects = is_array($select) ? $select : func_get_args();
750
751 118
        return $this->add('select', new Expr\Select($selects), false);
752
    }
753
754
    /**
755
     * Adds a DISTINCT flag to this query.
756
     *
757
     * <code>
758
     *     $qb = $em->createQueryBuilder()
759
     *         ->select('u')
760
     *         ->distinct()
761
     *         ->from('User', 'u');
762
     * </code>
763
     *
764
     * @param bool $flag
765
     *
766
     * @return self
767
     */
768 1
    public function distinct($flag = true)
769
    {
770 1
        $this->_dqlParts['distinct'] = (bool) $flag;
771
772 1
        return $this;
773
    }
774
775
    /**
776
     * Adds an item that is to be returned in the query result.
777
     *
778
     * <code>
779
     *     $qb = $em->createQueryBuilder()
780
     *         ->select('u')
781
     *         ->addSelect('p')
782
     *         ->from('User', 'u')
783
     *         ->leftJoin('u.Phonenumbers', 'p');
784
     * </code>
785
     *
786
     * @param mixed $select The selection expression.
787
     *
788
     * @return self
789
     */
790 1
    public function addSelect($select = null)
791
    {
792 1
        $this->_type = self::SELECT;
793
794 1
        if (empty($select)) {
795
            return $this;
796
        }
797
798 1
        $selects = is_array($select) ? $select : func_get_args();
799
800 1
        return $this->add('select', new Expr\Select($selects), true);
801
    }
802
803
    /**
804
     * Turns the query being built into a bulk delete query that ranges over
805
     * a certain entity type.
806
     *
807
     * <code>
808
     *     $qb = $em->createQueryBuilder()
809
     *         ->delete('User', 'u')
810
     *         ->where('u.id = :user_id')
811
     *         ->setParameter('user_id', 1);
812
     * </code>
813
     *
814
     * @param string $delete The class/type whose instances are subject to the deletion.
815
     * @param string $alias  The class/type alias used in the constructed query.
816
     *
817
     * @return self
818
     */
819 4
    public function delete($delete = null, $alias = null)
820
    {
821 4
        $this->_type = self::DELETE;
822
823 4
        if ( ! $delete) {
824 1
            return $this;
825
        }
826
827 3
        return $this->add('from', new Expr\From($delete, $alias));
828
    }
829
830
    /**
831
     * Turns the query being built into a bulk update query that ranges over
832
     * a certain entity type.
833
     *
834
     * <code>
835
     *     $qb = $em->createQueryBuilder()
836
     *         ->update('User', 'u')
837
     *         ->set('u.password', '?1')
838
     *         ->where('u.id = ?2');
839
     * </code>
840
     *
841
     * @param string $update The class/type whose instances are subject to the update.
842
     * @param string $alias  The class/type alias used in the constructed query.
843
     *
844
     * @return self
845
     */
846 4
    public function update($update = null, $alias = null)
847
    {
848 4
        $this->_type = self::UPDATE;
849
850 4
        if ( ! $update) {
851 1
            return $this;
852
        }
853
854 3
        return $this->add('from', new Expr\From($update, $alias));
855
    }
856
857
    /**
858
     * Creates and adds a query root corresponding to the entity identified by the given alias,
859
     * forming a cartesian product with any existing query roots.
860
     *
861
     * <code>
862
     *     $qb = $em->createQueryBuilder()
863
     *         ->select('u')
864
     *         ->from('User', 'u');
865
     * </code>
866
     *
867
     * @param string $from    The class name.
868
     * @param string $alias   The alias of the class.
869
     * @param string $indexBy The index for the from.
870
     *
871
     * @return self
872
     */
873 118
    public function from($from, $alias, $indexBy = null)
874
    {
875 118
        return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
876
    }
877
878
    /**
879
     * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
880
     * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
881
     * setting an index by.
882
     *
883
     * <code>
884
     *     $qb = $userRepository->createQueryBuilder('u')
885
     *         ->indexBy('u', 'u.id');
886
     *
887
     *     // Is equivalent to...
888
     *
889
     *     $qb = $em->createQueryBuilder()
890
     *         ->select('u')
891
     *         ->from('User', 'u', 'u.id');
892
     * </code>
893
     *
894
     * @param string $alias   The root alias of the class.
895
     * @param string $indexBy The index for the from.
896
     *
897
     * @return self
898
     *
899
     * @throws Query\QueryException
900
     */
901 2
    public function indexBy($alias, $indexBy)
902
    {
903 2
        $rootAliases = $this->getRootAliases();
904
905 2
        if (!in_array($alias, $rootAliases)) {
906
            throw new Query\QueryException(
907
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
908
            );
909
        }
910
911 2
        foreach ($this->_dqlParts['from'] as &$fromClause) {
912
            /* @var Expr\From $fromClause */
913 2
            if ($fromClause->getAlias() !== $alias) {
914 1
                continue;
915
            }
916
917 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
918
        }
919
920 2
        return $this;
921
    }
922
923
    /**
924
     * Creates and adds a join over an entity association to the query.
925
     *
926
     * The entities in the joined association will be fetched as part of the query
927
     * result if the alias used for the joined association is placed in the select
928
     * expressions.
929
     *
930
     * <code>
931
     *     $qb = $em->createQueryBuilder()
932
     *         ->select('u')
933
     *         ->from('User', 'u')
934
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
935
     * </code>
936
     *
937
     * @param string      $join          The relationship to join.
938
     * @param string      $alias         The alias of the join.
939
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
940
     * @param string|null $condition     The condition for the join.
941
     * @param string|null $indexBy       The index for the join.
942
     *
943
     * @return self
944
     */
945 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
946
    {
947 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
948
    }
949
950
    /**
951
     * Creates and adds a join over an entity association to the query.
952
     *
953
     * The entities in the joined association will be fetched as part of the query
954
     * result if the alias used for the joined association is placed in the select
955
     * expressions.
956
     *
957
     *     [php]
958
     *     $qb = $em->createQueryBuilder()
959
     *         ->select('u')
960
     *         ->from('User', 'u')
961
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
962
     *
963
     * @param string      $join          The relationship to join.
964
     * @param string      $alias         The alias of the join.
965
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
966
     * @param string|null $condition     The condition for the join.
967
     * @param string|null $indexBy       The index for the join.
968
     *
969
     * @return self
970
     */
971 16
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
972
    {
973 16
        $parentAlias = substr($join, 0, strpos($join, '.'));
974
975 16
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
976
977 16
        $join = new Expr\Join(
978 16
            Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
979
        );
980
981 16
        return $this->add('join', [$rootAlias => $join], true);
982
    }
983
984
    /**
985
     * Creates and adds a left join over an entity association to the query.
986
     *
987
     * The entities in the joined association will be fetched as part of the query
988
     * result if the alias used for the joined association is placed in the select
989
     * expressions.
990
     *
991
     * <code>
992
     *     $qb = $em->createQueryBuilder()
993
     *         ->select('u')
994
     *         ->from('User', 'u')
995
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
996
     * </code>
997
     *
998
     * @param string      $join          The relationship to join.
999
     * @param string      $alias         The alias of the join.
1000
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1001
     * @param string|null $condition     The condition for the join.
1002
     * @param string|null $indexBy       The index for the join.
1003
     *
1004
     * @return self
1005
     */
1006 15
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1007
    {
1008 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
1009
1010 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
1011
1012 15
        $join = new Expr\Join(
1013 15
            Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
1014
        );
1015
1016 15
        return $this->add('join', [$rootAlias => $join], true);
1017
    }
1018
1019
    /**
1020
     * Sets a new value for a field in a bulk update query.
1021
     *
1022
     * <code>
1023
     *     $qb = $em->createQueryBuilder()
1024
     *         ->update('User', 'u')
1025
     *         ->set('u.password', '?1')
1026
     *         ->where('u.id = ?2');
1027
     * </code>
1028
     *
1029
     * @param string $key   The key/field to set.
1030
     * @param string $value The value, expression, placeholder, etc.
1031
     *
1032
     * @return self
1033
     */
1034 3
    public function set($key, $value)
1035
    {
1036 3
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1037
    }
1038
1039
    /**
1040
     * Specifies one or more restrictions to the query result.
1041
     * Replaces any previously specified restrictions, if any.
1042
     *
1043
     * <code>
1044
     *     $qb = $em->createQueryBuilder()
1045
     *         ->select('u')
1046
     *         ->from('User', 'u')
1047
     *         ->where('u.id = ?');
1048
     *
1049
     *     // You can optionally programmatically build and/or expressions
1050
     *     $qb = $em->createQueryBuilder();
1051
     *
1052
     *     $or = $qb->expr()->orX();
1053
     *     $or->add($qb->expr()->eq('u.id', 1));
1054
     *     $or->add($qb->expr()->eq('u.id', 2));
1055
     *
1056
     *     $qb->update('User', 'u')
1057
     *         ->set('u.password', '?')
1058
     *         ->where($or);
1059
     * </code>
1060
     *
1061
     * @param mixed $predicates The restriction predicates.
1062
     *
1063
     * @return self
1064
     */
1065 50
    public function where($predicates)
1066
    {
1067 50
        if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
1068 47
            $predicates = new Expr\Andx(func_get_args());
1069
        }
1070
1071 50
        return $this->add('where', $predicates);
1072
    }
1073
1074
    /**
1075
     * Adds one or more restrictions to the query results, forming a logical
1076
     * conjunction with any previously specified restrictions.
1077
     *
1078
     * <code>
1079
     *     $qb = $em->createQueryBuilder()
1080
     *         ->select('u')
1081
     *         ->from('User', 'u')
1082
     *         ->where('u.username LIKE ?')
1083
     *         ->andWhere('u.is_active = 1');
1084
     * </code>
1085
     *
1086
     * @param mixed $where The query restrictions.
1087
     *
1088
     * @return self
1089
     *
1090
     * @see where()
1091
     */
1092 23
    public function andWhere()
1093
    {
1094 23
        $args  = func_get_args();
1095 23
        $where = $this->getDQLPart('where');
1096
1097 23
        if ($where instanceof Expr\Andx) {
1098 11
            $where->addMultiple($args);
1099
        } else {
1100 15
            array_unshift($args, $where);
1101 15
            $where = new Expr\Andx($args);
1102
        }
1103
1104 23
        return $this->add('where', $where);
1105
    }
1106
1107
    /**
1108
     * Adds one or more restrictions to the query results, forming a logical
1109
     * disjunction with any previously specified restrictions.
1110
     *
1111
     * <code>
1112
     *     $qb = $em->createQueryBuilder()
1113
     *         ->select('u')
1114
     *         ->from('User', 'u')
1115
     *         ->where('u.id = 1')
1116
     *         ->orWhere('u.id = 2');
1117
     * </code>
1118
     *
1119
     * @param mixed $where The WHERE statement.
1120
     *
1121
     * @return self
1122
     *
1123
     * @see where()
1124
     */
1125 5
    public function orWhere()
1126
    {
1127 5
        $args  = func_get_args();
1128 5
        $where = $this->getDQLPart('where');
1129
1130 5
        if ($where instanceof Expr\Orx) {
1131
            $where->addMultiple($args);
1132
        } else {
1133 5
            array_unshift($args, $where);
1134 5
            $where = new Expr\Orx($args);
1135
        }
1136
1137 5
        return $this->add('where', $where);
1138
    }
1139
1140
    /**
1141
     * Specifies a grouping over the results of the query.
1142
     * Replaces any previously specified groupings, if any.
1143
     *
1144
     * <code>
1145
     *     $qb = $em->createQueryBuilder()
1146
     *         ->select('u')
1147
     *         ->from('User', 'u')
1148
     *         ->groupBy('u.id');
1149
     * </code>
1150
     *
1151
     * @param string $groupBy The grouping expression.
1152
     *
1153
     * @return self
1154
     */
1155 7
    public function groupBy($groupBy)
1156
    {
1157 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1158
    }
1159
1160
    /**
1161
     * Adds a grouping expression to the query.
1162
     *
1163
     * <code>
1164
     *     $qb = $em->createQueryBuilder()
1165
     *         ->select('u')
1166
     *         ->from('User', 'u')
1167
     *         ->groupBy('u.lastLogin')
1168
     *         ->addGroupBy('u.createdAt');
1169
     * </code>
1170
     *
1171
     * @param string $groupBy The grouping expression.
1172
     *
1173
     * @return self
1174
     */
1175 1
    public function addGroupBy($groupBy)
1176
    {
1177 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1178
    }
1179
1180
    /**
1181
     * Specifies a restriction over the groups of the query.
1182
     * Replaces any previous having restrictions, if any.
1183
     *
1184
     * @param mixed $having The restriction over the groups.
1185
     *
1186
     * @return self
1187
     */
1188 3
    public function having($having)
1189
    {
1190 3
        if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1191 3
            $having = new Expr\Andx(func_get_args());
1192
        }
1193
1194 3
        return $this->add('having', $having);
1195
    }
1196
1197
    /**
1198
     * Adds a restriction over the groups of the query, forming a logical
1199
     * conjunction with any existing having restrictions.
1200
     *
1201
     * @param mixed $having The restriction to append.
1202
     *
1203
     * @return self
1204
     */
1205 2
    public function andHaving($having)
1206
    {
1207 2
        $args   = func_get_args();
1208 2
        $having = $this->getDQLPart('having');
1209
1210 2
        if ($having instanceof Expr\Andx) {
1211 2
            $having->addMultiple($args);
1212
        } else {
1213
            array_unshift($args, $having);
1214
            $having = new Expr\Andx($args);
1215
        }
1216
1217 2
        return $this->add('having', $having);
1218
    }
1219
1220
    /**
1221
     * Adds a restriction over the groups of the query, forming a logical
1222
     * disjunction with any existing having restrictions.
1223
     *
1224
     * @param mixed $having The restriction to add.
1225
     *
1226
     * @return self
1227
     */
1228 1
    public function orHaving($having)
1229
    {
1230 1
        $args   = func_get_args();
1231 1
        $having = $this->getDQLPart('having');
1232
1233 1
        if ($having instanceof Expr\Orx) {
1234
            $having->addMultiple($args);
1235
        } else {
1236 1
            array_unshift($args, $having);
1237 1
            $having = new Expr\Orx($args);
1238
        }
1239
1240 1
        return $this->add('having', $having);
1241
    }
1242
1243
    /**
1244
     * Specifies an ordering for the query results.
1245
     * Replaces any previously specified orderings, if any.
1246
     *
1247
     * @param string|Expr\OrderBy $sort  The ordering expression.
1248
     * @param string              $order The ordering direction.
1249
     *
1250
     * @return self
1251
     */
1252 10
    public function orderBy($sort, $order = null)
1253
    {
1254 10
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1255
1256 10
        return $this->add('orderBy', $orderBy);
1257
    }
1258
1259
    /**
1260
     * Adds an ordering to the query results.
1261
     *
1262
     * @param string|Expr\OrderBy $sort  The ordering expression.
1263
     * @param string              $order The ordering direction.
1264
     *
1265
     * @return self
1266
     */
1267 4
    public function addOrderBy($sort, $order = null)
1268
    {
1269 4
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1270
1271 4
        return $this->add('orderBy', $orderBy, true);
1272
    }
1273
1274
    /**
1275
     * Adds criteria to the query.
1276
     *
1277
     * Adds where expressions with AND operator.
1278
     * Adds orderings.
1279
     * Overrides firstResult and maxResults if they're set.
1280
     *
1281
     * @param Criteria $criteria
1282
     *
1283
     * @return self
1284
     *
1285
     * @throws Query\QueryException
1286
     */
1287 13
    public function addCriteria(Criteria $criteria)
1288
    {
1289 13
        $allAliases = $this->getAllAliases();
1290 13
        if ( ! isset($allAliases[0])) {
1291
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1292
        }
1293
1294 13
        $visitor = new QueryExpressionVisitor($this->getAllAliases());
1295
1296 13
        if ($whereExpression = $criteria->getWhereExpression()) {
1297 9
            $this->andWhere($visitor->dispatch($whereExpression));
1298 9
            foreach ($visitor->getParameters() as $parameter) {
1299 9
                $this->parameters->add($parameter);
1300
            }
1301
        }
1302
1303 13
        if ($criteria->getOrderings()) {
1304 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1305
1306 2
                $hasValidAlias = false;
1307 2
                foreach($allAliases as $alias) {
1308 2
                    if(strpos($sort . '.', $alias . '.') === 0) {
1309 1
                        $hasValidAlias = true;
1310 2
                        break;
1311
                    }
1312
                }
1313
1314 2
                if(!$hasValidAlias) {
1315 1
                    $sort = $allAliases[0] . '.' . $sort;
1316
                }
1317
1318 2
                $this->addOrderBy($sort, $order);
1319
            }
1320
        }
1321
1322
        // Overwrite limits only if they was set in criteria
1323 13
        if (($firstResult = $criteria->getFirstResult()) !== null) {
1324 1
            $this->setFirstResult($firstResult);
1325
        }
1326 13
        if (($maxResults = $criteria->getMaxResults()) !== null) {
1327 1
            $this->setMaxResults($maxResults);
1328
        }
1329
1330 13
        return $this;
1331
    }
1332
1333
    /**
1334
     * Gets a query part by its name.
1335
     *
1336
     * @param string $queryPartName
1337
     *
1338
     * @return mixed $queryPart
1339
     *
1340
     * @todo Rename: getQueryPart (or remove?)
1341
     */
1342 108
    public function getDQLPart($queryPartName)
1343
    {
1344 108
        return $this->_dqlParts[$queryPartName];
1345
    }
1346
1347
    /**
1348
     * Gets all query parts.
1349
     *
1350
     * @return array $dqlParts
1351
     *
1352
     * @todo Rename: getQueryParts (or remove?)
1353
     */
1354 1
    public function getDQLParts()
1355
    {
1356 1
        return $this->_dqlParts;
1357
    }
1358
1359
    /**
1360
     * @return string
1361
     */
1362 1
    private function _getDQLForDelete()
1363
    {
1364
         return 'DELETE'
1365 1
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1366 1
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1367 1
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1368
    }
1369
1370
    /**
1371
     * @return string
1372
     */
1373 3
    private function _getDQLForUpdate()
1374
    {
1375
         return 'UPDATE'
1376 3
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1377 3
              . $this->_getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
1378 3
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1379 3
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1380
    }
1381
1382
    /**
1383
     * @return string
1384
     */
1385 87
    private function _getDQLForSelect()
1386
    {
1387
        $dql = 'SELECT'
1388 87
             . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
1389 87
             . $this->_getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
1390
1391 87
        $fromParts   = $this->getDQLPart('from');
1392 87
        $joinParts   = $this->getDQLPart('join');
1393 87
        $fromClauses = [];
1394
1395
        // Loop through all FROM clauses
1396 87
        if ( ! empty($fromParts)) {
1397 86
            $dql .= ' FROM ';
1398
1399 86
            foreach ($fromParts as $from) {
1400 86
                $fromClause = (string) $from;
1401
1402 86
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1403 26
                    foreach ($joinParts[$from->getAlias()] as $join) {
1404 26
                        $fromClause .= ' ' . ((string) $join);
1405
                    }
1406
                }
1407
1408 86
                $fromClauses[] = $fromClause;
1409
            }
1410
        }
1411
1412 87
        $dql .= implode(', ', $fromClauses)
1413 87
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1414 87
              . $this->_getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
1415 87
              . $this->_getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
1416 87
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1417
1418 87
        return $dql;
1419
    }
1420
1421
    /**
1422
     * @param string $queryPartName
1423
     * @param array  $options
1424
     *
1425
     * @return string
1426
     */
1427 90
    private function _getReducedDQLQueryPart($queryPartName, $options = [])
1428
    {
1429 90
        $queryPart = $this->getDQLPart($queryPartName);
1430
1431 90
        if (empty($queryPart)) {
1432 90
            return ($options['empty'] ?? '');
1433
        }
1434
1435 90
        return ($options['pre'] ?? '')
1436 90
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1437 90
             . ($options['post'] ?? '');
1438
    }
1439
1440
    /**
1441
     * Resets DQL parts.
1442
     *
1443
     * @param array|null $parts
1444
     *
1445
     * @return self
1446
     */
1447 2
    public function resetDQLParts($parts = null)
1448
    {
1449 2
        if (null === $parts) {
1450 1
            $parts = array_keys($this->_dqlParts);
1451
        }
1452
1453 2
        foreach ($parts as $part) {
1454 2
            $this->resetDQLPart($part);
1455
        }
1456
1457 2
        return $this;
1458
    }
1459
1460
    /**
1461
     * Resets single DQL part.
1462
     *
1463
     * @param string $part
1464
     *
1465
     * @return self
1466
     */
1467 3
    public function resetDQLPart($part)
1468
    {
1469 3
        $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? [] : null;
1470 3
        $this->_state           = self::STATE_DIRTY;
1471
1472 3
        return $this;
1473
    }
1474
1475
    /**
1476
     * Gets a string representation of this QueryBuilder which corresponds to
1477
     * the final DQL query being constructed.
1478
     *
1479
     * @return string The string representation of this QueryBuilder.
1480
     */
1481 5
    public function __toString()
1482
    {
1483 5
        return $this->getDQL();
1484
    }
1485
1486
    /**
1487
     * Deep clones all expression objects in the DQL parts.
1488
     *
1489
     * @return void
1490
     */
1491 3
    public function __clone()
1492
    {
1493 3
        foreach ($this->_dqlParts as $part => $elements) {
1494 3
            if (is_array($this->_dqlParts[$part])) {
1495 3
                foreach ($this->_dqlParts[$part] as $idx => $element) {
1496 2
                    if (is_object($element)) {
1497 3
                        $this->_dqlParts[$part][$idx] = clone $element;
1498
                    }
1499
                }
1500 3
            } else if (is_object($elements)) {
1501 3
                $this->_dqlParts[$part] = clone $elements;
1502
            }
1503
        }
1504
1505 3
        $parameters = [];
1506
1507 3
        foreach ($this->parameters as $parameter) {
1508 1
            $parameters[] = clone $parameter;
1509
        }
1510
1511 3
        $this->parameters = new ArrayCollection($parameters);
1512 3
    }
1513
}
1514