Failed Conditions
Pull Request — master (#8010)
by Oleg
05:58
created

QueryBuilder::setParameters()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

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

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

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