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

lib/Doctrine/ORM/QueryBuilder.php (1 issue)

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();
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

374
            $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...
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) {
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) {
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