Failed Conditions
Pull Request — master (#7409)
by
unknown
11:42 queued 03:42
created

QueryBuilder::getAliasByJoin()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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

388
            $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...
389
        }
390
391 30
        $this->joinRootAliases[$alias] = $rootAlias;
392
393 30
        return $rootAlias;
394
    }
395
396
    /**
397
     * Gets the FIRST root alias of the query. This is the first entity alias involved
398
     * in the construction of the query.
399
     *
400
     * <code>
401
     * $qb = $em->createQueryBuilder()
402
     *     ->select('u')
403
     *     ->from('User', 'u');
404
     *
405
     * echo $qb->getRootAlias(); // u
406
     * </code>
407
     *
408
     * @deprecated Please use $qb->getRootAliases() instead.
409
     * @throws \RuntimeException
410
     *
411
     * @return string
412
     */
413 4
    public function getRootAlias()
414
    {
415 4
        $aliases = $this->getRootAliases();
416
417 4
        if (! isset($aliases[0])) {
418
            throw new \RuntimeException('No alias was set before invoking getRootAlias().');
419
        }
420
421 4
        return $aliases[0];
422
    }
423
424
    /**
425
     * Gets the root aliases of the query. This is the entity aliases involved
426
     * in the construction of the query.
427
     *
428
     * <code>
429
     *     $qb = $em->createQueryBuilder()
430
     *         ->select('u')
431
     *         ->from('User', 'u');
432
     *
433
     *     $qb->getRootAliases(); // array('u')
434
     * </code>
435
     *
436
     * @return string[]
437
     */
438 46
    public function getRootAliases()
439
    {
440 46
        $aliases = [];
441
442 46
        foreach ($this->dqlParts['from'] as &$fromClause) {
443 46
            if (is_string($fromClause)) {
444
                $spacePos = strrpos($fromClause, ' ');
445
                $from     = substr($fromClause, 0, $spacePos);
446
                $alias    = substr($fromClause, $spacePos + 1);
447
448
                $fromClause = new Query\Expr\From($from, $alias);
449
            }
450
451 46
            $aliases[] = $fromClause->getAlias();
452
        }
453
454 46
        return $aliases;
455
    }
456
457
    /**
458
     * Gets all the aliases that have been used in the query.
459
     * Including all select root aliases and join aliases
460
     *
461
     * <code>
462
     *     $qb = $em->createQueryBuilder()
463
     *         ->select('u')
464
     *         ->from('User', 'u')
465
     *         ->join('u.articles','a');
466
     *
467
     *     $qb->getAllAliases(); // array('u','a')
468
     * </code>
469
     *
470
     * @return string[]
471
     */
472 15
    public function getAllAliases()
473
    {
474 15
        return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases));
475
    }
476
477
    /**
478
     * Gives the alias of a join.
479
     *
480
     * <code>
481
     *     $qb = $em->createQueryBuilder()
482
     *         ->select('u')
483
     *         ->from('User', 'u')
484
     *         ->join('u.articles','a');
485
     *
486
     *     $qb->getAliasByJoin('u.articles'); // a
487
     * </code>
488
     *
489
     * @param string $join
0 ignored issues
show
introduced by
Method \Doctrine\ORM\QueryBuilder::getAliasByJoin() has useless @param annotation for parameter $join.
Loading history...
490
     *
491
     * @return bool|string
492
     */
493
    public function getAliasByJoin(QueryBuilder $qb, string $join)
0 ignored issues
show
Unused Code introduced by
The parameter $qb is not used and could be removed. ( Ignorable by Annotation )

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

493
    public function getAliasByJoin(/** @scrutinizer ignore-unused */ QueryBuilder $qb, string $join)

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

Loading history...
494
    {
495
        foreach ($this->getDQLPart('join') as $joinParts) {
496
            /** @var Join $joinPart */
497
            foreach ($joinParts as $rootAlias => $joinPart) {
498
                // Maybe skip if the root alias doesn't fit the join,
499
                // but probably this is not worth the effort
500
                if ($joinPart->getJoin() === $join) {
501
                    // Join found, return the alias
502
                    return $joinPart->getAlias();
503
                }
504
            }
505
        }
506
        // No join found, return false
507
        return false;
508
    }
509
510
    /**
511
     * Gets the root entities of the query. This is the entity aliases involved
512
     * in the construction of the query.
513
     *
514
     * <code>
515
     *     $qb = $em->createQueryBuilder()
516
     *         ->select('u')
517
     *         ->from('User', 'u');
518
     *
519
     *     $qb->getRootEntities(); // array('User')
520
     * </code>
521
     *
522
     * @return string[]
523
     */
524 1
    public function getRootEntities()
525
    {
526 1
        $entities = [];
527
528 1
        foreach ($this->dqlParts['from'] as &$fromClause) {
529 1
            if (is_string($fromClause)) {
530
                $spacePos = strrpos($fromClause, ' ');
531
                $from     = substr($fromClause, 0, $spacePos);
532
                $alias    = substr($fromClause, $spacePos + 1);
533
534
                $fromClause = new Query\Expr\From($from, $alias);
535
            }
536
537 1
            $entities[] = $fromClause->getFrom();
538
        }
539
540 1
        return $entities;
541
    }
542
543
    /**
544
     * Sets a query parameter for the query being constructed.
545
     *
546
     * <code>
547
     *     $qb = $em->createQueryBuilder()
548
     *         ->select('u')
549
     *         ->from('User', 'u')
550
     *         ->where('u.id = :user_id')
551
     *         ->setParameter('user_id', 1);
552
     * </code>
553
     *
554
     * @param string|int      $key   The parameter position or name.
555
     * @param mixed           $value The parameter value.
556
     * @param string|int|null $type  ParameterType::* or \Doctrine\DBAL\Types\Type::* constant
557
     *
558
     * @return self
559
     */
560 19
    public function setParameter($key, $value, $type = null)
561
    {
562 19
        $existingParameter = $this->getParameter($key);
563
564 19
        if ($existingParameter !== null) {
565 1
            $existingParameter->setValue($value, $type);
566
567 1
            return $this;
568
        }
569
570 19
        $this->parameters->add(new Query\Parameter($key, $value, $type));
571
572 19
        return $this;
573
    }
574
575
    /**
576
     * Sets a collection of query parameters for the query being constructed.
577
     *
578
     * <code>
579
     *     $qb = $em->createQueryBuilder()
580
     *         ->select('u')
581
     *         ->from('User', 'u')
582
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
583
     *         ->setParameters(new ArrayCollection(array(
584
     *             new Parameter('user_id1', 1),
585
     *             new Parameter('user_id2', 2)
586
     *        )));
587
     * </code>
588
     *
589
     * @param ArrayCollection|array|mixed[] $parameters The query parameters to set.
590
     *
591
     * @return self
592
     */
593 4
    public function setParameters($parameters)
594
    {
595
        // BC compatibility with 2.3-
596 4
        if (is_array($parameters)) {
597 1
            $parameterCollection = new ArrayCollection();
598
599 1
            foreach ($parameters as $key => $value) {
600 1
                $parameter = new Query\Parameter($key, $value);
601
602 1
                $parameterCollection->add($parameter);
603
            }
604
605 1
            $parameters = $parameterCollection;
606
        }
607
608 4
        $this->parameters = $parameters;
609
610 4
        return $this;
611
    }
612
613
    /**
614
     * Gets all defined query parameters for the query being constructed.
615
     *
616
     * @return ArrayCollection The currently defined query parameters.
617
     */
618 6
    public function getParameters()
619
    {
620 6
        return $this->parameters;
621
    }
622
623
    /**
624
     * Gets a (previously set) query parameter of the query being constructed.
625
     *
626
     * @param mixed $key The key (index or name) of the bound parameter.
627
     *
628
     * @return Query\Parameter|null The value of the bound parameter.
629
     */
630 29
    public function getParameter($key)
631
    {
632 29
        $filteredParameters = $this->parameters->filter(
633
            function (Query\Parameter $parameter) use ($key) : bool {
634 19
                $parameterName = $parameter->getName();
635
636 19
                return $key === $parameterName || (string) $key === (string) $parameterName;
637 29
            }
638
        );
639
640 29
        return $filteredParameters->isEmpty() ? null : $filteredParameters->first();
641
    }
642
643
    /**
644
     * Sets the position of the first result to retrieve (the "offset").
645
     *
646
     * @param int $firstResult The first result to return.
647
     *
648
     * @return self
649
     */
650 2
    public function setFirstResult($firstResult)
651
    {
652 2
        $this->firstResult = $firstResult;
653
654 2
        return $this;
655
    }
656
657
    /**
658
     * Gets the position of the first result the query object was set to retrieve (the "offset").
659
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
660
     *
661
     * @return int The position of the first result.
662
     */
663 2
    public function getFirstResult()
664
    {
665 2
        return $this->firstResult;
666
    }
667
668
    /**
669
     * Sets the maximum number of results to retrieve (the "limit").
670
     *
671
     * @param int|null $maxResults The maximum number of results to retrieve.
672
     *
673
     * @return self
674
     */
675 3
    public function setMaxResults($maxResults)
676
    {
677 3
        $this->maxResults = $maxResults;
678
679 3
        return $this;
680
    }
681
682
    /**
683
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
684
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
685
     *
686
     * @return int|null Maximum number of results.
687
     */
688 2
    public function getMaxResults()
689
    {
690 2
        return $this->maxResults;
691
    }
692
693
    /**
694
     * Either appends to or replaces a single, generic query part.
695
     *
696
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
697
     * 'groupBy', 'having' and 'orderBy'.
698
     *
699
     * @param string         $dqlPartName The DQL part name.
700
     * @param object|mixed[] $dqlPart     An Expr object.
701
     * @param bool           $append      Whether to append (true) or replace (false).
702
     *
703
     * @return self
704
     */
705 125
    public function add($dqlPartName, $dqlPart, $append = false)
706
    {
707 125
        if ($append && ($dqlPartName === 'where' || $dqlPartName === 'having')) {
708 1
            throw new \InvalidArgumentException(
709
                "Using \$append = true does not have an effect with 'where' or 'having' " .
710 1
                'parts. See QueryBuilder#andWhere() for an example for correct usage.'
711
            );
712
        }
713
714 125
        $isMultiple = is_array($this->dqlParts[$dqlPartName])
715 125
            && ! ($dqlPartName === 'join' && ! $append);
716
717
        // Allow adding any part retrieved from self::getDQLParts().
718 125
        if (is_array($dqlPart) && $dqlPartName !== 'join') {
719 1
            $dqlPart = reset($dqlPart);
720
        }
721
722
        // This is introduced for backwards compatibility reasons.
723
        // TODO: Remove for 3.0
724 125
        if ($dqlPartName === 'join') {
725 31
            $newDqlPart = [];
726
727 31
            foreach ($dqlPart as $k => $v) {
728 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

728
                $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...
729
730 31
                $newDqlPart[$k] = $v;
731
            }
732
733 31
            $dqlPart = $newDqlPart;
734
        }
735
736 125
        if ($append && $isMultiple) {
737 119
            if (is_array($dqlPart)) {
738 31
                $key = key($dqlPart);
739
740 31
                $this->dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
741
            } else {
742 119
                $this->dqlParts[$dqlPartName][] = $dqlPart;
743
            }
744
        } else {
745 123
            $this->dqlParts[$dqlPartName] = ($isMultiple) ? [$dqlPart] : $dqlPart;
746
        }
747
748 125
        $this->state = self::STATE_DIRTY;
749
750 125
        return $this;
751
    }
752
753
    /**
754
     * Specifies an item that is to be returned in the query result.
755
     * Replaces any previously specified selections, if any.
756
     *
757
     * <code>
758
     *     $qb = $em->createQueryBuilder()
759
     *         ->select('u', 'p')
760
     *         ->from('User', 'u')
761
     *         ->leftJoin('u.Phonenumbers', 'p');
762
     * </code>
763
     *
764
     * @param mixed $select The selection expressions.
765
     *
766
     * @return self
767
     */
768 118
    public function select($select = null)
769
    {
770 118
        $this->type = self::SELECT;
771
772 118
        if (empty($select)) {
773 1
            return $this;
774
        }
775
776 117
        $selects = is_array($select) ? $select : func_get_args();
777
778 117
        return $this->add('select', new Expr\Select($selects), false);
779
    }
780
781
    /**
782
     * Adds a DISTINCT flag to this query.
783
     *
784
     * <code>
785
     *     $qb = $em->createQueryBuilder()
786
     *         ->select('u')
787
     *         ->distinct()
788
     *         ->from('User', 'u');
789
     * </code>
790
     *
791
     * @param bool $flag
792
     *
793
     * @return self
794
     */
795 1
    public function distinct($flag = true)
796
    {
797 1
        $this->dqlParts['distinct'] = (bool) $flag;
798
799 1
        return $this;
800
    }
801
802
    /**
803
     * Adds an item that is to be returned in the query result.
804
     *
805
     * <code>
806
     *     $qb = $em->createQueryBuilder()
807
     *         ->select('u')
808
     *         ->addSelect('p')
809
     *         ->from('User', 'u')
810
     *         ->leftJoin('u.Phonenumbers', 'p');
811
     * </code>
812
     *
813
     * @param mixed $select The selection expression.
814
     *
815
     * @return self
816
     */
817 1
    public function addSelect($select = null)
818
    {
819 1
        $this->type = self::SELECT;
820
821 1
        if (empty($select)) {
822
            return $this;
823
        }
824
825 1
        $selects = is_array($select) ? $select : func_get_args();
826
827 1
        return $this->add('select', new Expr\Select($selects), true);
828
    }
829
830
    /**
831
     * Turns the query being built into a bulk delete query that ranges over
832
     * a certain entity type.
833
     *
834
     * <code>
835
     *     $qb = $em->createQueryBuilder()
836
     *         ->delete('User', 'u')
837
     *         ->where('u.id = :user_id')
838
     *         ->setParameter('user_id', 1);
839
     * </code>
840
     *
841
     * @param string $delete The class/type whose instances are subject to the deletion.
842
     * @param string $alias  The class/type alias used in the constructed query.
843
     *
844
     * @return self
845
     */
846 4
    public function delete($delete = null, $alias = null)
847
    {
848 4
        $this->type = self::DELETE;
849
850 4
        if (! $delete) {
851 1
            return $this;
852
        }
853
854 3
        return $this->add('from', new Expr\From($delete, $alias));
855
    }
856
857
    /**
858
     * Turns the query being built into a bulk update query that ranges over
859
     * a certain entity type.
860
     *
861
     * <code>
862
     *     $qb = $em->createQueryBuilder()
863
     *         ->update('User', 'u')
864
     *         ->set('u.password', '?1')
865
     *         ->where('u.id = ?2');
866
     * </code>
867
     *
868
     * @param string $update The class/type whose instances are subject to the update.
869
     * @param string $alias  The class/type alias used in the constructed query.
870
     *
871
     * @return self
872
     */
873 4
    public function update($update = null, $alias = null)
874
    {
875 4
        $this->type = self::UPDATE;
876
877 4
        if (! $update) {
878 1
            return $this;
879
        }
880
881 3
        return $this->add('from', new Expr\From($update, $alias));
882
    }
883
884
    /**
885
     * Creates and adds a query root corresponding to the entity identified by the given alias,
886
     * forming a cartesian product with any existing query roots.
887
     *
888
     * <code>
889
     *     $qb = $em->createQueryBuilder()
890
     *         ->select('u')
891
     *         ->from('User', 'u');
892
     * </code>
893
     *
894
     * @param string $from    The class name.
895
     * @param string $alias   The alias of the class.
896
     * @param string $indexBy The index for the from.
897
     *
898
     * @return self
899
     */
900 117
    public function from($from, $alias, $indexBy = null)
901
    {
902 117
        return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
903
    }
904
905
    /**
906
     * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
907
     * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
908
     * setting an index by.
909
     *
910
     * <code>
911
     *     $qb = $userRepository->createQueryBuilder('u')
912
     *         ->indexBy('u', 'u.id');
913
     *
914
     *     // Is equivalent to...
915
     *
916
     *     $qb = $em->createQueryBuilder()
917
     *         ->select('u')
918
     *         ->from('User', 'u', 'u.id');
919
     * </code>
920
     *
921
     * @param string $alias   The root alias of the class.
922
     * @param string $indexBy The index for the from.
923
     *
924
     * @return self
925
     *
926
     * @throws Query\QueryException
927
     */
928 2
    public function indexBy($alias, $indexBy)
929
    {
930 2
        $rootAliases = $this->getRootAliases();
931
932 2
        if (! in_array($alias, $rootAliases, true)) {
933
            throw new Query\QueryException(
934
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
935
            );
936
        }
937
938 2
        foreach ($this->dqlParts['from'] as &$fromClause) {
939
            /** @var Expr\From $fromClause */
940 2
            if ($fromClause->getAlias() !== $alias) {
941 1
                continue;
942
            }
943
944 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
945
        }
946
947 2
        return $this;
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
     * <code>
958
     *     $qb = $em->createQueryBuilder()
959
     *         ->select('u')
960
     *         ->from('User', 'u')
961
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
962
     * </code>
963
     *
964
     * @param string      $join          The relationship to join.
965
     * @param string      $alias         The alias of the join.
966
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
967
     * @param string|null $condition     The condition for the join.
968
     * @param string|null $indexBy       The index for the join.
969
     *
970
     * @return self
971
     */
972 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
973
    {
974 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
975
    }
976
977
    /**
978
     * Creates and adds a join over an entity association to the query.
979
     *
980
     * The entities in the joined association will be fetched as part of the query
981
     * result if the alias used for the joined association is placed in the select
982
     * expressions.
983
     *
984
     *     [php]
985
     *     $qb = $em->createQueryBuilder()
986
     *         ->select('u')
987
     *         ->from('User', 'u')
988
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
989
     *
990
     * @param string      $join          The relationship to join.
991
     * @param string      $alias         The alias of the join.
992
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
993
     * @param string|null $condition     The condition for the join.
994
     * @param string|null $indexBy       The index for the join.
995
     *
996
     * @return self
997
     */
998 16
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
999
    {
1000 16
        $hasParentAlias = strpos($join, '.');
1001 16
        $parentAlias    = substr($join, 0, $hasParentAlias === false ? 0 : $hasParentAlias);
1002 16
        $rootAlias      = $this->findRootAlias($alias, $parentAlias);
1003 16
        $join           = new Expr\Join(
1004 16
            Expr\Join::INNER_JOIN,
1005 16
            $join,
1006 16
            $alias,
1007 16
            $conditionType,
1008 16
            $condition,
1009 16
            $indexBy
1010
        );
1011
1012 16
        return $this->add('join', [$rootAlias => $join], true);
1013
    }
1014
1015
    /**
1016
     * Creates and adds a left join over an entity association to the query.
1017
     *
1018
     * The entities in the joined association will be fetched as part of the query
1019
     * result if the alias used for the joined association is placed in the select
1020
     * expressions.
1021
     *
1022
     * <code>
1023
     *     $qb = $em->createQueryBuilder()
1024
     *         ->select('u')
1025
     *         ->from('User', 'u')
1026
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
1027
     * </code>
1028
     *
1029
     * @param string      $join          The relationship to join.
1030
     * @param string      $alias         The alias of the join.
1031
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1032
     * @param string|null $condition     The condition for the join.
1033
     * @param string|null $indexBy       The index for the join.
1034
     *
1035
     * @return self
1036
     */
1037 15
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1038
    {
1039 15
        $hasParentAlias = strpos($join, '.');
1040 15
        $parentAlias    = substr($join, 0, $hasParentAlias === false ? 0 : $hasParentAlias);
1041 15
        $rootAlias      = $this->findRootAlias($alias, $parentAlias);
1042 15
        $join           = new Expr\Join(Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy);
1043
1044 15
        return $this->add('join', [$rootAlias => $join], true);
1045
    }
1046
1047
    /**
1048
     * leftJoins a property only if the join does not exists already.
1049
     * returns either the generated alias if the join did not exists
1050
     * if the alias of the already existing join
1051
     *
1052
     * <code>
1053
     *     $qb = $em->createQueryBuilder()
1054
     *         ->select('u')
1055
     *         ->from('User', 'u')
1056
     *         ->leftJoin('u.Phonenumbers', 'p');
1057
     *
1058
     *     $qb->leftJoinIfNotExists('u', 'Phonenumbers'); // p
1059
     *
1060
     *     $qb->leftJoinIfNotExists('u', 'Emailaddresses'); // u_Emailaddresses_4b3403665fea6
1061
     * </code>
1062
     * 
1063
     * @param string       $rootAlias
0 ignored issues
show
introduced by
Method \Doctrine\ORM\QueryBuilder::leftJoinIfNotExists() has useless @param annotation for parameter $rootAlias.
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter type; 7 found
Loading history...
1064
     * @param string       $property
0 ignored issues
show
introduced by
Method \Doctrine\ORM\QueryBuilder::leftJoinIfNotExists() has useless @param annotation for parameter $property.
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter type; 7 found
Loading history...
1065
     * 
1066
     * @return string
1067
     */
1068
    protected function leftJoinIfNotExists(string $rootAlias, string $property)
1069
    {
1070
        $alias = $this->getAliasByJoin($rootAlias . '.' . $property);
0 ignored issues
show
Bug introduced by
The call to Doctrine\ORM\QueryBuilder::getAliasByJoin() has too few arguments starting with join. ( Ignorable by Annotation )

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

1070
        /** @scrutinizer ignore-call */ 
1071
        $alias = $this->getAliasByJoin($rootAlias . '.' . $property);

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

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

Loading history...
Bug introduced by
$rootAlias . '.' . $property of type string is incompatible with the type Doctrine\ORM\QueryBuilder expected by parameter $qb of Doctrine\ORM\QueryBuilder::getAliasByJoin(). ( Ignorable by Annotation )

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

1070
        $alias = $this->getAliasByJoin(/** @scrutinizer ignore-type */ $rootAlias . '.' . $property);
Loading history...
1071
        if (false === $alias) {
0 ignored issues
show
introduced by
Yoda comparisons are disallowed.
Loading history...
1072
            // Generate an alias
1073
            $alias = $rootAlias . '_' . $property . '_' . uniqid();
0 ignored issues
show
introduced by
Function uniqid() should not be referenced via a fallback global name, but via a use statement.
Loading history...
1074
            $this->leftJoin($rootAlias . '.' . $property, $alias);
1075
        }
1076
        // it's either generated or found, but we want to know the alias
1077
        return $alias;
1078
    }
1079
1080
    /**
1081
     * Sets a new value for a field in a bulk update query.
1082
     *
1083
     * <code>
1084
     *     $qb = $em->createQueryBuilder()
1085
     *         ->update('User', 'u')
1086
     *         ->set('u.password', '?1')
1087
     *         ->where('u.id = ?2');
1088
     * </code>
1089
     *
1090
     * @param string $key   The key/field to set.
1091
     * @param string $value The value, expression, placeholder, etc.
1092
     *
1093
     * @return self
1094
     */
1095 3
    public function set($key, $value)
1096
    {
1097 3
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1098
    }
1099
1100
    /**
1101
     * Specifies one or more restrictions to the query result.
1102
     * Replaces any previously specified restrictions, if any.
1103
     *
1104
     * <code>
1105
     *     $qb = $em->createQueryBuilder()
1106
     *         ->select('u')
1107
     *         ->from('User', 'u')
1108
     *         ->where('u.id = ?');
1109
     *
1110
     *     // You can optionally programmatically build and/or expressions
1111
     *     $qb = $em->createQueryBuilder();
1112
     *
1113
     *     $or = $qb->expr()->orX();
1114
     *     $or->add($qb->expr()->eq('u.id', 1));
1115
     *     $or->add($qb->expr()->eq('u.id', 2));
1116
     *
1117
     *     $qb->update('User', 'u')
1118
     *         ->set('u.password', '?')
1119
     *         ->where($or);
1120
     * </code>
1121
     *
1122
     * @param mixed $predicates The restriction predicates.
1123
     */
1124 49
    public function where($predicates)
1125
    {
1126 49
        if (! (func_num_args() === 1 && $predicates instanceof Expr\Composite)) {
1127 46
            $predicates = new Expr\Andx(func_get_args());
1128
        }
1129
1130 49
        return $this->add('where', $predicates);
1131
    }
1132
1133
    /**
1134
     * Adds one or more restrictions to the query results, forming a logical
1135
     * conjunction with any previously specified restrictions.
1136
     *
1137
     * <code>
1138
     *     $qb = $em->createQueryBuilder()
1139
     *         ->select('u')
1140
     *         ->from('User', 'u')
1141
     *         ->where('u.username LIKE ?')
1142
     *         ->andWhere('u.is_active = 1');
1143
     * </code>
1144
     *
1145
     * @//param mixed $where The query restrictions.
1146
     *
1147
     * @see where()
1148
     */
1149 23
    public function andWhere()
1150
    {
1151 23
        $args  = func_get_args();
1152 23
        $where = $this->getDQLPart('where');
1153
1154 23
        if ($where instanceof Expr\Andx) {
1155 11
            $where->addMultiple($args);
1156
        } else {
1157 15
            array_unshift($args, $where);
1158 15
            $where = new Expr\Andx($args);
1159
        }
1160
1161 23
        return $this->add('where', $where);
1162
    }
1163
1164
    /**
1165
     * Adds one or more restrictions to the query results, forming a logical
1166
     * disjunction with any previously specified restrictions.
1167
     *
1168
     * <code>
1169
     *     $qb = $em->createQueryBuilder()
1170
     *         ->select('u')
1171
     *         ->from('User', 'u')
1172
     *         ->where('u.id = 1')
1173
     *         ->orWhere('u.id = 2');
1174
     * </code>
1175
     *
1176
     * @//param mixed $where The WHERE statement.
1177
     *
1178
     * @see where()
1179
     */
1180 5
    public function orWhere()
1181
    {
1182 5
        $args  = func_get_args();
1183 5
        $where = $this->getDQLPart('where');
1184
1185 5
        if ($where instanceof Expr\Orx) {
1186
            $where->addMultiple($args);
1187
        } else {
1188 5
            array_unshift($args, $where);
1189 5
            $where = new Expr\Orx($args);
1190
        }
1191
1192 5
        return $this->add('where', $where);
1193
    }
1194
1195
    /**
1196
     * Specifies a grouping over the results of the query.
1197
     * Replaces any previously specified groupings, if any.
1198
     *
1199
     * <code>
1200
     *     $qb = $em->createQueryBuilder()
1201
     *         ->select('u')
1202
     *         ->from('User', 'u')
1203
     *         ->groupBy('u.id');
1204
     * </code>
1205
     *
1206
     * @param string $groupBy The grouping expression.
1207
     *
1208
     * @return self
1209
     */
1210 7
    public function groupBy($groupBy)
1211
    {
1212 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1213
    }
1214
1215
    /**
1216
     * Adds a grouping expression to the query.
1217
     *
1218
     * <code>
1219
     *     $qb = $em->createQueryBuilder()
1220
     *         ->select('u')
1221
     *         ->from('User', 'u')
1222
     *         ->groupBy('u.lastLogin')
1223
     *         ->addGroupBy('u.createdAt');
1224
     * </code>
1225
     *
1226
     * @param string $groupBy The grouping expression.
1227
     *
1228
     * @return self
1229
     */
1230 1
    public function addGroupBy($groupBy)
1231
    {
1232 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1233
    }
1234
1235
    /**
1236
     * Specifies a restriction over the groups of the query.
1237
     * Replaces any previous having restrictions, if any.
1238
     *
1239
     * @param mixed $having The restriction over the groups.
1240
     *
1241
     * @return self
1242
     */
1243 3
    public function having($having)
1244
    {
1245 3
        if (! (func_num_args() === 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1246 3
            $having = new Expr\Andx(func_get_args());
1247
        }
1248
1249 3
        return $this->add('having', $having);
1250
    }
1251
1252
    /**
1253
     * Adds a restriction over the groups of the query, forming a logical
1254
     * conjunction with any existing having restrictions.
1255
     *
1256
     * @param mixed $having The restriction to append.
1257
     *
1258
     * @return self
1259
     */
1260 2
    public function andHaving($having)
1261
    {
1262 2
        $args   = func_get_args();
1263 2
        $having = $this->getDQLPart('having');
1264
1265 2
        if ($having instanceof Expr\Andx) {
1266 2
            $having->addMultiple($args);
1267
        } else {
1268
            array_unshift($args, $having);
1269
            $having = new Expr\Andx($args);
1270
        }
1271
1272 2
        return $this->add('having', $having);
1273
    }
1274
1275
    /**
1276
     * Adds a restriction over the groups of the query, forming a logical
1277
     * disjunction with any existing having restrictions.
1278
     *
1279
     * @param mixed $having The restriction to add.
1280
     *
1281
     * @return self
1282
     */
1283 1
    public function orHaving($having)
1284
    {
1285 1
        $args   = func_get_args();
1286 1
        $having = $this->getDQLPart('having');
1287
1288 1
        if ($having instanceof Expr\Orx) {
1289
            $having->addMultiple($args);
1290
        } else {
1291 1
            array_unshift($args, $having);
1292 1
            $having = new Expr\Orx($args);
1293
        }
1294
1295 1
        return $this->add('having', $having);
1296
    }
1297
1298
    /**
1299
     * Specifies an ordering for the query results.
1300
     * Replaces any previously specified orderings, if any.
1301
     *
1302
     * @param string|Expr\OrderBy $sort  The ordering expression.
1303
     * @param string              $order The ordering direction.
1304
     *
1305
     * @return self
1306
     */
1307 10
    public function orderBy($sort, $order = null)
1308
    {
1309 10
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1310
1311 10
        return $this->add('orderBy', $orderBy);
1312
    }
1313
1314
    /**
1315
     * Adds an ordering to the query results.
1316
     *
1317
     * @param string|Expr\OrderBy $sort  The ordering expression.
1318
     * @param string              $order The ordering direction.
1319
     *
1320
     * @return self
1321
     */
1322 4
    public function addOrderBy($sort, $order = null)
1323
    {
1324 4
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1325
1326 4
        return $this->add('orderBy', $orderBy, true);
1327
    }
1328
1329
    /**
1330
     * Adds criteria to the query.
1331
     *
1332
     * Adds where expressions with AND operator.
1333
     * Adds orderings.
1334
     * Overrides firstResult and maxResults if they're set.
1335
     *
1336
     * @return self
1337
     *
1338
     * @throws Query\QueryException
1339
     */
1340 13
    public function addCriteria(Criteria $criteria)
1341
    {
1342 13
        $allAliases = $this->getAllAliases();
1343 13
        if (! isset($allAliases[0])) {
1344
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1345
        }
1346
1347 13
        $visitor         = new QueryExpressionVisitor($this->getAllAliases());
1348 13
        $whereExpression = $criteria->getWhereExpression();
1349
1350 13
        if ($whereExpression) {
1351 9
            $this->andWhere($visitor->dispatch($whereExpression));
1352 9
            foreach ($visitor->getParameters() as $parameter) {
1353 9
                $this->parameters->add($parameter);
1354
            }
1355
        }
1356
1357 13
        if ($criteria->getOrderings()) {
1358 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1359 2
                $hasValidAlias = false;
1360 2
                foreach ($allAliases as $alias) {
1361 2
                    if (strpos($sort . '.', $alias . '.') === 0) {
1362 1
                        $hasValidAlias = true;
1363 2
                        break;
1364
                    }
1365
                }
1366
1367 2
                if (! $hasValidAlias) {
1368 1
                    $sort = $allAliases[0] . '.' . $sort;
1369
                }
1370
1371 2
                $this->addOrderBy($sort, $order);
1372
            }
1373
        }
1374
1375 13
        $firstResult = $criteria->getFirstResult();
1376 13
        $maxResults  = $criteria->getMaxResults();
1377
1378
        // Overwrite limits only if they was set in criteria
1379 13
        if ($firstResult !== null) {
1380 1
            $this->setFirstResult($firstResult);
1381
        }
1382 13
        if ($maxResults !== null) {
1383 1
            $this->setMaxResults($maxResults);
1384
        }
1385
1386 13
        return $this;
1387
    }
1388
1389
    /**
1390
     * Gets a query part by its name.
1391
     *
1392
     * @return mixed $queryPart
1393
     *
1394
     * @todo Rename: getQueryPart (or remove?)
1395
     */
1396 107
    public function getDQLPart($queryPartName)
1397
    {
1398 107
        return $this->dqlParts[$queryPartName];
1399
    }
1400
1401
    /**
1402
     * Gets all query parts.
1403
     *
1404
     * @return mixed[] $dqlParts
1405
     *
1406
     * @todo Rename: getQueryParts (or remove?)
1407
     */
1408 1
    public function getDQLParts()
1409
    {
1410 1
        return $this->dqlParts;
1411
    }
1412
1413
    /**
1414
     * @return string
1415
     */
1416 1
    private function getDQLForDelete()
1417
    {
1418
        return 'DELETE'
1419 1
              . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1420 1
              . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1421 1
              . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1422
    }
1423
1424
    /**
1425
     * @return string
1426
     */
1427 3
    private function getDQLForUpdate()
1428
    {
1429
        return 'UPDATE'
1430 3
              . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1431 3
              . $this->getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
1432 3
              . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1433 3
              . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1434
    }
1435
1436
    /**
1437
     * @return string
1438
     */
1439 86
    private function getDQLForSelect()
1440
    {
1441
        $dql = 'SELECT'
1442 86
             . ($this->dqlParts['distinct']===true ? ' DISTINCT' : '')
1443 86
             . $this->getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
1444
1445 86
        $fromParts   = $this->getDQLPart('from');
1446 86
        $joinParts   = $this->getDQLPart('join');
1447 86
        $fromClauses = [];
1448
1449
        // Loop through all FROM clauses
1450 86
        if (! empty($fromParts)) {
1451 85
            $dql .= ' FROM ';
1452
1453 85
            foreach ($fromParts as $from) {
1454 85
                $fromClause = (string) $from;
1455
1456 85
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1457 26
                    foreach ($joinParts[$from->getAlias()] as $join) {
1458 26
                        $fromClause .= ' ' . ((string) $join);
1459
                    }
1460
                }
1461
1462 85
                $fromClauses[] = $fromClause;
1463
            }
1464
        }
1465
1466 86
        $dql .= implode(', ', $fromClauses)
1467 86
              . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1468 86
              . $this->getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
1469 86
              . $this->getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
1470 86
              . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1471
1472 86
        return $dql;
1473
    }
1474
1475
    /**
1476
     * @param string  $queryPartName
1477
     * @param mixed[] $options
1478
     *
1479
     * @return string
1480
     */
1481 89
    private function getReducedDQLQueryPart($queryPartName, $options = [])
1482
    {
1483 89
        $queryPart = $this->getDQLPart($queryPartName);
1484
1485 89
        if (empty($queryPart)) {
1486 89
            return $options['empty'] ?? '';
1487
        }
1488
1489 89
        return ($options['pre'] ?? '')
1490 89
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1491 89
             . ($options['post'] ?? '');
1492
    }
1493
1494
    /**
1495
     * Resets DQL parts.
1496
     *
1497
     * @param string[]|null $parts
1498
     */
1499 2
    public function resetDQLParts($parts = null)
1500
    {
1501 2
        if ($parts === null) {
1502 1
            $parts = array_keys($this->dqlParts);
1503
        }
1504
1505 2
        foreach ($parts as $part) {
1506 2
            $this->resetDQLPart($part);
1507
        }
1508
1509 2
        return $this;
1510
    }
1511
1512
    /**
1513
     * Resets single DQL part.
1514
     *
1515
     * @param string $part
1516
     *
1517
     * @return self
1518
     */
1519 3
    public function resetDQLPart($part)
1520
    {
1521 3
        $this->dqlParts[$part] = is_array($this->dqlParts[$part]) ? [] : null;
1522 3
        $this->state           = self::STATE_DIRTY;
1523
1524 3
        return $this;
1525
    }
1526
1527
    /**
1528
     * Gets a string representation of this QueryBuilder which corresponds to
1529
     * the final DQL query being constructed.
1530
     *
1531
     * @return string The string representation of this QueryBuilder.
1532
     */
1533 5
    public function __toString()
1534
    {
1535 5
        return $this->getDQL();
1536
    }
1537
1538
    /**
1539
     * Deep clones all expression objects in the DQL parts.
1540
     *
1541
     */
1542 3
    public function __clone()
1543
    {
1544 3
        foreach ($this->dqlParts as $part => $elements) {
1545 3
            if (is_array($this->dqlParts[$part])) {
1546 3
                foreach ($this->dqlParts[$part] as $idx => $element) {
1547 2
                    if (is_object($element)) {
1548 3
                        $this->dqlParts[$part][$idx] = clone $element;
1549
                    }
1550
                }
1551 3
            } elseif (is_object($elements)) {
1552 3
                $this->dqlParts[$part] = clone $elements;
1553
            }
1554
        }
1555
1556 3
        $parameters = [];
1557
1558 3
        foreach ($this->parameters as $parameter) {
1559 1
            $parameters[] = clone $parameter;
1560
        }
1561
1562 3
        $this->parameters = new ArrayCollection($parameters);
1563 3
    }
1564
}
1565