QueryBuilder::setLifetime()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
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\QueryExpressionVisitor;
11
use InvalidArgumentException;
12
use RuntimeException;
13
use function array_keys;
14
use function array_merge;
15
use function array_unshift;
16
use function func_get_args;
17
use function func_num_args;
18
use function implode;
19
use function in_array;
20
use function is_array;
21
use function is_numeric;
22
use function is_object;
23
use function is_string;
24
use function key;
25
use function reset;
26
use function sprintf;
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 129
    public function __construct(EntityManagerInterface $em)
149
    {
150 129
        $this->em         = $em;
151 129
        $this->parameters = new ArrayCollection();
152 129
    }
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 89
    public function getDQL()
305
    {
306 89
        if ($this->dql !== null && $this->state === self::STATE_CLEAN) {
307 44
            return $this->dql;
308
        }
309
310 89
        switch ($this->type) {
311 89
            case self::DELETE:
312 1
                $dql = $this->getDQLForDelete();
313 1
                break;
314
315 88
            case self::UPDATE:
316 3
                $dql = $this->getDQLForUpdate();
317 3
                break;
318
319 86
            case self::SELECT:
320
            default:
321 86
                $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 125
    public function add($dqlPartName, $dqlPart, $append = false)
676
    {
677 125
        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 125
        $isMultiple = is_array($this->dqlParts[$dqlPartName])
685 125
            && ! ($dqlPartName === 'join' && ! $append);
686
687
        // Allow adding any part retrieved from self::getDQLParts().
688 125
        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 125
        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 125
        if ($append && $isMultiple) {
707 119
            if (is_array($dqlPart)) {
708 31
                $key = key($dqlPart);
709
710 31
                $this->dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
711
            } else {
712 119
                $this->dqlParts[$dqlPartName][] = $dqlPart;
713
            }
714
        } else {
715 123
            $this->dqlParts[$dqlPartName] = $isMultiple ? [$dqlPart] : $dqlPart;
716
        }
717
718 125
        $this->state = self::STATE_DIRTY;
719
720 125
        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 1
    public function distinct($flag = true)
766
    {
767 1
        $this->dqlParts['distinct'] = (bool) $flag;
768
769 1
        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 117
    public function from($from, $alias, $indexBy = null)
871
    {
872 117
        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 Query\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 new Query\QueryException(
904
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
905
            );
906
        }
907
908 2
        foreach ($this->dqlParts['from'] as &$fromClause) {
909
            /** @var Expr\From $fromClause */
910 2
            if ($fromClause->getAlias() !== $alias) {
911 1
                continue;
912
            }
913
914 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
915
        }
916
917 2
        return $this;
918
    }
919
920
    /**
921
     * Creates and adds a join over an entity association to the query.
922
     *
923
     * The entities in the joined association will be fetched as part of the query
924
     * result if the alias used for the joined association is placed in the select
925
     * expressions.
926
     *
927
     * <code>
928
     *     $qb = $em->createQueryBuilder()
929
     *         ->select('u')
930
     *         ->from('User', 'u')
931
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
932
     * </code>
933
     *
934
     * @param string      $join          The relationship to join.
935
     * @param string      $alias         The alias of the join.
936
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
937
     * @param string|null $condition     The condition for the join.
938
     * @param string|null $indexBy       The index for the join.
939
     *
940
     * @return self
941
     */
942 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
943
    {
944 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
945
    }
946
947
    /**
948
     * Creates and adds a join over an entity association to the query.
949
     *
950
     * The entities in the joined association will be fetched as part of the query
951
     * result if the alias used for the joined association is placed in the select
952
     * expressions.
953
     *
954
     *     [php]
955
     *     $qb = $em->createQueryBuilder()
956
     *         ->select('u')
957
     *         ->from('User', 'u')
958
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
959
     *
960
     * @param string      $join          The relationship to join.
961
     * @param string      $alias         The alias of the join.
962
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
963
     * @param string|null $condition     The condition for the join.
964
     * @param string|null $indexBy       The index for the join.
965
     *
966
     * @return self
967
     */
968 16
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
969
    {
970 16
        $hasParentAlias = strpos($join, '.');
971 16
        $parentAlias    = substr($join, 0, $hasParentAlias === false ? 0 : $hasParentAlias);
972 16
        $rootAlias      = $this->findRootAlias($alias, $parentAlias);
973 16
        $join           = new Expr\Join(
974 16
            Expr\Join::INNER_JOIN,
975
            $join,
976
            $alias,
977
            $conditionType,
978
            $condition,
979
            $indexBy
980
        );
981
982 16
        return $this->add('join', [$rootAlias => $join], true);
983
    }
984
985
    /**
986
     * Creates and adds a left join over an entity association to the query.
987
     *
988
     * The entities in the joined association will be fetched as part of the query
989
     * result if the alias used for the joined association is placed in the select
990
     * expressions.
991
     *
992
     * <code>
993
     *     $qb = $em->createQueryBuilder()
994
     *         ->select('u')
995
     *         ->from('User', 'u')
996
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
997
     * </code>
998
     *
999
     * @param string      $join          The relationship to join.
1000
     * @param string      $alias         The alias of the join.
1001
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1002
     * @param string|null $condition     The condition for the join.
1003
     * @param string|null $indexBy       The index for the join.
1004
     *
1005
     * @return self
1006
     */
1007 15
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1008
    {
1009 15
        $hasParentAlias = strpos($join, '.');
1010 15
        $parentAlias    = substr($join, 0, $hasParentAlias === false ? 0 : $hasParentAlias);
1011 15
        $rootAlias      = $this->findRootAlias($alias, $parentAlias);
1012 15
        $join           = new Expr\Join(Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy);
1013
1014 15
        return $this->add('join', [$rootAlias => $join], true);
1015
    }
1016
1017
    /**
1018
     * Sets a new value for a field in a bulk update query.
1019
     *
1020
     * <code>
1021
     *     $qb = $em->createQueryBuilder()
1022
     *         ->update('User', 'u')
1023
     *         ->set('u.password', '?1')
1024
     *         ->where('u.id = ?2');
1025
     * </code>
1026
     *
1027
     * @param string $key   The key/field to set.
1028
     * @param mixed  $value The value, expression, placeholder, etc.
1029
     *
1030
     * @return self
1031
     */
1032 3
    public function set($key, $value)
1033
    {
1034 3
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1035
    }
1036
1037
    /**
1038
     * Specifies one or more restrictions to the query result.
1039
     * Replaces any previously specified restrictions, if any.
1040
     *
1041
     * <code>
1042
     *     $qb = $em->createQueryBuilder()
1043
     *         ->select('u')
1044
     *         ->from('User', 'u')
1045
     *         ->where('u.id = ?');
1046
     *
1047
     *     // You can optionally programmatically build and/or expressions
1048
     *     $qb = $em->createQueryBuilder();
1049
     *
1050
     *     $or = $qb->expr()->orX();
1051
     *     $or->add($qb->expr()->eq('u.id', 1));
1052
     *     $or->add($qb->expr()->eq('u.id', 2));
1053
     *
1054
     *     $qb->update('User', 'u')
1055
     *         ->set('u.password', '?')
1056
     *         ->where($or);
1057
     * </code>
1058
     *
1059
     * @param mixed $predicates The restriction predicates.
1060
     */
1061 49
    public function where($predicates)
1062
    {
1063 49
        if (! (func_num_args() === 1 && $predicates instanceof Expr\Composite)) {
1064 46
            $predicates = new Expr\Andx(func_get_args());
1065
        }
1066
1067 49
        return $this->add('where', $predicates);
1068
    }
1069
1070
    /**
1071
     * Adds one or more restrictions to the query results, forming a logical
1072
     * conjunction with any previously specified restrictions.
1073
     *
1074
     * <code>
1075
     *     $qb = $em->createQueryBuilder()
1076
     *         ->select('u')
1077
     *         ->from('User', 'u')
1078
     *         ->where('u.username LIKE ?')
1079
     *         ->andWhere('u.is_active = 1');
1080
     * </code>
1081
     *
1082
     * @see where()
1083
     *
1084
     * @//param mixed $where The query restrictions.
1085
     */
1086 23
    public function andWhere()
1087
    {
1088 23
        $args  = func_get_args();
1089 23
        $where = $this->getDQLPart('where');
1090
1091 23
        if ($where instanceof Expr\Andx) {
1092 11
            $where->addMultiple($args);
1093
        } else {
1094 15
            array_unshift($args, $where);
1095 15
            $where = new Expr\Andx($args);
1096
        }
1097
1098 23
        return $this->add('where', $where);
1099
    }
1100
1101
    /**
1102
     * Adds one or more restrictions to the query results, forming a logical
1103
     * disjunction with any previously specified restrictions.
1104
     *
1105
     * <code>
1106
     *     $qb = $em->createQueryBuilder()
1107
     *         ->select('u')
1108
     *         ->from('User', 'u')
1109
     *         ->where('u.id = 1')
1110
     *         ->orWhere('u.id = 2');
1111
     * </code>
1112
     *
1113
     * @see where()
1114
     *
1115
     * @//param mixed $where The WHERE statement.
1116
     */
1117 5
    public function orWhere()
1118
    {
1119 5
        $args  = func_get_args();
1120 5
        $where = $this->getDQLPart('where');
1121
1122 5
        if ($where instanceof Expr\Orx) {
1123
            $where->addMultiple($args);
1124
        } else {
1125 5
            array_unshift($args, $where);
1126 5
            $where = new Expr\Orx($args);
1127
        }
1128
1129 5
        return $this->add('where', $where);
1130
    }
1131
1132
    /**
1133
     * Specifies a grouping over the results of the query.
1134
     * Replaces any previously specified groupings, if any.
1135
     *
1136
     * <code>
1137
     *     $qb = $em->createQueryBuilder()
1138
     *         ->select('u')
1139
     *         ->from('User', 'u')
1140
     *         ->groupBy('u.id');
1141
     * </code>
1142
     *
1143
     * @param string $groupBy The grouping expression.
1144
     *
1145
     * @return self
1146
     */
1147 7
    public function groupBy($groupBy)
1148
    {
1149 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1150
    }
1151
1152
    /**
1153
     * Adds a grouping expression to the query.
1154
     *
1155
     * <code>
1156
     *     $qb = $em->createQueryBuilder()
1157
     *         ->select('u')
1158
     *         ->from('User', 'u')
1159
     *         ->groupBy('u.lastLogin')
1160
     *         ->addGroupBy('u.createdAt');
1161
     * </code>
1162
     *
1163
     * @param string $groupBy The grouping expression.
1164
     *
1165
     * @return self
1166
     */
1167 1
    public function addGroupBy($groupBy)
1168
    {
1169 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1170
    }
1171
1172
    /**
1173
     * Specifies a restriction over the groups of the query.
1174
     * Replaces any previous having restrictions, if any.
1175
     *
1176
     * @param mixed $having The restriction over the groups.
1177
     *
1178
     * @return self
1179
     */
1180 3
    public function having($having)
1181
    {
1182 3
        if (! (func_num_args() === 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1183 3
            $having = new Expr\Andx(func_get_args());
1184
        }
1185
1186 3
        return $this->add('having', $having);
1187
    }
1188
1189
    /**
1190
     * Adds a restriction over the groups of the query, forming a logical
1191
     * conjunction with any existing having restrictions.
1192
     *
1193
     * @param mixed $having The restriction to append.
1194
     *
1195
     * @return self
1196
     */
1197 2
    public function andHaving($having)
1198
    {
1199 2
        $args   = func_get_args();
1200 2
        $having = $this->getDQLPart('having');
1201
1202 2
        if ($having instanceof Expr\Andx) {
1203 2
            $having->addMultiple($args);
1204
        } else {
1205
            array_unshift($args, $having);
1206
            $having = new Expr\Andx($args);
1207
        }
1208
1209 2
        return $this->add('having', $having);
1210
    }
1211
1212
    /**
1213
     * Adds a restriction over the groups of the query, forming a logical
1214
     * disjunction with any existing having restrictions.
1215
     *
1216
     * @param mixed $having The restriction to add.
1217
     *
1218
     * @return self
1219
     */
1220 1
    public function orHaving($having)
1221
    {
1222 1
        $args   = func_get_args();
1223 1
        $having = $this->getDQLPart('having');
1224
1225 1
        if ($having instanceof Expr\Orx) {
1226
            $having->addMultiple($args);
1227
        } else {
1228 1
            array_unshift($args, $having);
1229 1
            $having = new Expr\Orx($args);
1230
        }
1231
1232 1
        return $this->add('having', $having);
1233
    }
1234
1235
    /**
1236
     * Specifies an ordering for the query results.
1237
     * Replaces any previously specified orderings, if any.
1238
     *
1239
     * @param string|Expr\OrderBy $sort  The ordering expression.
1240
     * @param string              $order The ordering direction.
1241
     *
1242
     * @return self
1243
     */
1244 10
    public function orderBy($sort, $order = null)
1245
    {
1246 10
        $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order);
1247
1248 10
        return $this->add('orderBy', $orderBy);
1249
    }
1250
1251
    /**
1252
     * Adds an ordering to the query results.
1253
     *
1254
     * @param string|Expr\OrderBy $sort  The ordering expression.
1255
     * @param string              $order The ordering direction.
1256
     *
1257
     * @return self
1258
     */
1259 4
    public function addOrderBy($sort, $order = null)
1260
    {
1261 4
        $orderBy = $sort instanceof Expr\OrderBy ? $sort : new Expr\OrderBy($sort, $order);
1262
1263 4
        return $this->add('orderBy', $orderBy, true);
1264
    }
1265
1266
    /**
1267
     * Adds criteria to the query.
1268
     *
1269
     * Adds where expressions with AND operator.
1270
     * Adds orderings.
1271
     * Overrides firstResult and maxResults if they're set.
1272
     *
1273
     * @return self
1274
     *
1275
     * @throws Query\QueryException
1276
     */
1277 13
    public function addCriteria(Criteria $criteria)
1278
    {
1279 13
        $allAliases = $this->getAllAliases();
1280 13
        if (! isset($allAliases[0])) {
1281
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1282
        }
1283
1284 13
        $visitor         = new QueryExpressionVisitor($this->getAllAliases());
1285 13
        $whereExpression = $criteria->getWhereExpression();
1286
1287 13
        if ($whereExpression) {
1288 9
            $this->andWhere($visitor->dispatch($whereExpression));
1289 9
            foreach ($visitor->getParameters() as $parameter) {
1290 9
                $this->parameters->add($parameter);
1291
            }
1292
        }
1293
1294 13
        if ($criteria->getOrderings()) {
1295 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1296 2
                $hasValidAlias = false;
1297 2
                foreach ($allAliases as $alias) {
1298 2
                    if (strpos($sort . '.', $alias . '.') === 0) {
1299 1
                        $hasValidAlias = true;
1300 1
                        break;
1301
                    }
1302
                }
1303
1304 2
                if (! $hasValidAlias) {
1305 1
                    $sort = $allAliases[0] . '.' . $sort;
1306
                }
1307
1308 2
                $this->addOrderBy($sort, $order);
1309
            }
1310
        }
1311
1312 13
        $firstResult = $criteria->getFirstResult();
1313 13
        $maxResults  = $criteria->getMaxResults();
1314
1315
        // Overwrite limits only if they was set in criteria
1316 13
        if ($firstResult !== null) {
1317 1
            $this->setFirstResult($firstResult);
1318
        }
1319 13
        if ($maxResults !== null) {
1320 1
            $this->setMaxResults($maxResults);
1321
        }
1322
1323 13
        return $this;
1324
    }
1325
1326
    /**
1327
     * Gets a query part by its name.
1328
     *
1329
     * @return mixed
1330
     *
1331
     * @todo Rename: getQueryPart (or remove?)
1332
     */
1333 107
    public function getDQLPart($queryPartName)
1334
    {
1335 107
        return $this->dqlParts[$queryPartName];
1336
    }
1337
1338
    /**
1339
     * Gets all query parts.
1340
     *
1341
     * @return mixed[] $dqlParts
1342
     *
1343
     * @todo Rename: getQueryParts (or remove?)
1344
     */
1345 1
    public function getDQLParts()
1346
    {
1347 1
        return $this->dqlParts;
1348
    }
1349
1350
    /**
1351
     * @return string
1352
     */
1353 1
    private function getDQLForDelete()
1354
    {
1355
        return 'DELETE'
1356 1
              . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1357 1
              . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1358 1
              . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1359
    }
1360
1361
    /**
1362
     * @return string
1363
     */
1364 3
    private function getDQLForUpdate()
1365
    {
1366
        return 'UPDATE'
1367 3
              . $this->getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1368 3
              . $this->getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
1369 3
              . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1370 3
              . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1371
    }
1372
1373
    /**
1374
     * @return string
1375
     */
1376 86
    private function getDQLForSelect()
1377
    {
1378
        $dql = 'SELECT'
1379 86
             . ($this->dqlParts['distinct']===true ? ' DISTINCT' : '')
1380 86
             . $this->getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
1381
1382 86
        $fromParts   = $this->getDQLPart('from');
1383 86
        $joinParts   = $this->getDQLPart('join');
1384 86
        $fromClauses = [];
1385
1386
        // Loop through all FROM clauses
1387 86
        if (! empty($fromParts)) {
1388 85
            $dql .= ' FROM ';
1389
1390 85
            foreach ($fromParts as $from) {
1391 85
                $fromClause = (string) $from;
1392
1393 85
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1394 26
                    foreach ($joinParts[$from->getAlias()] as $join) {
1395 26
                        $fromClause .= ' ' . ((string) $join);
1396
                    }
1397
                }
1398
1399 85
                $fromClauses[] = $fromClause;
1400
            }
1401
        }
1402
1403 86
        $dql .= implode(', ', $fromClauses)
1404 86
              . $this->getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1405 86
              . $this->getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
1406 86
              . $this->getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
1407 86
              . $this->getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1408
1409 86
        return $dql;
1410
    }
1411
1412
    /**
1413
     * @param string  $queryPartName
1414
     * @param mixed[] $options
1415
     *
1416
     * @return string
1417
     */
1418 89
    private function getReducedDQLQueryPart($queryPartName, $options = [])
1419
    {
1420 89
        $queryPart = $this->getDQLPart($queryPartName);
1421
1422 89
        if (empty($queryPart)) {
1423 89
            return $options['empty'] ?? '';
1424
        }
1425
1426 89
        return ($options['pre'] ?? '')
1427 89
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1428 89
             . ($options['post'] ?? '');
1429
    }
1430
1431
    /**
1432
     * Resets DQL parts.
1433
     *
1434
     * @param string[]|null $parts
1435
     */
1436 2
    public function resetDQLParts($parts = null)
1437
    {
1438 2
        if ($parts === null) {
1439 1
            $parts = array_keys($this->dqlParts);
1440
        }
1441
1442 2
        foreach ($parts as $part) {
1443 2
            $this->resetDQLPart($part);
1444
        }
1445
1446 2
        return $this;
1447
    }
1448
1449
    /**
1450
     * Resets single DQL part.
1451
     *
1452
     * @param string $part
1453
     *
1454
     * @return self
1455
     */
1456 3
    public function resetDQLPart($part)
1457
    {
1458 3
        $this->dqlParts[$part] = is_array($this->dqlParts[$part]) ? [] : null;
1459 3
        $this->state           = self::STATE_DIRTY;
1460
1461 3
        return $this;
1462
    }
1463
1464
    /**
1465
     * Gets a string representation of this QueryBuilder which corresponds to
1466
     * the final DQL query being constructed.
1467
     *
1468
     * @return string The string representation of this QueryBuilder.
1469
     */
1470 5
    public function __toString()
1471
    {
1472 5
        return $this->getDQL();
1473
    }
1474
1475
    /**
1476
     * Deep clones all expression objects in the DQL parts.
1477
     */
1478 3
    public function __clone()
1479
    {
1480 3
        foreach ($this->dqlParts as $part => $elements) {
1481 3
            if (is_array($this->dqlParts[$part])) {
1482 3
                foreach ($this->dqlParts[$part] as $idx => $element) {
1483 2
                    if (is_object($element)) {
1484 2
                        $this->dqlParts[$part][$idx] = clone $element;
1485
                    }
1486
                }
1487 3
            } elseif (is_object($elements)) {
1488 1
                $this->dqlParts[$part] = clone $elements;
1489
            }
1490
        }
1491
1492 3
        $parameters = [];
1493
1494 3
        foreach ($this->parameters as $parameter) {
1495 1
            $parameters[] = clone $parameter;
1496
        }
1497
1498 3
        $this->parameters = new ArrayCollection($parameters);
1499 3
    }
1500
}
1501