Failed Conditions
Push — 2.7 ( c036c0...266f0d )
by Jonathan
57:23 queued 50:07
created

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

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM;
21
22
use Doctrine\Common\Collections\ArrayCollection;
23
use Doctrine\Common\Collections\Criteria;
24
25
use Doctrine\ORM\Query\Expr;
26
use Doctrine\ORM\Query\QueryExpressionVisitor;
27
28
/**
29
 * This class is responsible for building DQL query strings via an object oriented
30
 * PHP interface.
31
 *
32
 * @since 2.0
33
 * @author Guilherme Blanco <[email protected]>
34
 * @author Jonathan Wage <[email protected]>
35
 * @author Roman Borschel <[email protected]>
36
 */
37
class QueryBuilder
38
{
39
    /* The query types. */
40
    const SELECT = 0;
41
    const DELETE = 1;
42
    const UPDATE = 2;
43
44
    /* The builder states. */
45
    const STATE_DIRTY = 0;
46
    const STATE_CLEAN = 1;
47
48
    /**
49
     * The EntityManager used by this QueryBuilder.
50
     *
51
     * @var EntityManagerInterface
52
     */
53
    private $_em;
54
55
    /**
56
     * The array of DQL parts collected.
57
     *
58
     * @var array
59
     */
60
    private $_dqlParts = [
61
        'distinct' => false,
62
        'select'  => [],
63
        'from'    => [],
64
        'join'    => [],
65
        'set'     => [],
66
        'where'   => null,
67
        'groupBy' => [],
68
        'having'  => null,
69
        'orderBy' => []
70
    ];
71
72
    /**
73
     * The type of query this is. Can be select, update or delete.
74
     *
75
     * @var integer
76
     */
77
    private $_type = self::SELECT;
78
79
    /**
80
     * The state of the query object. Can be dirty or clean.
81
     *
82
     * @var integer
83
     */
84
    private $_state = self::STATE_CLEAN;
85
86
    /**
87
     * The complete DQL string for this query.
88
     *
89
     * @var string
90
     */
91
    private $_dql;
92
93
    /**
94
     * The query parameters.
95
     *
96
     * @var \Doctrine\Common\Collections\ArrayCollection
97
     */
98
    private $parameters;
99
100
    /**
101
     * The index of the first result to retrieve.
102
     *
103
     * @var integer
104
     */
105
    private $_firstResult = null;
106
107
    /**
108
     * The maximum number of results to retrieve.
109
     *
110
     * @var integer|null
111
     */
112
    private $_maxResults = null;
113
114
    /**
115
     * Keeps root entity alias names for join entities.
116
     *
117
     * @var array
118
     */
119
    private $joinRootAliases = [];
120
121
     /**
122
     * Whether to use second level cache, if available.
123
     *
124
     * @var boolean
125
     */
126
    protected $cacheable = false;
127
128
    /**
129
     * Second level cache region name.
130
     *
131
     * @var string|null
132
     */
133
    protected $cacheRegion;
134
135
    /**
136
     * Second level query cache mode.
137
     *
138
     * @var integer|null
139
     */
140
    protected $cacheMode;
141
142
    /**
143
     * @var integer
144
     */
145
    protected $lifetime = 0;
146
147
    /**
148
     * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
149
     *
150
     * @param EntityManagerInterface $em The EntityManager to use.
151
     */
152 130
    public function __construct(EntityManagerInterface $em)
153
    {
154 130
        $this->_em = $em;
155 130
        $this->parameters = new ArrayCollection();
156 130
    }
157
158
    /**
159
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
160
     * This producer method is intended for convenient inline usage. Example:
161
     *
162
     * <code>
163
     *     $qb = $em->createQueryBuilder();
164
     *     $qb
165
     *         ->select('u')
166
     *         ->from('User', 'u')
167
     *         ->where($qb->expr()->eq('u.id', 1));
168
     * </code>
169
     *
170
     * For more complex expression construction, consider storing the expression
171
     * builder object in a local variable.
172
     *
173
     * @return Query\Expr
174
     */
175 11
    public function expr()
176
    {
177 11
        return $this->_em->getExpressionBuilder();
178
    }
179
180
    /**
181
     *
182
     * Enable/disable second level query (result) caching for this query.
183
     *
184
     * @param boolean $cacheable
185
     *
186
     * @return self
187
     */
188 1
    public function setCacheable($cacheable)
189
    {
190 1
        $this->cacheable = (boolean) $cacheable;
191
192 1
        return $this;
193
    }
194
195
    /**
196
     * @return boolean TRUE if the query results are enable for second level cache, FALSE otherwise.
197
     */
198 1
    public function isCacheable()
199
    {
200 1
        return $this->cacheable;
201
    }
202
203
    /**
204
     * @param string $cacheRegion
205
     *
206
     * @return self
207
     */
208 1
    public function setCacheRegion($cacheRegion)
209
    {
210 1
        $this->cacheRegion = (string) $cacheRegion;
211
212 1
        return $this;
213
    }
214
215
    /**
216
    * Obtain the name of the second level query cache region in which query results will be stored
217
    *
218
    * @return string|null The cache region name; NULL indicates the default region.
219
    */
220 1
    public function getCacheRegion()
221
    {
222 1
        return $this->cacheRegion;
223
    }
224
225
    /**
226
     * @return integer
227
     */
228 1
    public function getLifetime()
229
    {
230 1
        return $this->lifetime;
231
    }
232
233
    /**
234
     * Sets the life-time for this query into second level cache.
235
     *
236
     * @param integer $lifetime
237
     *
238
     * @return self
239
     */
240 1
    public function setLifetime($lifetime)
241
    {
242 1
        $this->lifetime = (integer) $lifetime;
243
244 1
        return $this;
245
    }
246
247
    /**
248
     * @return integer
249
     */
250 1
    public function getCacheMode()
251
    {
252 1
        return $this->cacheMode;
253
    }
254
255
    /**
256
     * @param integer $cacheMode
257
     *
258
     * @return self
259
     */
260 1
    public function setCacheMode($cacheMode)
261
    {
262 1
        $this->cacheMode = (integer) $cacheMode;
263
264 1
        return $this;
265
    }
266
267
    /**
268
     * Gets the type of the currently built query.
269
     *
270
     * @return integer
271
     */
272 4
    public function getType()
273
    {
274 4
        return $this->_type;
275
    }
276
277
    /**
278
     * Gets the associated EntityManager for this query builder.
279
     *
280
     * @return EntityManager
281
     */
282 1
    public function getEntityManager()
283
    {
284 1
        return $this->_em;
285
    }
286
287
    /**
288
     * Gets the state of this query builder instance.
289
     *
290
     * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
291
     */
292 2
    public function getState()
293
    {
294 2
        return $this->_state;
295
    }
296
297
    /**
298
     * Gets the complete DQL string formed by the current specifications of this QueryBuilder.
299
     *
300
     * <code>
301
     *     $qb = $em->createQueryBuilder()
302
     *         ->select('u')
303
     *         ->from('User', 'u');
304
     *     echo $qb->getDql(); // SELECT u FROM User u
305
     * </code>
306
     *
307
     * @return string The DQL query string.
308
     */
309 90
    public function getDQL()
310
    {
311 90
        if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
312 44
            return $this->_dql;
313
        }
314
315 90
        switch ($this->_type) {
316 90
            case self::DELETE:
317 1
                $dql = $this->_getDQLForDelete();
318 1
                break;
319
320 89
            case self::UPDATE:
321 3
                $dql = $this->_getDQLForUpdate();
322 3
                break;
323
324 87
            case self::SELECT:
325
            default:
326 87
                $dql = $this->_getDQLForSelect();
327 87
                break;
328
        }
329
330 90
        $this->_state = self::STATE_CLEAN;
331 90
        $this->_dql   = $dql;
332
333 90
        return $dql;
334
    }
335
336
    /**
337
     * Constructs a Query instance from the current specifications of the builder.
338
     *
339
     * <code>
340
     *     $qb = $em->createQueryBuilder()
341
     *         ->select('u')
342
     *         ->from('User', 'u');
343
     *     $q = $qb->getQuery();
344
     *     $results = $q->execute();
345
     * </code>
346
     *
347
     * @return Query
348
     */
349 79
    public function getQuery()
350
    {
351 79
        $parameters = clone $this->parameters;
352 79
        $query      = $this->_em->createQuery($this->getDQL())
353 79
            ->setParameters($parameters)
354 79
            ->setFirstResult($this->_firstResult)
355 79
            ->setMaxResults($this->_maxResults);
356
357 79
        if ($this->lifetime) {
358 1
            $query->setLifetime($this->lifetime);
359
        }
360
361 79
        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...
362 1
            $query->setCacheMode($this->cacheMode);
363
        }
364
365 79
        if ($this->cacheable) {
366 1
            $query->setCacheable($this->cacheable);
367
        }
368
369 79
        if ($this->cacheRegion) {
370 1
            $query->setCacheRegion($this->cacheRegion);
371
        }
372
373 79
        return $query;
374
    }
375
376
    /**
377
     * Finds the root entity alias of the joined entity.
378
     *
379
     * @param string $alias       The alias of the new join entity
380
     * @param string $parentAlias The parent entity alias of the join relationship
381
     *
382
     * @return string
383
     */
384 30
    private function findRootAlias($alias, $parentAlias)
385
    {
386 30
        $rootAlias = null;
387
388 30
        if (in_array($parentAlias, $this->getRootAliases())) {
389 29
            $rootAlias = $parentAlias;
390 7
        } elseif (isset($this->joinRootAliases[$parentAlias])) {
391 6
            $rootAlias = $this->joinRootAliases[$parentAlias];
392
        } else {
393
            // Should never happen with correct joining order. Might be
394
            // thoughtful to throw exception instead.
395 1
            $rootAlias = $this->getRootAlias();
396
        }
397
398 30
        $this->joinRootAliases[$alias] = $rootAlias;
399
400 30
        return $rootAlias;
401
    }
402
403
    /**
404
     * Gets the FIRST root alias of the query. This is the first entity alias involved
405
     * in the construction of the query.
406
     *
407
     * <code>
408
     * $qb = $em->createQueryBuilder()
409
     *     ->select('u')
410
     *     ->from('User', 'u');
411
     *
412
     * echo $qb->getRootAlias(); // u
413
     * </code>
414
     *
415
     * @deprecated Please use $qb->getRootAliases() instead.
416
     * @throws \RuntimeException
417
     *
418
     * @return string
419
     */
420 4
    public function getRootAlias()
421
    {
422 4
        $aliases = $this->getRootAliases();
423
424 4
        if ( ! isset($aliases[0])) {
425
            throw new \RuntimeException('No alias was set before invoking getRootAlias().');
426
        }
427
428 4
        return $aliases[0];
429
    }
430
431
    /**
432
     * Gets the root aliases of the query. This is the entity aliases involved
433
     * in the construction of the query.
434
     *
435
     * <code>
436
     *     $qb = $em->createQueryBuilder()
437
     *         ->select('u')
438
     *         ->from('User', 'u');
439
     *
440
     *     $qb->getRootAliases(); // array('u')
441
     * </code>
442
     *
443
     * @return array
444
     */
445 46
    public function getRootAliases()
446
    {
447 46
        $aliases = [];
448
449 46
        foreach ($this->_dqlParts['from'] as &$fromClause) {
450 46
            if (is_string($fromClause)) {
451
                $spacePos = strrpos($fromClause, ' ');
452
                $from     = substr($fromClause, 0, $spacePos);
453
                $alias    = substr($fromClause, $spacePos + 1);
454
455
                $fromClause = new Query\Expr\From($from, $alias);
456
            }
457
458 46
            $aliases[] = $fromClause->getAlias();
459
        }
460
461 46
        return $aliases;
462
    }
463
464
    /**
465
     * Gets all the aliases that have been used in the query.
466
     * Including all select root aliases and join aliases
467
     *
468
     * <code>
469
     *     $qb = $em->createQueryBuilder()
470
     *         ->select('u')
471
     *         ->from('User', 'u')
472
     *         ->join('u.articles','a');
473
     *
474
     *     $qb->getAllAliases(); // array('u','a')
475
     * </code>
476
     * @return array
477
     */
478 15
    public function getAllAliases()
479
    {
480 15
        return array_merge($this->getRootAliases(), array_keys($this->joinRootAliases));
481
    }
482
483
    /**
484
     * Gets the root entities of the query. This is the entity aliases involved
485
     * in the construction of the query.
486
     *
487
     * <code>
488
     *     $qb = $em->createQueryBuilder()
489
     *         ->select('u')
490
     *         ->from('User', 'u');
491
     *
492
     *     $qb->getRootEntities(); // array('User')
493
     * </code>
494
     *
495
     * @return array
496
     */
497 1
    public function getRootEntities()
498
    {
499 1
        $entities = [];
500
501 1
        foreach ($this->_dqlParts['from'] as &$fromClause) {
502 1
            if (is_string($fromClause)) {
503
                $spacePos = strrpos($fromClause, ' ');
504
                $from     = substr($fromClause, 0, $spacePos);
505
                $alias    = substr($fromClause, $spacePos + 1);
506
507
                $fromClause = new Query\Expr\From($from, $alias);
508
            }
509
510 1
            $entities[] = $fromClause->getFrom();
511
        }
512
513 1
        return $entities;
514
    }
515
516
    /**
517
     * Sets a query parameter for the query being constructed.
518
     *
519
     * <code>
520
     *     $qb = $em->createQueryBuilder()
521
     *         ->select('u')
522
     *         ->from('User', 'u')
523
     *         ->where('u.id = :user_id')
524
     *         ->setParameter('user_id', 1);
525
     * </code>
526
     *
527
     * @param string|integer $key   The parameter position or name.
528
     * @param mixed          $value The parameter value.
529
     * @param string|integer|null    $type  PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
530
     *
531
     * @return self
532
     */
533 20
    public function setParameter($key, $value, $type = null)
534
    {
535 20
        $existingParameter = $this->getParameter($key);
536
537 20
        if ($existingParameter !== null) {
538 1
            $existingParameter->setValue($value, $type);
539
540 1
            return $this;
541
        }
542
543 20
        $this->parameters->add(new Query\Parameter($key, $value, $type));
544
545 20
        return $this;
546
    }
547
548
    /**
549
     * Sets a collection of query parameters for the query being constructed.
550
     *
551
     * <code>
552
     *     $qb = $em->createQueryBuilder()
553
     *         ->select('u')
554
     *         ->from('User', 'u')
555
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
556
     *         ->setParameters(new ArrayCollection(array(
557
     *             new Parameter('user_id1', 1),
558
     *             new Parameter('user_id2', 2)
559
     *        )));
560
     * </code>
561
     *
562
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters to set.
563
     *
564
     * @return self
565
     */
566 4
    public function setParameters($parameters)
567
    {
568
        // BC compatibility with 2.3-
569 4
        if (is_array($parameters)) {
570 1
            $parameterCollection = new ArrayCollection();
571
572 1
            foreach ($parameters as $key => $value) {
573 1
                $parameter = new Query\Parameter($key, $value);
574
575 1
                $parameterCollection->add($parameter);
576
            }
577
578 1
            $parameters = $parameterCollection;
579
        }
580
581 4
        $this->parameters = $parameters;
582
583 4
        return $this;
584
    }
585
586
    /**
587
     * Gets all defined query parameters for the query being constructed.
588
     *
589
     * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
590
     */
591 6
    public function getParameters()
592
    {
593 6
        return $this->parameters;
594
    }
595
596
    /**
597
     * Gets a (previously set) query parameter of the query being constructed.
598
     *
599
     * @param mixed $key The key (index or name) of the bound parameter.
600
     *
601
     * @return Query\Parameter|null The value of the bound parameter.
602
     */
603 30
    public function getParameter($key)
604
    {
605 30
        $filteredParameters = $this->parameters->filter(
606 30
            function (Query\Parameter $parameter) use ($key) : bool {
607 19
                $parameterName = $parameter->getName();
608
609 19
                return $key === $parameterName || (string) $key === (string) $parameterName;
610 30
            }
611
        );
612
613 30
        return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
614
    }
615
616
    /**
617
     * Sets the position of the first result to retrieve (the "offset").
618
     *
619
     * @param integer $firstResult The first result to return.
620
     *
621
     * @return self
622
     */
623 2
    public function setFirstResult($firstResult)
624
    {
625 2
        $this->_firstResult = $firstResult;
626
627 2
        return $this;
628
    }
629
630
    /**
631
     * Gets the position of the first result the query object was set to retrieve (the "offset").
632
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
633
     *
634
     * @return integer The position of the first result.
635
     */
636 2
    public function getFirstResult()
637
    {
638 2
        return $this->_firstResult;
639
    }
640
641
    /**
642
     * Sets the maximum number of results to retrieve (the "limit").
643
     *
644
     * @param integer|null $maxResults The maximum number of results to retrieve.
645
     *
646
     * @return self
647
     */
648 3
    public function setMaxResults($maxResults)
649
    {
650 3
        $this->_maxResults = $maxResults;
651
652 3
        return $this;
653
    }
654
655
    /**
656
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
657
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
658
     *
659
     * @return integer|null Maximum number of results.
660
     */
661 2
    public function getMaxResults()
662
    {
663 2
        return $this->_maxResults;
664
    }
665
666
    /**
667
     * Either appends to or replaces a single, generic query part.
668
     *
669
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
670
     * 'groupBy', 'having' and 'orderBy'.
671
     *
672
     * @param string       $dqlPartName The DQL part name.
673
     * @param object|array $dqlPart     An Expr object.
674
     * @param bool         $append      Whether to append (true) or replace (false).
675
     *
676
     * @return self
677
     */
678 126
    public function add($dqlPartName, $dqlPart, $append = false)
679
    {
680 126
        if ($append && ($dqlPartName === "where" || $dqlPartName === "having")) {
681 1
            throw new \InvalidArgumentException(
682
                "Using \$append = true does not have an effect with 'where' or 'having' ".
683 1
                "parts. See QueryBuilder#andWhere() for an example for correct usage."
684
            );
685
        }
686
687 126
        $isMultiple = is_array($this->_dqlParts[$dqlPartName])
688 126
            && !($dqlPartName == 'join' && !$append);
689
690
        // Allow adding any part retrieved from self::getDQLParts().
691 126
        if (is_array($dqlPart) && $dqlPartName != 'join') {
692 1
            $dqlPart = reset($dqlPart);
693
        }
694
695
        // This is introduced for backwards compatibility reasons.
696
        // TODO: Remove for 3.0
697 126
        if ($dqlPartName == 'join') {
698 31
            $newDqlPart = [];
699
700 31
            foreach ($dqlPart as $k => $v) {
701 31
                $k = is_numeric($k) ? $this->getRootAlias() : $k;
702
703 31
                $newDqlPart[$k] = $v;
704
            }
705
706 31
            $dqlPart = $newDqlPart;
707
        }
708
709 126
        if ($append && $isMultiple) {
710 120
            if (is_array($dqlPart)) {
711 31
                $key = key($dqlPart);
712
713 31
                $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
714
            } else {
715 120
                $this->_dqlParts[$dqlPartName][] = $dqlPart;
716
            }
717
        } else {
718 124
            $this->_dqlParts[$dqlPartName] = ($isMultiple) ? [$dqlPart] : $dqlPart;
719
        }
720
721 126
        $this->_state = self::STATE_DIRTY;
722
723 126
        return $this;
724
    }
725
726
    /**
727
     * Specifies an item that is to be returned in the query result.
728
     * Replaces any previously specified selections, if any.
729
     *
730
     * <code>
731
     *     $qb = $em->createQueryBuilder()
732
     *         ->select('u', 'p')
733
     *         ->from('User', 'u')
734
     *         ->leftJoin('u.Phonenumbers', 'p');
735
     * </code>
736
     *
737
     * @param mixed $select The selection expressions.
738
     *
739
     * @return self
740
     */
741 119
    public function select($select = null)
742
    {
743 119
        $this->_type = self::SELECT;
744
745 119
        if (empty($select)) {
746 1
            return $this;
747
        }
748
749 118
        $selects = is_array($select) ? $select : func_get_args();
750
751 118
        return $this->add('select', new Expr\Select($selects), false);
752
    }
753
754
    /**
755
     * Adds a DISTINCT flag to this query.
756
     *
757
     * <code>
758
     *     $qb = $em->createQueryBuilder()
759
     *         ->select('u')
760
     *         ->distinct()
761
     *         ->from('User', 'u');
762
     * </code>
763
     *
764
     * @param bool $flag
765
     *
766
     * @return self
767
     */
768 1
    public function distinct($flag = true)
769
    {
770 1
        $this->_dqlParts['distinct'] = (bool) $flag;
771
772 1
        return $this;
773
    }
774
775
    /**
776
     * Adds an item that is to be returned in the query result.
777
     *
778
     * <code>
779
     *     $qb = $em->createQueryBuilder()
780
     *         ->select('u')
781
     *         ->addSelect('p')
782
     *         ->from('User', 'u')
783
     *         ->leftJoin('u.Phonenumbers', 'p');
784
     * </code>
785
     *
786
     * @param mixed $select The selection expression.
787
     *
788
     * @return self
789
     */
790 1
    public function addSelect($select = null)
791
    {
792 1
        $this->_type = self::SELECT;
793
794 1
        if (empty($select)) {
795
            return $this;
796
        }
797
798 1
        $selects = is_array($select) ? $select : func_get_args();
799
800 1
        return $this->add('select', new Expr\Select($selects), true);
801
    }
802
803
    /**
804
     * Turns the query being built into a bulk delete query that ranges over
805
     * a certain entity type.
806
     *
807
     * <code>
808
     *     $qb = $em->createQueryBuilder()
809
     *         ->delete('User', 'u')
810
     *         ->where('u.id = :user_id')
811
     *         ->setParameter('user_id', 1);
812
     * </code>
813
     *
814
     * @param string $delete The class/type whose instances are subject to the deletion.
815
     * @param string $alias  The class/type alias used in the constructed query.
816
     *
817
     * @return self
818
     */
819 4
    public function delete($delete = null, $alias = null)
820
    {
821 4
        $this->_type = self::DELETE;
822
823 4
        if ( ! $delete) {
824 1
            return $this;
825
        }
826
827 3
        return $this->add('from', new Expr\From($delete, $alias));
828
    }
829
830
    /**
831
     * Turns the query being built into a bulk update query that ranges over
832
     * a certain entity type.
833
     *
834
     * <code>
835
     *     $qb = $em->createQueryBuilder()
836
     *         ->update('User', 'u')
837
     *         ->set('u.password', '?1')
838
     *         ->where('u.id = ?2');
839
     * </code>
840
     *
841
     * @param string $update The class/type whose instances are subject to the update.
842
     * @param string $alias  The class/type alias used in the constructed query.
843
     *
844
     * @return self
845
     */
846 4
    public function update($update = null, $alias = null)
847
    {
848 4
        $this->_type = self::UPDATE;
849
850 4
        if ( ! $update) {
851 1
            return $this;
852
        }
853
854 3
        return $this->add('from', new Expr\From($update, $alias));
855
    }
856
857
    /**
858
     * Creates and adds a query root corresponding to the entity identified by the given alias,
859
     * forming a cartesian product with any existing query roots.
860
     *
861
     * <code>
862
     *     $qb = $em->createQueryBuilder()
863
     *         ->select('u')
864
     *         ->from('User', 'u');
865
     * </code>
866
     *
867
     * @param string $from    The class name.
868
     * @param string $alias   The alias of the class.
869
     * @param string $indexBy The index for the from.
870
     *
871
     * @return self
872
     */
873 118
    public function from($from, $alias, $indexBy = null)
874
    {
875 118
        return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
876
    }
877
878
    /**
879
     * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
880
     * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
881
     * setting an index by.
882
     *
883
     * <code>
884
     *     $qb = $userRepository->createQueryBuilder('u')
885
     *         ->indexBy('u', 'u.id');
886
     *
887
     *     // Is equivalent to...
888
     *
889
     *     $qb = $em->createQueryBuilder()
890
     *         ->select('u')
891
     *         ->from('User', 'u', 'u.id');
892
     * </code>
893
     *
894
     * @param string $alias   The root alias of the class.
895
     * @param string $indexBy The index for the from.
896
     *
897
     * @return self
898
     *
899
     * @throws Query\QueryException
900
     */
901 2
    public function indexBy($alias, $indexBy)
902
    {
903 2
        $rootAliases = $this->getRootAliases();
904
905 2
        if (!in_array($alias, $rootAliases)) {
906
            throw new Query\QueryException(
907
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
908
            );
909
        }
910
911 2
        foreach ($this->_dqlParts['from'] as &$fromClause) {
912
            /* @var Expr\From $fromClause */
913 2
            if ($fromClause->getAlias() !== $alias) {
914 1
                continue;
915
            }
916
917 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
918
        }
919
920 2
        return $this;
921
    }
922
923
    /**
924
     * Creates and adds a join over an entity association to the query.
925
     *
926
     * The entities in the joined association will be fetched as part of the query
927
     * result if the alias used for the joined association is placed in the select
928
     * expressions.
929
     *
930
     * <code>
931
     *     $qb = $em->createQueryBuilder()
932
     *         ->select('u')
933
     *         ->from('User', 'u')
934
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
935
     * </code>
936
     *
937
     * @param string      $join          The relationship to join.
938
     * @param string      $alias         The alias of the join.
939
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
940
     * @param string|null $condition     The condition for the join.
941
     * @param string|null $indexBy       The index for the join.
942
     *
943
     * @return self
944
     */
945 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
946
    {
947 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
948
    }
949
950
    /**
951
     * Creates and adds a join over an entity association to the query.
952
     *
953
     * The entities in the joined association will be fetched as part of the query
954
     * result if the alias used for the joined association is placed in the select
955
     * expressions.
956
     *
957
     *     [php]
958
     *     $qb = $em->createQueryBuilder()
959
     *         ->select('u')
960
     *         ->from('User', 'u')
961
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
962
     *
963
     * @param string      $join          The relationship to join.
964
     * @param string      $alias         The alias of the join.
965
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
966
     * @param string|null $condition     The condition for the join.
967
     * @param string|null $indexBy       The index for the join.
968
     *
969
     * @return self
970
     */
971 16
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
972
    {
973 16
        $parentAlias = substr($join, 0, strpos($join, '.'));
974
975 16
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
976
977 16
        $join = new Expr\Join(
978 16
            Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
979
        );
980
981 16
        return $this->add('join', [$rootAlias => $join], true);
982
    }
983
984
    /**
985
     * Creates and adds a left join over an entity association to the query.
986
     *
987
     * The entities in the joined association will be fetched as part of the query
988
     * result if the alias used for the joined association is placed in the select
989
     * expressions.
990
     *
991
     * <code>
992
     *     $qb = $em->createQueryBuilder()
993
     *         ->select('u')
994
     *         ->from('User', 'u')
995
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
996
     * </code>
997
     *
998
     * @param string      $join          The relationship to join.
999
     * @param string      $alias         The alias of the join.
1000
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1001
     * @param string|null $condition     The condition for the join.
1002
     * @param string|null $indexBy       The index for the join.
1003
     *
1004
     * @return self
1005
     */
1006 15
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1007
    {
1008 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
1009
1010 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
1011
1012 15
        $join = new Expr\Join(
1013 15
            Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
1014
        );
1015
1016 15
        return $this->add('join', [$rootAlias => $join], true);
1017
    }
1018
1019
    /**
1020
     * Sets a new value for a field in a bulk update query.
1021
     *
1022
     * <code>
1023
     *     $qb = $em->createQueryBuilder()
1024
     *         ->update('User', 'u')
1025
     *         ->set('u.password', '?1')
1026
     *         ->where('u.id = ?2');
1027
     * </code>
1028
     *
1029
     * @param string $key   The key/field to set.
1030
     * @param mixed  $value The value, expression, placeholder, etc.
1031
     *
1032
     * @return self
1033
     */
1034 3
    public function set($key, $value)
1035
    {
1036 3
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1037
    }
1038
1039
    /**
1040
     * Specifies one or more restrictions to the query result.
1041
     * Replaces any previously specified restrictions, if any.
1042
     *
1043
     * <code>
1044
     *     $qb = $em->createQueryBuilder()
1045
     *         ->select('u')
1046
     *         ->from('User', 'u')
1047
     *         ->where('u.id = ?');
1048
     *
1049
     *     // You can optionally programmatically build and/or expressions
1050
     *     $qb = $em->createQueryBuilder();
1051
     *
1052
     *     $or = $qb->expr()->orX();
1053
     *     $or->add($qb->expr()->eq('u.id', 1));
1054
     *     $or->add($qb->expr()->eq('u.id', 2));
1055
     *
1056
     *     $qb->update('User', 'u')
1057
     *         ->set('u.password', '?')
1058
     *         ->where($or);
1059
     * </code>
1060
     *
1061
     * @param mixed $predicates The restriction predicates.
1062
     *
1063
     * @return self
1064
     */
1065 50
    public function where($predicates)
1066
    {
1067 50
        if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
1068 47
            $predicates = new Expr\Andx(func_get_args());
1069
        }
1070
1071 50
        return $this->add('where', $predicates);
1072
    }
1073
1074
    /**
1075
     * Adds one or more restrictions to the query results, forming a logical
1076
     * conjunction with any previously specified restrictions.
1077
     *
1078
     * <code>
1079
     *     $qb = $em->createQueryBuilder()
1080
     *         ->select('u')
1081
     *         ->from('User', 'u')
1082
     *         ->where('u.username LIKE ?')
1083
     *         ->andWhere('u.is_active = 1');
1084
     * </code>
1085
     *
1086
     * @param mixed $where The query restrictions.
1087
     *
1088
     * @return self
1089
     *
1090
     * @see where()
1091
     */
1092 23
    public function andWhere()
1093
    {
1094 23
        $args  = func_get_args();
1095 23
        $where = $this->getDQLPart('where');
1096
1097 23
        if ($where instanceof Expr\Andx) {
1098 11
            $where->addMultiple($args);
1099
        } else {
1100 15
            array_unshift($args, $where);
1101 15
            $where = new Expr\Andx($args);
1102
        }
1103
1104 23
        return $this->add('where', $where);
1105
    }
1106
1107
    /**
1108
     * Adds one or more restrictions to the query results, forming a logical
1109
     * disjunction with any previously specified restrictions.
1110
     *
1111
     * <code>
1112
     *     $qb = $em->createQueryBuilder()
1113
     *         ->select('u')
1114
     *         ->from('User', 'u')
1115
     *         ->where('u.id = 1')
1116
     *         ->orWhere('u.id = 2');
1117
     * </code>
1118
     *
1119
     * @param mixed $where The WHERE statement.
1120
     *
1121
     * @return self
1122
     *
1123
     * @see where()
1124
     */
1125 5
    public function orWhere()
1126
    {
1127 5
        $args  = func_get_args();
1128 5
        $where = $this->getDQLPart('where');
1129
1130 5
        if ($where instanceof Expr\Orx) {
1131
            $where->addMultiple($args);
1132
        } else {
1133 5
            array_unshift($args, $where);
1134 5
            $where = new Expr\Orx($args);
1135
        }
1136
1137 5
        return $this->add('where', $where);
1138
    }
1139
1140
    /**
1141
     * Specifies a grouping over the results of the query.
1142
     * Replaces any previously specified groupings, if any.
1143
     *
1144
     * <code>
1145
     *     $qb = $em->createQueryBuilder()
1146
     *         ->select('u')
1147
     *         ->from('User', 'u')
1148
     *         ->groupBy('u.id');
1149
     * </code>
1150
     *
1151
     * @param string $groupBy The grouping expression.
1152
     *
1153
     * @return self
1154
     */
1155 7
    public function groupBy($groupBy)
1156
    {
1157 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1158
    }
1159
1160
    /**
1161
     * Adds a grouping expression to the query.
1162
     *
1163
     * <code>
1164
     *     $qb = $em->createQueryBuilder()
1165
     *         ->select('u')
1166
     *         ->from('User', 'u')
1167
     *         ->groupBy('u.lastLogin')
1168
     *         ->addGroupBy('u.createdAt');
1169
     * </code>
1170
     *
1171
     * @param string $groupBy The grouping expression.
1172
     *
1173
     * @return self
1174
     */
1175 1
    public function addGroupBy($groupBy)
1176
    {
1177 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1178
    }
1179
1180
    /**
1181
     * Specifies a restriction over the groups of the query.
1182
     * Replaces any previous having restrictions, if any.
1183
     *
1184
     * @param mixed $having The restriction over the groups.
1185
     *
1186
     * @return self
1187
     */
1188 3
    public function having($having)
1189
    {
1190 3
        if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1191 3
            $having = new Expr\Andx(func_get_args());
1192
        }
1193
1194 3
        return $this->add('having', $having);
1195
    }
1196
1197
    /**
1198
     * Adds a restriction over the groups of the query, forming a logical
1199
     * conjunction with any existing having restrictions.
1200
     *
1201
     * @param mixed $having The restriction to append.
1202
     *
1203
     * @return self
1204
     */
1205 2
    public function andHaving($having)
1206
    {
1207 2
        $args   = func_get_args();
1208 2
        $having = $this->getDQLPart('having');
1209
1210 2
        if ($having instanceof Expr\Andx) {
1211 2
            $having->addMultiple($args);
1212
        } else {
1213
            array_unshift($args, $having);
1214
            $having = new Expr\Andx($args);
1215
        }
1216
1217 2
        return $this->add('having', $having);
1218
    }
1219
1220
    /**
1221
     * Adds a restriction over the groups of the query, forming a logical
1222
     * disjunction with any existing having restrictions.
1223
     *
1224
     * @param mixed $having The restriction to add.
1225
     *
1226
     * @return self
1227
     */
1228 1
    public function orHaving($having)
1229
    {
1230 1
        $args   = func_get_args();
1231 1
        $having = $this->getDQLPart('having');
1232
1233 1
        if ($having instanceof Expr\Orx) {
1234
            $having->addMultiple($args);
1235
        } else {
1236 1
            array_unshift($args, $having);
1237 1
            $having = new Expr\Orx($args);
1238
        }
1239
1240 1
        return $this->add('having', $having);
1241
    }
1242
1243
    /**
1244
     * Specifies an ordering for the query results.
1245
     * Replaces any previously specified orderings, if any.
1246
     *
1247
     * @param string|Expr\OrderBy $sort  The ordering expression.
1248
     * @param string              $order The ordering direction.
1249
     *
1250
     * @return self
1251
     */
1252 10
    public function orderBy($sort, $order = null)
1253
    {
1254 10
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1255
1256 10
        return $this->add('orderBy', $orderBy);
1257
    }
1258
1259
    /**
1260
     * Adds an ordering to the query results.
1261
     *
1262
     * @param string|Expr\OrderBy $sort  The ordering expression.
1263
     * @param string              $order The ordering direction.
1264
     *
1265
     * @return self
1266
     */
1267 4
    public function addOrderBy($sort, $order = null)
1268
    {
1269 4
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1270
1271 4
        return $this->add('orderBy', $orderBy, true);
1272
    }
1273
1274
    /**
1275
     * Adds criteria to the query.
1276
     *
1277
     * Adds where expressions with AND operator.
1278
     * Adds orderings.
1279
     * Overrides firstResult and maxResults if they're set.
1280
     *
1281
     * @param Criteria $criteria
1282
     *
1283
     * @return self
1284
     *
1285
     * @throws Query\QueryException
1286
     */
1287 13
    public function addCriteria(Criteria $criteria)
1288
    {
1289 13
        $allAliases = $this->getAllAliases();
1290 13
        if ( ! isset($allAliases[0])) {
1291
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1292
        }
1293
1294 13
        $visitor = new QueryExpressionVisitor($this->getAllAliases());
1295
1296 13
        if ($whereExpression = $criteria->getWhereExpression()) {
1297 9
            $this->andWhere($visitor->dispatch($whereExpression));
1298 9
            foreach ($visitor->getParameters() as $parameter) {
1299 9
                $this->parameters->add($parameter);
1300
            }
1301
        }
1302
1303 13
        if ($criteria->getOrderings()) {
1304 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1305
1306 2
                $hasValidAlias = false;
1307 2
                foreach($allAliases as $alias) {
1308 2
                    if(strpos($sort . '.', $alias . '.') === 0) {
1309 1
                        $hasValidAlias = true;
1310 2
                        break;
1311
                    }
1312
                }
1313
1314 2
                if(!$hasValidAlias) {
1315 1
                    $sort = $allAliases[0] . '.' . $sort;
1316
                }
1317
1318 2
                $this->addOrderBy($sort, $order);
1319
            }
1320
        }
1321
1322
        // Overwrite limits only if they was set in criteria
1323 13
        if (($firstResult = $criteria->getFirstResult()) !== null) {
1324 1
            $this->setFirstResult($firstResult);
1325
        }
1326 13
        if (($maxResults = $criteria->getMaxResults()) !== null) {
1327 1
            $this->setMaxResults($maxResults);
1328
        }
1329
1330 13
        return $this;
1331
    }
1332
1333
    /**
1334
     * Gets a query part by its name.
1335
     *
1336
     * @param string $queryPartName
1337
     *
1338
     * @return mixed $queryPart
1339
     *
1340
     * @todo Rename: getQueryPart (or remove?)
1341
     */
1342 108
    public function getDQLPart($queryPartName)
1343
    {
1344 108
        return $this->_dqlParts[$queryPartName];
1345
    }
1346
1347
    /**
1348
     * Gets all query parts.
1349
     *
1350
     * @return array $dqlParts
1351
     *
1352
     * @todo Rename: getQueryParts (or remove?)
1353
     */
1354 1
    public function getDQLParts()
1355
    {
1356 1
        return $this->_dqlParts;
1357
    }
1358
1359
    /**
1360
     * @return string
1361
     */
1362 1
    private function _getDQLForDelete()
1363
    {
1364
         return 'DELETE'
1365 1
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1366 1
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1367 1
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1368
    }
1369
1370
    /**
1371
     * @return string
1372
     */
1373 3
    private function _getDQLForUpdate()
1374
    {
1375
         return 'UPDATE'
1376 3
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1377 3
              . $this->_getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
1378 3
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1379 3
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1380
    }
1381
1382
    /**
1383
     * @return string
1384
     */
1385 87
    private function _getDQLForSelect()
1386
    {
1387
        $dql = 'SELECT'
1388 87
             . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
1389 87
             . $this->_getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
1390
1391 87
        $fromParts   = $this->getDQLPart('from');
1392 87
        $joinParts   = $this->getDQLPart('join');
1393 87
        $fromClauses = [];
1394
1395
        // Loop through all FROM clauses
1396 87
        if ( ! empty($fromParts)) {
1397 86
            $dql .= ' FROM ';
1398
1399 86
            foreach ($fromParts as $from) {
1400 86
                $fromClause = (string) $from;
1401
1402 86
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1403 26
                    foreach ($joinParts[$from->getAlias()] as $join) {
1404 26
                        $fromClause .= ' ' . ((string) $join);
1405
                    }
1406
                }
1407
1408 86
                $fromClauses[] = $fromClause;
1409
            }
1410
        }
1411
1412 87
        $dql .= implode(', ', $fromClauses)
1413 87
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1414 87
              . $this->_getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
1415 87
              . $this->_getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
1416 87
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1417
1418 87
        return $dql;
1419
    }
1420
1421
    /**
1422
     * @param string $queryPartName
1423
     * @param array  $options
1424
     *
1425
     * @return string
1426
     */
1427 90
    private function _getReducedDQLQueryPart($queryPartName, $options = [])
1428
    {
1429 90
        $queryPart = $this->getDQLPart($queryPartName);
1430
1431 90
        if (empty($queryPart)) {
1432 90
            return ($options['empty'] ?? '');
1433
        }
1434
1435 90
        return ($options['pre'] ?? '')
1436 90
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1437 90
             . ($options['post'] ?? '');
1438
    }
1439
1440
    /**
1441
     * Resets DQL parts.
1442
     *
1443
     * @param array|null $parts
1444
     *
1445
     * @return self
1446
     */
1447 2
    public function resetDQLParts($parts = null)
1448
    {
1449 2
        if (null === $parts) {
1450 1
            $parts = array_keys($this->_dqlParts);
1451
        }
1452
1453 2
        foreach ($parts as $part) {
1454 2
            $this->resetDQLPart($part);
1455
        }
1456
1457 2
        return $this;
1458
    }
1459
1460
    /**
1461
     * Resets single DQL part.
1462
     *
1463
     * @param string $part
1464
     *
1465
     * @return self
1466
     */
1467 3
    public function resetDQLPart($part)
1468
    {
1469 3
        $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? [] : null;
1470 3
        $this->_state           = self::STATE_DIRTY;
1471
1472 3
        return $this;
1473
    }
1474
1475
    /**
1476
     * Gets a string representation of this QueryBuilder which corresponds to
1477
     * the final DQL query being constructed.
1478
     *
1479
     * @return string The string representation of this QueryBuilder.
1480
     */
1481 5
    public function __toString()
1482
    {
1483 5
        return $this->getDQL();
1484
    }
1485
1486
    /**
1487
     * Deep clones all expression objects in the DQL parts.
1488
     *
1489
     * @return void
1490
     */
1491 3
    public function __clone()
1492
    {
1493 3
        foreach ($this->_dqlParts as $part => $elements) {
1494 3
            if (is_array($this->_dqlParts[$part])) {
1495 3
                foreach ($this->_dqlParts[$part] as $idx => $element) {
1496 2
                    if (is_object($element)) {
1497 3
                        $this->_dqlParts[$part][$idx] = clone $element;
1498
                    }
1499
                }
1500 3
            } else if (is_object($elements)) {
1501 3
                $this->_dqlParts[$part] = clone $elements;
1502
            }
1503
        }
1504
1505 3
        $parameters = [];
1506
1507 3
        foreach ($this->parameters as $parameter) {
1508 1
            $parameters[] = clone $parameter;
1509
        }
1510
1511 3
        $this->parameters = new ArrayCollection($parameters);
1512 3
    }
1513
}
1514