Failed Conditions
Pull Request — master (#6735)
by Matthias
11:21
created

QueryBuilder::setLifetime()   A

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

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

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