Completed
Push — master ( a0071b...e33605 )
by Michael
12s
created

lib/Doctrine/ORM/QueryBuilder.php (2 issues)

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
10
use Doctrine\ORM\Query\Expr;
11
use Doctrine\ORM\Query\QueryExpressionVisitor;
12
13
/**
14
 * This class is responsible for building DQL query strings via an object oriented
15
 * PHP interface.
16
 */
17
class QueryBuilder
18
{
19
    /* The query types. */
20
    public const SELECT = 0;
21
    public const DELETE = 1;
22
    public const UPDATE = 2;
23
24
    /* The builder states. */
25
    public const STATE_DIRTY = 0;
26
    public const STATE_CLEAN = 1;
27
28
    /**
29
     * The EntityManager used by this QueryBuilder.
30
     *
31
     * @var EntityManagerInterface
32
     */
33
    private $em;
34
35
    /**
36
     * The array of DQL parts collected.
37
     *
38
     * @var mixed[]
39
     */
40
    private $dqlParts = [
41
        'distinct' => false,
42
        'select'  => [],
43
        'from'    => [],
44
        'join'    => [],
45
        'set'     => [],
46
        'where'   => null,
47
        'groupBy' => [],
48
        'having'  => null,
49
        'orderBy' => [],
50
    ];
51
52
    /**
53
     * The type of query this is. Can be select, update or delete.
54
     *
55
     * @var int
56
     */
57
    private $type = self::SELECT;
58
59
    /**
60
     * The state of the query object. Can be dirty or clean.
61
     *
62
     * @var int
63
     */
64
    private $state = self::STATE_CLEAN;
65
66
    /**
67
     * The complete DQL string for this query.
68
     *
69
     * @var string
70
     */
71
    private $dql;
72
73
    /**
74
     * The query parameters.
75
     *
76
     * @var ArrayCollection
77
     */
78
    private $parameters;
79
80
    /**
81
     * The index of the first result to retrieve.
82
     *
83
     * @var int
84
     */
85
    private $firstResult;
86
87
    /**
88
     * The maximum number of results to retrieve.
89
     *
90
     * @var int|null
91
     */
92
    private $maxResults;
93
94
    /**
95
     * Keeps root entity alias names for join entities.
96
     *
97
     * @var mixed[]
98
     */
99
    private $joinRootAliases = [];
100
101
    /**
102
     * Whether to use second level cache, if available.
103
     *
104
     * @var bool
105
     */
106
    protected $cacheable = false;
107
108
    /**
109
     * Second level cache region name.
110
     *
111
     * @var string|null
112
     */
113
    protected $cacheRegion;
114
115
    /**
116
     * Second level query cache mode.
117
     *
118
     * @var int|null
119
     */
120
    protected $cacheMode;
121
122
    /**
123
     * @var int
124
     */
125
    protected $lifetime = 0;
126
127
    /**
128
     * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
129
     *
130
     * @param EntityManagerInterface $em The EntityManager to use.
131
     */
132 127
    public function __construct(EntityManagerInterface $em)
133
    {
134 127
        $this->em         = $em;
135 127
        $this->parameters = new ArrayCollection();
136 127
    }
137
138
    /**
139
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
140
     * This producer method is intended for convenient inline usage. Example:
141
     *
142
     * <code>
143
     *     $qb = $em->createQueryBuilder();
144
     *     $qb
145
     *         ->select('u')
146
     *         ->from('User', 'u')
147
     *         ->where($qb->expr()->eq('u.id', 1));
148
     * </code>
149
     *
150
     * For more complex expression construction, consider storing the expression
151
     * builder object in a local variable.
152
     *
153
     * @return Query\Expr
154
     */
155 11
    public function expr()
156
    {
157 11
        return $this->em->getExpressionBuilder();
158
    }
159
160
    /**
161
     * Enable/disable second level query (result) caching for this query.
162
     *
163
     * @param bool $cacheable
164
     *
165
     * @return self
166
     */
167 1
    public function setCacheable($cacheable)
168
    {
169 1
        $this->cacheable = (bool) $cacheable;
170
171 1
        return $this;
172
    }
173
174
    /**
175
     * @return bool TRUE if the query results are enable for second level cache, FALSE otherwise.
176
     */
177 1
    public function isCacheable()
178
    {
179 1
        return $this->cacheable;
180
    }
181
182
    /**
183
     * @param string $cacheRegion
184
     *
185
     * @return self
186
     */
187 1
    public function setCacheRegion($cacheRegion)
188
    {
189 1
        $this->cacheRegion = (string) $cacheRegion;
190
191 1
        return $this;
192
    }
193
194
    /**
195
     * Obtain the name of the second level query cache region in which query results will be stored
196
     *
197
     * @return string|null The cache region name; NULL indicates the default region.
198
     */
199 1
    public function getCacheRegion()
200
    {
201 1
        return $this->cacheRegion;
202
    }
203
204
    /**
205
     * @return int
206
     */
207 1
    public function getLifetime()
208
    {
209 1
        return $this->lifetime;
210
    }
211
212
    /**
213
     * Sets the life-time for this query into second level cache.
214
     *
215
     * @param int $lifetime
216
     *
217
     * @return self
218
     */
219 1
    public function setLifetime($lifetime)
220
    {
221 1
        $this->lifetime = (int) $lifetime;
222
223 1
        return $this;
224
    }
225
226
    /**
227
     * @return int
228
     */
229 1
    public function getCacheMode()
230
    {
231 1
        return $this->cacheMode;
232
    }
233
234
    /**
235
     * @param int $cacheMode
236
     *
237
     * @return self
238
     */
239 1
    public function setCacheMode($cacheMode)
240
    {
241 1
        $this->cacheMode = (int) $cacheMode;
242
243 1
        return $this;
244
    }
245
246
    /**
247
     * Gets the type of the currently built query.
248
     *
249
     * @return int
250
     */
251 4
    public function getType()
252
    {
253 4
        return $this->type;
254
    }
255
256
    /**
257
     * Gets the associated EntityManager for this query builder.
258
     *
259
     * @return EntityManagerInterface
260
     */
261 1
    public function getEntityManager()
262
    {
263 1
        return $this->em;
264
    }
265
266
    /**
267
     * Gets the state of this query builder instance.
268
     *
269
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
270
     */
271 2
    public function getState()
272
    {
273 2
        return $this->state;
274
    }
275
276
    /**
277
     * Gets the complete DQL string formed by the current specifications of this QueryBuilder.
278
     *
279
     * <code>
280
     *     $qb = $em->createQueryBuilder()
281
     *         ->select('u')
282
     *         ->from('User', 'u');
283
     *     echo $qb->getDql(); // SELECT u FROM User u
284
     * </code>
285
     *
286
     * @return string The DQL query string.
287
     */
288 87
    public function getDQL()
289
    {
290 87
        if ($this->dql !== null && $this->state === self::STATE_CLEAN) {
291 44
            return $this->dql;
292
        }
293
294 87
        switch ($this->type) {
295 87
            case self::DELETE:
296 1
                $dql = $this->getDQLForDelete();
297 1
                break;
298
299 86
            case self::UPDATE:
300 3
                $dql = $this->getDQLForUpdate();
301 3
                break;
302
303 84
            case self::SELECT:
304
            default:
305 84
                $dql = $this->getDQLForSelect();
306 84
                break;
307
        }
308
309 87
        $this->state = self::STATE_CLEAN;
310 87
        $this->dql   = $dql;
311
312 87
        return $dql;
313
    }
314
315
    /**
316
     * Constructs a Query instance from the current specifications of the builder.
317
     *
318
     * <code>
319
     *     $qb = $em->createQueryBuilder()
320
     *         ->select('u')
321
     *         ->from('User', 'u');
322
     *     $q = $qb->getQuery();
323
     *     $results = $q->execute();
324
     * </code>
325
     *
326
     * @return Query
327
     */
328 76
    public function getQuery()
329
    {
330 76
        $parameters = clone $this->parameters;
331 76
        $query      = $this->em->createQuery($this->getDQL())
332 76
            ->setParameters($parameters)
333 76
            ->setFirstResult($this->firstResult)
334 76
            ->setMaxResults($this->maxResults);
335
336 76
        if ($this->lifetime) {
337 1
            $query->setLifetime($this->lifetime);
338
        }
339
340 76
        if ($this->cacheMode) {
341 1
            $query->setCacheMode($this->cacheMode);
342
        }
343
344 76
        if ($this->cacheable) {
345 1
            $query->setCacheable($this->cacheable);
346
        }
347
348 76
        if ($this->cacheRegion) {
349 1
            $query->setCacheRegion($this->cacheRegion);
350
        }
351
352 76
        return $query;
353
    }
354
355
    /**
356
     * Finds the root entity alias of the joined entity.
357
     *
358
     * @param string $alias       The alias of the new join entity
359
     * @param string $parentAlias The parent entity alias of the join relationship
360
     *
361
     * @return string
362
     */
363 30
    private function findRootAlias($alias, $parentAlias)
364
    {
365 30
        $rootAlias = null;
366
367 30
        if (in_array($parentAlias, $this->getRootAliases())) {
368 29
            $rootAlias = $parentAlias;
369 7
        } elseif (isset($this->joinRootAliases[$parentAlias])) {
370 6
            $rootAlias = $this->joinRootAliases[$parentAlias];
371
        } else {
372
            // Should never happen with correct joining order. Might be
373
            // thoughtful to throw exception instead.
374 1
            $rootAlias = $this->getRootAlias();
375
        }
376
377 30
        $this->joinRootAliases[$alias] = $rootAlias;
378
379 30
        return $rootAlias;
380
    }
381
382
    /**
383
     * Gets the FIRST root alias of the query. This is the first entity alias involved
384
     * in the construction of the query.
385
     *
386
     * <code>
387
     * $qb = $em->createQueryBuilder()
388
     *     ->select('u')
389
     *     ->from('User', 'u');
390
     *
391
     * echo $qb->getRootAlias(); // u
392
     * </code>
393
     *
394
     * @deprecated Please use $qb->getRootAliases() instead.
395
     * @throws \RuntimeException
396
     *
397
     * @return string
398
     */
399 4
    public function getRootAlias()
400
    {
401 4
        $aliases = $this->getRootAliases();
402
403 4
        if (! isset($aliases[0])) {
404
            throw new \RuntimeException('No alias was set before invoking getRootAlias().');
405
        }
406
407 4
        return $aliases[0];
408
    }
409
410
    /**
411
     * Gets the root aliases of the query. This is the entity aliases involved
412
     * in the construction of the query.
413
     *
414
     * <code>
415
     *     $qb = $em->createQueryBuilder()
416
     *         ->select('u')
417
     *         ->from('User', 'u');
418
     *
419
     *     $qb->getRootAliases(); // array('u')
420
     * </code>
421
     *
422
     * @return string[]
423
     */
424 46
    public function getRootAliases()
425
    {
426 46
        $aliases = [];
427
428 46
        foreach ($this->dqlParts['from'] as &$fromClause) {
429 46
            if (is_string($fromClause)) {
430
                $spacePos = strrpos($fromClause, ' ');
431
                $from     = substr($fromClause, 0, $spacePos);
432
                $alias    = substr($fromClause, $spacePos + 1);
433
434
                $fromClause = new Query\Expr\From($from, $alias);
435
            }
436
437 46
            $aliases[] = $fromClause->getAlias();
438
        }
439
440 46
        return $aliases;
441
    }
442
443
    /**
444
     * Gets all the aliases that have been used in the query.
445
     * Including all select root aliases and join aliases
446
     *
447
     * <code>
448
     *     $qb = $em->createQueryBuilder()
449
     *         ->select('u')
450
     *         ->from('User', 'u')
451
     *         ->join('u.articles','a');
452
     *
453
     *     $qb->getAllAliases(); // array('u','a')
454
     * </code>
455
     *
456
     * @return string[]
457
     */
458 15
    public function getAllAliases()
459
    {
460 15
        return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases));
461
    }
462
463
    /**
464
     * Gets the root entities of the query. This is the entity aliases involved
465
     * in the construction of the query.
466
     *
467
     * <code>
468
     *     $qb = $em->createQueryBuilder()
469
     *         ->select('u')
470
     *         ->from('User', 'u');
471
     *
472
     *     $qb->getRootEntities(); // array('User')
473
     * </code>
474
     *
475
     * @return string[]
476
     */
477 1
    public function getRootEntities()
478
    {
479 1
        $entities = [];
480
481 1
        foreach ($this->dqlParts['from'] as &$fromClause) {
482 1
            if (is_string($fromClause)) {
483
                $spacePos = strrpos($fromClause, ' ');
484
                $from     = substr($fromClause, 0, $spacePos);
485
                $alias    = substr($fromClause, $spacePos + 1);
486
487
                $fromClause = new Query\Expr\From($from, $alias);
488
            }
489
490 1
            $entities[] = $fromClause->getFrom();
491
        }
492
493 1
        return $entities;
494
    }
495
496
    /**
497
     * Sets a query parameter for the query being constructed.
498
     *
499
     * <code>
500
     *     $qb = $em->createQueryBuilder()
501
     *         ->select('u')
502
     *         ->from('User', 'u')
503
     *         ->where('u.id = :user_id')
504
     *         ->setParameter('user_id', 1);
505
     * </code>
506
     *
507
     * @param string|int      $key   The parameter position or name.
508
     * @param mixed           $value The parameter value.
509
     * @param string|int|null $type  PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
510
     *
511
     * @return self
512
     */
513 17
    public function setParameter($key, $value, $type = null)
514
    {
515 17
        $existingParameter = $this->getParameter($key);
516
517 17
        if ($existingParameter !== null) {
518 1
            $existingParameter->setValue($value, $type);
519
520 1
            return $this;
521
        }
522
523 17
        $this->parameters->add(new Query\Parameter($key, $value, $type));
524
525 17
        return $this;
526
    }
527
528
    /**
529
     * Sets a collection of query parameters for the query being constructed.
530
     *
531
     * <code>
532
     *     $qb = $em->createQueryBuilder()
533
     *         ->select('u')
534
     *         ->from('User', 'u')
535
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
536
     *         ->setParameters(new ArrayCollection(array(
537
     *             new Parameter('user_id1', 1),
538
     *             new Parameter('user_id2', 2)
539
     *        )));
540
     * </code>
541
     *
542
     * @param \Doctrine\Common\Collections\ArrayCollection|array|mixed[] $parameters The query parameters to set.
543
     *
544
     * @return self
545
     */
546 4
    public function setParameters($parameters)
547
    {
548
        // BC compatibility with 2.3-
549 4
        if (is_array($parameters)) {
550 1
            $parameterCollection = new ArrayCollection();
551
552 1
            foreach ($parameters as $key => $value) {
553 1
                $parameter = new Query\Parameter($key, $value);
554
555 1
                $parameterCollection->add($parameter);
556
            }
557
558 1
            $parameters = $parameterCollection;
559
        }
560
561 4
        $this->parameters = $parameters;
562
563 4
        return $this;
564
    }
565
566
    /**
567
     * Gets all defined query parameters for the query being constructed.
568
     *
569
     * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
570
     */
571 6
    public function getParameters()
572
    {
573 6
        return $this->parameters;
574
    }
575
576
    /**
577
     * Gets a (previously set) query parameter of the query being constructed.
578
     *
579
     * @param mixed $key The key (index or name) of the bound parameter.
580
     *
581
     * @return Query\Parameter|null The value of the bound parameter.
582
     */
583 27
    public function getParameter($key)
584
    {
585 27
        $filteredParameters = $this->parameters->filter(
586 27
            function (Query\Parameter $parameter) use ($key) : bool {
587 19
                $parameterName = $parameter->getName();
588
589 19
                return $key === $parameterName || (string) $key === (string) $parameterName;
590 27
            }
591
        );
592
593 27
        return $filteredParameters->isEmpty() ? null : $filteredParameters->first();
594
    }
595
596
    /**
597
     * Sets the position of the first result to retrieve (the "offset").
598
     *
599
     * @param int $firstResult The first result to return.
600
     *
601
     * @return self
602
     */
603 2
    public function setFirstResult($firstResult)
604
    {
605 2
        $this->firstResult = $firstResult;
606
607 2
        return $this;
608
    }
609
610
    /**
611
     * Gets the position of the first result the query object was set to retrieve (the "offset").
612
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
613
     *
614
     * @return int The position of the first result.
615
     */
616 2
    public function getFirstResult()
617
    {
618 2
        return $this->firstResult;
619
    }
620
621
    /**
622
     * Sets the maximum number of results to retrieve (the "limit").
623
     *
624
     * @param int|null $maxResults The maximum number of results to retrieve.
625
     *
626
     * @return self
627
     */
628 3
    public function setMaxResults($maxResults)
629
    {
630 3
        $this->maxResults = $maxResults;
631
632 3
        return $this;
633
    }
634
635
    /**
636
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
637
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
638
     *
639
     * @return int|null Maximum number of results.
640
     */
641 2
    public function getMaxResults()
642
    {
643 2
        return $this->maxResults;
644
    }
645
646
    /**
647
     * Either appends to or replaces a single, generic query part.
648
     *
649
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
650
     * 'groupBy', 'having' and 'orderBy'.
651
     *
652
     * @param string         $dqlPartName The DQL part name.
653
     * @param object|mixed[] $dqlPart     An Expr object.
654
     * @param bool           $append      Whether to append (true) or replace (false).
655
     *
656
     * @return self
657
     */
658 123
    public function add($dqlPartName, $dqlPart, $append = false)
659
    {
660 123
        if ($append && ($dqlPartName === 'where' || $dqlPartName === 'having')) {
661 1
            throw new \InvalidArgumentException(
662
                "Using \$append = true does not have an effect with 'where' or 'having' " .
663 1
                'parts. See QueryBuilder#andWhere() for an example for correct usage.'
664
            );
665
        }
666
667 123
        $isMultiple = is_array($this->dqlParts[$dqlPartName])
668 123
            && ! ($dqlPartName === 'join' && ! $append);
669
670
        // Allow adding any part retrieved from self::getDQLParts().
671 123
        if (is_array($dqlPart) && $dqlPartName !== 'join') {
672 1
            $dqlPart = reset($dqlPart);
673
        }
674
675
        // This is introduced for backwards compatibility reasons.
676
        // TODO: Remove for 3.0
677 123
        if ($dqlPartName === 'join') {
678 31
            $newDqlPart = [];
679
680 31
            foreach ($dqlPart as $k => $v) {
681 31
                $k = is_numeric($k) ? $this->getRootAlias() : $k;
682
683 31
                $newDqlPart[$k] = $v;
684
            }
685
686 31
            $dqlPart = $newDqlPart;
687
        }
688
689 123
        if ($append && $isMultiple) {
690 117
            if (is_array($dqlPart)) {
691 31
                $key = key($dqlPart);
692
693 31
                $this->dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
694
            } else {
695 117
                $this->dqlParts[$dqlPartName][] = $dqlPart;
696
            }
697
        } else {
698 121
            $this->dqlParts[$dqlPartName] = ($isMultiple) ? [$dqlPart] : $dqlPart;
699
        }
700
701 123
        $this->state = self::STATE_DIRTY;
702
703 123
        return $this;
704
    }
705
706
    /**
707
     * Specifies an item that is to be returned in the query result.
708
     * Replaces any previously specified selections, if any.
709
     *
710
     * <code>
711
     *     $qb = $em->createQueryBuilder()
712
     *         ->select('u', 'p')
713
     *         ->from('User', 'u')
714
     *         ->leftJoin('u.Phonenumbers', 'p');
715
     * </code>
716
     *
717
     * @param mixed $select The selection expressions.
718
     *
719
     * @return self
720
     */
721 116
    public function select($select = null)
722
    {
723 116
        $this->type = self::SELECT;
724
725 116
        if (empty($select)) {
726 1
            return $this;
727
        }
728
729 115
        $selects = is_array($select) ? $select : func_get_args();
730
731 115
        return $this->add('select', new Expr\Select($selects), false);
732
    }
733
734
    /**
735
     * Adds a DISTINCT flag to this query.
736
     *
737
     * <code>
738
     *     $qb = $em->createQueryBuilder()
739
     *         ->select('u')
740
     *         ->distinct()
741
     *         ->from('User', 'u');
742
     * </code>
743
     *
744
     * @param bool $flag
745
     *
746
     * @return self
747
     */
748 1
    public function distinct($flag = true)
749
    {
750 1
        $this->dqlParts['distinct'] = (bool) $flag;
751
752 1
        return $this;
753
    }
754
755
    /**
756
     * Adds an item that is to be returned in the query result.
757
     *
758
     * <code>
759
     *     $qb = $em->createQueryBuilder()
760
     *         ->select('u')
761
     *         ->addSelect('p')
762
     *         ->from('User', 'u')
763
     *         ->leftJoin('u.Phonenumbers', 'p');
764
     * </code>
765
     *
766
     * @param mixed $select The selection expression.
767
     *
768
     * @return self
769
     */
770 1
    public function addSelect($select = null)
771
    {
772 1
        $this->type = self::SELECT;
773
774 1
        if (empty($select)) {
775
            return $this;
776
        }
777
778 1
        $selects = is_array($select) ? $select : func_get_args();
779
780 1
        return $this->add('select', new Expr\Select($selects), true);
781
    }
782
783
    /**
784
     * Turns the query being built into a bulk delete query that ranges over
785
     * a certain entity type.
786
     *
787
     * <code>
788
     *     $qb = $em->createQueryBuilder()
789
     *         ->delete('User', 'u')
790
     *         ->where('u.id = :user_id')
791
     *         ->setParameter('user_id', 1);
792
     * </code>
793
     *
794
     * @param string $delete The class/type whose instances are subject to the deletion.
795
     * @param string $alias  The class/type alias used in the constructed query.
796
     *
797
     * @return self
798
     */
799 4
    public function delete($delete = null, $alias = null)
800
    {
801 4
        $this->type = self::DELETE;
802
803 4
        if (! $delete) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $delete of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
804 1
            return $this;
805
        }
806
807 3
        return $this->add('from', new Expr\From($delete, $alias));
808
    }
809
810
    /**
811
     * Turns the query being built into a bulk update query that ranges over
812
     * a certain entity type.
813
     *
814
     * <code>
815
     *     $qb = $em->createQueryBuilder()
816
     *         ->update('User', 'u')
817
     *         ->set('u.password', '?1')
818
     *         ->where('u.id = ?2');
819
     * </code>
820
     *
821
     * @param string $update The class/type whose instances are subject to the update.
822
     * @param string $alias  The class/type alias used in the constructed query.
823
     *
824
     * @return self
825
     */
826 4
    public function update($update = null, $alias = null)
827
    {
828 4
        $this->type = self::UPDATE;
829
830 4
        if (! $update) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $update of type null|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

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

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

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

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