Failed Conditions
Pull Request — master (#8010)
by Oleg
09:21
created

QueryBuilder::set()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
357 1
            $query->setCacheMode($this->cacheMode);
358
        }
359
360 78
        if ($this->cacheable) {
361 1
            $query->setCacheable($this->cacheable);
362
        }
363
364 78
        if ($this->cacheRegion) {
365 1
            $query->setCacheRegion($this->cacheRegion);
366
        }
367
368 78
        return $query;
369
    }
370
371
    /**
372
     * Finds the root entity alias of the joined entity.
373
     *
374
     * @param string $alias       The alias of the new join entity
375
     * @param string $parentAlias The parent entity alias of the join relationship
376
     *
377
     * @return string
378
     */
379 30
    private function findRootAlias($alias, $parentAlias)
380
    {
381 30
        $rootAlias = null;
382
383 30
        if (in_array($parentAlias, $this->getRootAliases(), true)) {
384 29
            $rootAlias = $parentAlias;
385 7
        } elseif (isset($this->joinRootAliases[$parentAlias])) {
386 6
            $rootAlias = $this->joinRootAliases[$parentAlias];
387
        } else {
388
            // Should never happen with correct joining order. Might be
389
            // thoughtful to throw exception instead.
390 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

390
            $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...
391
        }
392
393 30
        $this->joinRootAliases[$alias] = $rootAlias;
394
395 30
        return $rootAlias;
396
    }
397
398
    /**
399
     * Gets the FIRST root alias of the query. This is the first entity alias involved
400
     * in the construction of the query.
401
     *
402
     * <code>
403
     * $qb = $em->createQueryBuilder()
404
     *     ->select('u')
405
     *     ->from('User', 'u');
406
     *
407
     * echo $qb->getRootAlias(); // u
408
     * </code>
409
     *
410
     * @deprecated Please use $qb->getRootAliases() instead.
411
     *
412
     * @return string
413
     *
414
     * @throws RuntimeException
415
     */
416 4
    public function getRootAlias()
417
    {
418 4
        $aliases = $this->getRootAliases();
419
420 4
        if (! isset($aliases[0])) {
421
            throw new RuntimeException('No alias was set before invoking getRootAlias().');
422
        }
423
424 4
        return $aliases[0];
425
    }
426
427
    /**
428
     * Gets the root aliases of the query. This is the entity aliases involved
429
     * in the construction of the query.
430
     *
431
     * <code>
432
     *     $qb = $em->createQueryBuilder()
433
     *         ->select('u')
434
     *         ->from('User', 'u');
435
     *
436
     *     $qb->getRootAliases(); // array('u')
437
     * </code>
438
     *
439
     * @return string[]
440
     */
441 46
    public function getRootAliases()
442
    {
443 46
        $aliases = [];
444
445 46
        foreach ($this->dqlParts['from'] as &$fromClause) {
446 46
            if (is_string($fromClause)) {
447
                $spacePos = strrpos($fromClause, ' ');
448
                $from     = substr($fromClause, 0, $spacePos);
449
                $alias    = substr($fromClause, $spacePos + 1);
450
451
                $fromClause = new Query\Expr\From($from, $alias);
452
            }
453
454 46
            $aliases[] = $fromClause->getAlias();
455
        }
456
457 46
        return $aliases;
458
    }
459
460
    /**
461
     * Gets all the aliases that have been used in the query.
462
     * Including all select root aliases and join aliases
463
     *
464
     * <code>
465
     *     $qb = $em->createQueryBuilder()
466
     *         ->select('u')
467
     *         ->from('User', 'u')
468
     *         ->join('u.articles','a');
469
     *
470
     *     $qb->getAllAliases(); // array('u','a')
471
     * </code>
472
     *
473
     * @return string[]
474
     */
475 15
    public function getAllAliases()
476
    {
477 15
        return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases));
478
    }
479
480
    /**
481
     * Gets the root entities of the query. This is the entity aliases involved
482
     * in the construction of the query.
483
     *
484
     * <code>
485
     *     $qb = $em->createQueryBuilder()
486
     *         ->select('u')
487
     *         ->from('User', 'u');
488
     *
489
     *     $qb->getRootEntities(); // array('User')
490
     * </code>
491
     *
492
     * @return string[]
493
     */
494 1
    public function getRootEntities()
495
    {
496 1
        $entities = [];
497
498 1
        foreach ($this->dqlParts['from'] as &$fromClause) {
499 1
            if (is_string($fromClause)) {
500
                $spacePos = strrpos($fromClause, ' ');
501
                $from     = substr($fromClause, 0, $spacePos);
502
                $alias    = substr($fromClause, $spacePos + 1);
503
504
                $fromClause = new Query\Expr\From($from, $alias);
505
            }
506
507 1
            $entities[] = $fromClause->getFrom();
508
        }
509
510 1
        return $entities;
511
    }
512
513
    /**
514
     * Sets a query parameter for the query being constructed.
515
     *
516
     * <code>
517
     *     $qb = $em->createQueryBuilder()
518
     *         ->select('u')
519
     *         ->from('User', 'u')
520
     *         ->where('u.id = :user_id')
521
     *         ->setParameter('user_id', 1);
522
     * </code>
523
     *
524
     * @param string|int      $key   The parameter position or name.
525
     * @param mixed           $value The parameter value.
526
     * @param string|int|null $type  ParameterType::* or \Doctrine\DBAL\Types\Type::* constant
527
     *
528
     * @return self
529
     */
530 19
    public function setParameter($key, $value, $type = null)
531
    {
532 19
        $existingParameter = $this->getParameter($key);
533
534 19
        if ($existingParameter !== null) {
535 1
            $existingParameter->setValue($value, $type);
536
537 1
            return $this;
538
        }
539
540 19
        $this->parameters->add(new Query\Parameter($key, $value, $type));
541
542 19
        return $this;
543
    }
544
545
    /**
546
     * Sets a collection of query parameters for the query being constructed.
547
     *
548
     * <code>
549
     *     $qb = $em->createQueryBuilder()
550
     *         ->select('u')
551
     *         ->from('User', 'u')
552
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
553
     *         ->setParameters(new ArrayCollection(array(
554
     *             new Parameter('user_id1', 1),
555
     *             new Parameter('user_id2', 2)
556
     *        )));
557
     * </code>
558
     *
559
     * @param ArrayCollection|array|mixed[] $parameters The query parameters to set.
560
     *
561
     * @return self
562
     */
563 4
    public function setParameters($parameters)
564
    {
565
        // BC compatibility with 2.3-
566 4
        if (is_array($parameters)) {
567 1
            $parameterCollection = new ArrayCollection();
568
569 1
            foreach ($parameters as $key => $value) {
570 1
                $parameter = new Query\Parameter($key, $value);
571
572 1
                $parameterCollection->add($parameter);
573
            }
574
575 1
            $parameters = $parameterCollection;
576
        }
577
578 4
        $this->parameters = $parameters;
579
580 4
        return $this;
581
    }
582
583
    /**
584
     * Gets all defined query parameters for the query being constructed.
585
     *
586
     * @return ArrayCollection The currently defined query parameters.
587
     */
588 6
    public function getParameters()
589
    {
590 6
        return $this->parameters;
591
    }
592
593
    /**
594
     * Gets a (previously set) query parameter of the query being constructed.
595
     *
596
     * @param mixed $key The key (index or name) of the bound parameter.
597
     *
598
     * @return Query\Parameter|null The value of the bound parameter.
599
     */
600 29
    public function getParameter($key)
601
    {
602 29
        $filteredParameters = $this->parameters->filter(
603
            static function (Query\Parameter $parameter) use ($key) : bool {
604 19
                $parameterName = $parameter->getName();
605
606 19
                return $key === $parameterName || (string) $key === (string) $parameterName;
607 29
            }
608
        );
609
610 29
        return $filteredParameters->isEmpty() ? null : $filteredParameters->first();
611
    }
612
613
    /**
614
     * Sets the position of the first result to retrieve (the "offset").
615
     *
616
     * @param int $firstResult The first result to return.
617
     *
618
     * @return self
619
     */
620 2
    public function setFirstResult($firstResult)
621
    {
622 2
        $this->firstResult = $firstResult;
623
624 2
        return $this;
625
    }
626
627
    /**
628
     * Gets the position of the first result the query object was set to retrieve (the "offset").
629
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
630
     *
631
     * @return int The position of the first result.
632
     */
633 2
    public function getFirstResult()
634
    {
635 2
        return $this->firstResult;
636
    }
637
638
    /**
639
     * Sets the maximum number of results to retrieve (the "limit").
640
     *
641
     * @param int|null $maxResults The maximum number of results to retrieve.
642
     *
643
     * @return self
644
     */
645 3
    public function setMaxResults($maxResults)
646
    {
647 3
        $this->maxResults = $maxResults;
648
649 3
        return $this;
650
    }
651
652
    /**
653
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
654
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
655
     *
656
     * @return int|null Maximum number of results.
657
     */
658 2
    public function getMaxResults()
659
    {
660 2
        return $this->maxResults;
661
    }
662
663
    /**
664
     * Either appends to or replaces a single, generic query part.
665
     *
666
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
667
     * 'groupBy', 'having' and 'orderBy'.
668
     *
669
     * @param string         $dqlPartName The DQL part name.
670
     * @param object|mixed[] $dqlPart     An Expr object.
671
     * @param bool           $append      Whether to append (true) or replace (false).
672
     *
673
     * @return self
674
     */
675 127
    public function add($dqlPartName, $dqlPart, $append = false)
676
    {
677 127
        if ($append && ($dqlPartName === 'where' || $dqlPartName === 'having')) {
678 1
            throw new InvalidArgumentException(
679
                "Using \$append = true does not have an effect with 'where' or 'having' " .
680 1
                'parts. See QueryBuilder#andWhere() for an example for correct usage.'
681
            );
682
        }
683
684 127
        $isMultiple = is_array($this->dqlParts[$dqlPartName])
685 127
            && ! ($dqlPartName === 'join' && ! $append);
686
687
        // Allow adding any part retrieved from self::getDQLParts().
688 127
        if (is_array($dqlPart) && $dqlPartName !== 'join') {
689 1
            $dqlPart = reset($dqlPart);
690
        }
691
692
        // This is introduced for backwards compatibility reasons.
693
        // TODO: Remove for 3.0
694 127
        if ($dqlPartName === 'join') {
695 31
            $newDqlPart = [];
696
697 31
            foreach ($dqlPart as $k => $v) {
698 31
                $k = is_numeric($k) ? $this->getRootAlias() : $k;
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated: Please use $qb->getRootAliases() instead. ( Ignorable by Annotation )

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

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

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

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

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