Failed Conditions
Push — master ( 6744b4...2b8acb )
by Marco
60:45 queued 60:36
created

QueryBuilder::setLifetime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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
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 118
    public function __construct(EntityManagerInterface $em)
153
    {
154 118
        $this->_em = $em;
155 118
        $this->parameters = new ArrayCollection();
156 118
    }
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 82
    public function getDQL()
310
    {
311 82
        if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
312 44
            return $this->_dql;
313
        }
314
315 82
        switch ($this->_type) {
316 82
            case self::DELETE:
317 1
                $dql = $this->_getDQLForDelete();
318 1
                break;
319
320 81
            case self::UPDATE:
321 3
                $dql = $this->_getDQLForUpdate();
322 3
                break;
323
324 79
            case self::SELECT:
325
            default:
326 79
                $dql = $this->_getDQLForSelect();
327 79
                break;
328
        }
329
330 82
        $this->_state = self::STATE_CLEAN;
331 82
        $this->_dql   = $dql;
332
333 82
        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 71
    public function getQuery()
350
    {
351 71
        $parameters = clone $this->parameters;
352 71
        $query      = $this->_em->createQuery($this->getDQL())
353 71
            ->setParameters($parameters)
354 71
            ->setFirstResult($this->_firstResult)
355 71
            ->setMaxResults($this->_maxResults);
356
357 71
        if ($this->lifetime) {
358 1
            $query->setLifetime($this->lifetime);
359
        }
360
361 71
        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 zero. 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 71
        if ($this->cacheable) {
366 1
            $query->setCacheable($this->cacheable);
367
        }
368
369 71
        if ($this->cacheRegion) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheRegion of type string|null is loosely compared to true; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
370 1
            $query->setCacheRegion($this->cacheRegion);
371
        }
372
373 71
        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 29
    private function findRootAlias($alias, $parentAlias)
385
    {
386 29
        $rootAlias = null;
0 ignored issues
show
Unused Code introduced by
$rootAlias is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
387
388 29
        if (in_array($parentAlias, $this->getRootAliases())) {
389 29
            $rootAlias = $parentAlias;
390 6
        } 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
            $rootAlias = $this->getRootAlias();
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated with message: Please use $qb->getRootAliases() instead.

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

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

Loading history...
396
        }
397
398 29
        $this->joinRootAliases[$alias] = $rootAlias;
399
400 29
        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 3
    public function getRootAlias()
421
    {
422 3
        $aliases = $this->getRootAliases();
423
424 3
        if ( ! isset($aliases[0])) {
425
            throw new \RuntimeException('No alias was set before invoking getRootAlias().');
426
        }
427
428 3
        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 45 View Code Duplication
    public function getRootAliases()
446
    {
447 45
        $aliases = [];
448
449 45
        foreach ($this->_dqlParts['from'] as &$fromClause) {
450 45
            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 45
            $aliases[] = $fromClause->getAlias();
459
        }
460
461 45
        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 View Code Duplication
    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 10 View Code Duplication
    public function setParameter($key, $value, $type = null)
534
    {
535 10
        $filteredParameters = $this->parameters->filter(
536 10
            function ($parameter) use ($key)
537
            {
538
                /* @var Query\Parameter $parameter */
539
                // Must not be identical because of string to integer conversion
540 3
                return ($key == $parameter->getName());
541 10
            }
542
        );
543
544 10
        if (count($filteredParameters)) {
545
            /* @var Query\Parameter $parameter */
546
            $parameter = $filteredParameters->first();
547
            $parameter->setValue($value, $type);
548
549
            return $this;
550
        }
551
552 10
        $parameter = new Query\Parameter($key, $value, $type);
553
554 10
        $this->parameters->add($parameter);
555
556 10
        return $this;
557
    }
558
559
    /**
560
     * Sets a collection of query parameters for the query being constructed.
561
     *
562
     * <code>
563
     *     $qb = $em->createQueryBuilder()
564
     *         ->select('u')
565
     *         ->from('User', 'u')
566
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
567
     *         ->setParameters(new ArrayCollection(array(
568
     *             new Parameter('user_id1', 1),
569
     *             new Parameter('user_id2', 2)
570
     *        )));
571
     * </code>
572
     *
573
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters to set.
574
     *
575
     * @return self
576
     */
577 4 View Code Duplication
    public function setParameters($parameters)
578
    {
579
        // BC compatibility with 2.3-
580 4
        if (is_array($parameters)) {
581 1
            $parameterCollection = new ArrayCollection();
582
583 1
            foreach ($parameters as $key => $value) {
584 1
                $parameter = new Query\Parameter($key, $value);
585
586 1
                $parameterCollection->add($parameter);
587
            }
588
589 1
            $parameters = $parameterCollection;
590
        }
591
592 4
        $this->parameters = $parameters;
593
594 4
        return $this;
595
    }
596
597
    /**
598
     * Gets all defined query parameters for the query being constructed.
599
     *
600
     * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
601
     */
602 2
    public function getParameters()
603
    {
604 2
        return $this->parameters;
605
    }
606
607
    /**
608
     * Gets a (previously set) query parameter of the query being constructed.
609
     *
610
     * @param mixed $key The key (index or name) of the bound parameter.
611
     *
612
     * @return Query\Parameter|null The value of the bound parameter.
613
     */
614 12 View Code Duplication
    public function getParameter($key)
615
    {
616 12
        $filteredParameters = $this->parameters->filter(
617 12
            function ($parameter) use ($key)
618
            {
619
                /* @var Query\Parameter $parameter */
620
                // Must not be identical because of string to integer conversion
621 12
                return ($key == $parameter->getName());
622 12
            }
623
        );
624
625 12
        return count($filteredParameters) ? $filteredParameters->first() : null;
626
    }
627
628
    /**
629
     * Sets the position of the first result to retrieve (the "offset").
630
     *
631
     * @param integer $firstResult The first result to return.
632
     *
633
     * @return self
634
     */
635 2
    public function setFirstResult($firstResult)
636
    {
637 2
        $this->_firstResult = $firstResult;
638
639 2
        return $this;
640
    }
641
642
    /**
643
     * Gets the position of the first result the query object was set to retrieve (the "offset").
644
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
645
     *
646
     * @return integer The position of the first result.
647
     */
648 2
    public function getFirstResult()
649
    {
650 2
        return $this->_firstResult;
651
    }
652
653
    /**
654
     * Sets the maximum number of results to retrieve (the "limit").
655
     *
656
     * @param integer $maxResults The maximum number of results to retrieve.
657
     *
658
     * @return self
659
     */
660 3
    public function setMaxResults($maxResults)
661
    {
662 3
        $this->_maxResults = $maxResults;
663
664 3
        return $this;
665
    }
666
667
    /**
668
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
669
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
670
     *
671
     * @return integer Maximum number of results.
672
     */
673 2
    public function getMaxResults()
674
    {
675 2
        return $this->_maxResults;
676
    }
677
678
    /**
679
     * Either appends to or replaces a single, generic query part.
680
     *
681
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
682
     * 'groupBy', 'having' and 'orderBy'.
683
     *
684
     * @param string       $dqlPartName The DQL part name.
685
     * @param object|array $dqlPart     An Expr object.
686
     * @param bool         $append      Whether to append (true) or replace (false).
687
     *
688
     * @return self
689
     */
690 114
    public function add($dqlPartName, $dqlPart, $append = false)
691
    {
692 114
        if ($append && ($dqlPartName === "where" || $dqlPartName === "having")) {
693 1
            throw new \InvalidArgumentException(
694
                "Using \$append = true does not have an effect with 'where' or 'having' ".
695 1
                "parts. See QueryBuilder#andWhere() for an example for correct usage."
696
            );
697
        }
698
699 114
        $isMultiple = is_array($this->_dqlParts[$dqlPartName])
700 114
            && !($dqlPartName == 'join' && !$append);
701
702
        // Allow adding any part retrieved from self::getDQLParts().
703 114
        if (is_array($dqlPart) && $dqlPartName != 'join') {
704 1
            $dqlPart = reset($dqlPart);
705
        }
706
707
        // This is introduced for backwards compatibility reasons.
708
        // TODO: Remove for 3.0
709 114
        if ($dqlPartName == 'join') {
710 30
            $newDqlPart = [];
711
712 30
            foreach ($dqlPart as $k => $v) {
713 30
                $k = is_numeric($k) ? $this->getRootAlias() : $k;
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated with message: Please use $qb->getRootAliases() instead.

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

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

Loading history...
714
715 30
                $newDqlPart[$k] = $v;
716
            }
717
718 30
            $dqlPart = $newDqlPart;
719
        }
720
721 114
        if ($append && $isMultiple) {
722 108
            if (is_array($dqlPart)) {
723 30
                $key = key($dqlPart);
724
725 30
                $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
726
            } else {
727 108
                $this->_dqlParts[$dqlPartName][] = $dqlPart;
728
            }
729
        } else {
730 112
            $this->_dqlParts[$dqlPartName] = ($isMultiple) ? [$dqlPart] : $dqlPart;
731
        }
732
733 114
        $this->_state = self::STATE_DIRTY;
734
735 114
        return $this;
736
    }
737
738
    /**
739
     * Specifies an item that is to be returned in the query result.
740
     * Replaces any previously specified selections, if any.
741
     *
742
     * <code>
743
     *     $qb = $em->createQueryBuilder()
744
     *         ->select('u', 'p')
745
     *         ->from('User', 'u')
746
     *         ->leftJoin('u.Phonenumbers', 'p');
747
     * </code>
748
     *
749
     * @param mixed $select The selection expressions.
750
     *
751
     * @return self
752
     */
753 107 View Code Duplication
    public function select($select = null)
754
    {
755 107
        $this->_type = self::SELECT;
756
757 107
        if (empty($select)) {
758 1
            return $this;
759
        }
760
761 106
        $selects = is_array($select) ? $select : func_get_args();
762
763 106
        return $this->add('select', new Expr\Select($selects), false);
764
    }
765
766
    /**
767
     * Adds a DISTINCT flag to this query.
768
     *
769
     * <code>
770
     *     $qb = $em->createQueryBuilder()
771
     *         ->select('u')
772
     *         ->distinct()
773
     *         ->from('User', 'u');
774
     * </code>
775
     *
776
     * @param bool $flag
777
     *
778
     * @return self
779
     */
780 1
    public function distinct($flag = true)
781
    {
782 1
        $this->_dqlParts['distinct'] = (bool) $flag;
783
784 1
        return $this;
785
    }
786
787
    /**
788
     * Adds an item that is to be returned in the query result.
789
     *
790
     * <code>
791
     *     $qb = $em->createQueryBuilder()
792
     *         ->select('u')
793
     *         ->addSelect('p')
794
     *         ->from('User', 'u')
795
     *         ->leftJoin('u.Phonenumbers', 'p');
796
     * </code>
797
     *
798
     * @param mixed $select The selection expression.
799
     *
800
     * @return self
801
     */
802 1 View Code Duplication
    public function addSelect($select = null)
803
    {
804 1
        $this->_type = self::SELECT;
805
806 1
        if (empty($select)) {
807
            return $this;
808
        }
809
810 1
        $selects = is_array($select) ? $select : func_get_args();
811
812 1
        return $this->add('select', new Expr\Select($selects), true);
813
    }
814
815
    /**
816
     * Turns the query being built into a bulk delete query that ranges over
817
     * a certain entity type.
818
     *
819
     * <code>
820
     *     $qb = $em->createQueryBuilder()
821
     *         ->delete('User', 'u')
822
     *         ->where('u.id = :user_id')
823
     *         ->setParameter('user_id', 1);
824
     * </code>
825
     *
826
     * @param string $delete The class/type whose instances are subject to the deletion.
827
     * @param string $alias  The class/type alias used in the constructed query.
828
     *
829
     * @return self
830
     */
831 4 View Code Duplication
    public function delete($delete = null, $alias = null)
832
    {
833 4
        $this->_type = self::DELETE;
834
835 4
        if ( ! $delete) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $delete of type string|null is loosely compared to false; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
836 1
            return $this;
837
        }
838
839 3
        return $this->add('from', new Expr\From($delete, $alias));
840
    }
841
842
    /**
843
     * Turns the query being built into a bulk update query that ranges over
844
     * a certain entity type.
845
     *
846
     * <code>
847
     *     $qb = $em->createQueryBuilder()
848
     *         ->update('User', 'u')
849
     *         ->set('u.password', '?1')
850
     *         ->where('u.id = ?2');
851
     * </code>
852
     *
853
     * @param string $update The class/type whose instances are subject to the update.
854
     * @param string $alias  The class/type alias used in the constructed query.
855
     *
856
     * @return self
857
     */
858 4 View Code Duplication
    public function update($update = null, $alias = null)
859
    {
860 4
        $this->_type = self::UPDATE;
861
862 4
        if ( ! $update) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $update of type string|null is loosely compared to false; this is ambiguous if the string can be empty. 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 string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
863 1
            return $this;
864
        }
865
866 3
        return $this->add('from', new Expr\From($update, $alias));
867
    }
868
869
    /**
870
     * Creates and adds a query root corresponding to the entity identified by the given alias,
871
     * forming a cartesian product with any existing query roots.
872
     *
873
     * <code>
874
     *     $qb = $em->createQueryBuilder()
875
     *         ->select('u')
876
     *         ->from('User', 'u');
877
     * </code>
878
     *
879
     * @param string $from    The class name.
880
     * @param string $alias   The alias of the class.
881
     * @param string $indexBy The index for the from.
882
     *
883
     * @return self
884
     */
885 106
    public function from($from, $alias, $indexBy = null)
886
    {
887 106
        return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
888
    }
889
890
    /**
891
     * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
892
     * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
893
     * setting an index by.
894
     *
895
     * <code>
896
     *     $qb = $userRepository->createQueryBuilder('u')
897
     *         ->indexBy('u', 'u.id');
898
     *
899
     *     // Is equivalent to...
900
     *
901
     *     $qb = $em->createQueryBuilder()
902
     *         ->select('u')
903
     *         ->from('User', 'u', 'u.id');
904
     * </code>
905
     *
906
     * @param string $alias   The root alias of the class.
907
     * @param string $indexBy The index for the from.
908
     *
909
     * @return self
910
     *
911
     * @throws Query\QueryException
912
     */
913 2
    public function indexBy($alias, $indexBy)
914
    {
915 2
        $rootAliases = $this->getRootAliases();
916
917 2
        if (!in_array($alias, $rootAliases)) {
918
            throw new Query\QueryException(
919
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
920
            );
921
        }
922
923 2
        foreach ($this->_dqlParts['from'] as &$fromClause) {
924
            /* @var Expr\From $fromClause */
925 2
            if ($fromClause->getAlias() !== $alias) {
926 1
                continue;
927
            }
928
929 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
930
        }
931
932 2
        return $this;
933
    }
934
935
    /**
936
     * Creates and adds a join over an entity association to the query.
937
     *
938
     * The entities in the joined association will be fetched as part of the query
939
     * result if the alias used for the joined association is placed in the select
940
     * expressions.
941
     *
942
     * <code>
943
     *     $qb = $em->createQueryBuilder()
944
     *         ->select('u')
945
     *         ->from('User', 'u')
946
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
947
     * </code>
948
     *
949
     * @param string      $join          The relationship to join.
950
     * @param string      $alias         The alias of the join.
951
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
952
     * @param string|null $condition     The condition for the join.
953
     * @param string|null $indexBy       The index for the join.
954
     *
955
     * @return self
956
     */
957 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
958
    {
959 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
960
    }
961
962
    /**
963
     * Creates and adds a join over an entity association to the query.
964
     *
965
     * The entities in the joined association will be fetched as part of the query
966
     * result if the alias used for the joined association is placed in the select
967
     * expressions.
968
     *
969
     *     [php]
970
     *     $qb = $em->createQueryBuilder()
971
     *         ->select('u')
972
     *         ->from('User', 'u')
973
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
974
     *
975
     * @param string      $join          The relationship to join.
976
     * @param string      $alias         The alias of the join.
977
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
978
     * @param string|null $condition     The condition for the join.
979
     * @param string|null $indexBy       The index for the join.
980
     *
981
     * @return self
982
     */
983 15 View Code Duplication
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
984
    {
985 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
986
987 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
988
989 15
        $join = new Expr\Join(
990 15
            Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
991
        );
992
993 15
        return $this->add('join', [$rootAlias => $join], true);
994
    }
995
996
    /**
997
     * Creates and adds a left join over an entity association to the query.
998
     *
999
     * The entities in the joined association will be fetched as part of the query
1000
     * result if the alias used for the joined association is placed in the select
1001
     * expressions.
1002
     *
1003
     * <code>
1004
     *     $qb = $em->createQueryBuilder()
1005
     *         ->select('u')
1006
     *         ->from('User', 'u')
1007
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
1008
     * </code>
1009
     *
1010
     * @param string      $join          The relationship to join.
1011
     * @param string      $alias         The alias of the join.
1012
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1013
     * @param string|null $condition     The condition for the join.
1014
     * @param string|null $indexBy       The index for the join.
1015
     *
1016
     * @return self
1017
     */
1018 15 View Code Duplication
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1019
    {
1020 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
1021
1022 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
1023
1024 15
        $join = new Expr\Join(
1025 15
            Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
1026
        );
1027
1028 15
        return $this->add('join', [$rootAlias => $join], true);
1029
    }
1030
1031
    /**
1032
     * Sets a new value for a field in a bulk update query.
1033
     *
1034
     * <code>
1035
     *     $qb = $em->createQueryBuilder()
1036
     *         ->update('User', 'u')
1037
     *         ->set('u.password', '?1')
1038
     *         ->where('u.id = ?2');
1039
     * </code>
1040
     *
1041
     * @param string $key   The key/field to set.
1042
     * @param string $value The value, expression, placeholder, etc.
1043
     *
1044
     * @return self
1045
     */
1046 3
    public function set($key, $value)
1047
    {
1048 3
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1049
    }
1050
1051
    /**
1052
     * Specifies one or more restrictions to the query result.
1053
     * Replaces any previously specified restrictions, if any.
1054
     *
1055
     * <code>
1056
     *     $qb = $em->createQueryBuilder()
1057
     *         ->select('u')
1058
     *         ->from('User', 'u')
1059
     *         ->where('u.id = ?');
1060
     *
1061
     *     // You can optionally programmatically build and/or expressions
1062
     *     $qb = $em->createQueryBuilder();
1063
     *
1064
     *     $or = $qb->expr()->orX();
1065
     *     $or->add($qb->expr()->eq('u.id', 1));
1066
     *     $or->add($qb->expr()->eq('u.id', 2));
1067
     *
1068
     *     $qb->update('User', 'u')
1069
     *         ->set('u.password', '?')
1070
     *         ->where($or);
1071
     * </code>
1072
     *
1073
     * @param mixed $predicates The restriction predicates.
1074
     *
1075
     * @return self
1076
     */
1077 42 View Code Duplication
    public function where($predicates)
1078
    {
1079 42
        if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
1080 39
            $predicates = new Expr\Andx(func_get_args());
1081
        }
1082
1083 42
        return $this->add('where', $predicates);
1084
    }
1085
1086
    /**
1087
     * Adds one or more restrictions to the query results, forming a logical
1088
     * conjunction with any previously specified restrictions.
1089
     *
1090
     * <code>
1091
     *     $qb = $em->createQueryBuilder()
1092
     *         ->select('u')
1093
     *         ->from('User', 'u')
1094
     *         ->where('u.username LIKE ?')
1095
     *         ->andWhere('u.is_active = 1');
1096
     * </code>
1097
     *
1098
     * @param mixed $where The query restrictions.
0 ignored issues
show
Bug introduced by
There is no parameter named $where. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1099
     *
1100
     * @return self
1101
     *
1102
     * @see where()
1103
     */
1104 17 View Code Duplication
    public function andWhere()
1105
    {
1106 17
        $args  = func_get_args();
1107 17
        $where = $this->getDQLPart('where');
1108
1109 17
        if ($where instanceof Expr\Andx) {
1110 6
            $where->addMultiple($args);
1111
        } else {
1112 12
            array_unshift($args, $where);
1113 12
            $where = new Expr\Andx($args);
1114
        }
1115
1116 17
        return $this->add('where', $where);
1117
    }
1118
1119
    /**
1120
     * Adds one or more restrictions to the query results, forming a logical
1121
     * disjunction with any previously specified restrictions.
1122
     *
1123
     * <code>
1124
     *     $qb = $em->createQueryBuilder()
1125
     *         ->select('u')
1126
     *         ->from('User', 'u')
1127
     *         ->where('u.id = 1')
1128
     *         ->orWhere('u.id = 2');
1129
     * </code>
1130
     *
1131
     * @param mixed $where The WHERE statement.
0 ignored issues
show
Bug introduced by
There is no parameter named $where. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1132
     *
1133
     * @return self
1134
     *
1135
     * @see where()
1136
     */
1137 5 View Code Duplication
    public function orWhere()
1138
    {
1139 5
        $args  = func_get_args();
1140 5
        $where = $this->getDQLPart('where');
1141
1142 5
        if ($where instanceof Expr\Orx) {
1143
            $where->addMultiple($args);
1144
        } else {
1145 5
            array_unshift($args, $where);
1146 5
            $where = new Expr\Orx($args);
1147
        }
1148
1149 5
        return $this->add('where', $where);
1150
    }
1151
1152
    /**
1153
     * Specifies a grouping over the results of the query.
1154
     * Replaces any previously specified groupings, if any.
1155
     *
1156
     * <code>
1157
     *     $qb = $em->createQueryBuilder()
1158
     *         ->select('u')
1159
     *         ->from('User', 'u')
1160
     *         ->groupBy('u.id');
1161
     * </code>
1162
     *
1163
     * @param string $groupBy The grouping expression.
1164
     *
1165
     * @return self
1166
     */
1167 7
    public function groupBy($groupBy)
0 ignored issues
show
Unused Code introduced by
The parameter $groupBy is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1168
    {
1169 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1170
    }
1171
1172
    /**
1173
     * Adds a grouping expression to the query.
1174
     *
1175
     * <code>
1176
     *     $qb = $em->createQueryBuilder()
1177
     *         ->select('u')
1178
     *         ->from('User', 'u')
1179
     *         ->groupBy('u.lastLogin')
1180
     *         ->addGroupBy('u.createdAt');
1181
     * </code>
1182
     *
1183
     * @param string $groupBy The grouping expression.
1184
     *
1185
     * @return self
1186
     */
1187 1
    public function addGroupBy($groupBy)
0 ignored issues
show
Unused Code introduced by
The parameter $groupBy is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1188
    {
1189 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1190
    }
1191
1192
    /**
1193
     * Specifies a restriction over the groups of the query.
1194
     * Replaces any previous having restrictions, if any.
1195
     *
1196
     * @param mixed $having The restriction over the groups.
1197
     *
1198
     * @return self
1199
     */
1200 3 View Code Duplication
    public function having($having)
1201
    {
1202 3
        if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1203 3
            $having = new Expr\Andx(func_get_args());
1204
        }
1205
1206 3
        return $this->add('having', $having);
1207
    }
1208
1209
    /**
1210
     * Adds a restriction over the groups of the query, forming a logical
1211
     * conjunction with any existing having restrictions.
1212
     *
1213
     * @param mixed $having The restriction to append.
1214
     *
1215
     * @return self
1216
     */
1217 2 View Code Duplication
    public function andHaving($having)
0 ignored issues
show
Unused Code introduced by
The parameter $having is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1218
    {
1219 2
        $args   = func_get_args();
1220 2
        $having = $this->getDQLPart('having');
1221
1222 2
        if ($having instanceof Expr\Andx) {
1223 2
            $having->addMultiple($args);
1224
        } else {
1225
            array_unshift($args, $having);
1226
            $having = new Expr\Andx($args);
1227
        }
1228
1229 2
        return $this->add('having', $having);
1230
    }
1231
1232
    /**
1233
     * Adds a restriction over the groups of the query, forming a logical
1234
     * disjunction with any existing having restrictions.
1235
     *
1236
     * @param mixed $having The restriction to add.
1237
     *
1238
     * @return self
1239
     */
1240 1 View Code Duplication
    public function orHaving($having)
0 ignored issues
show
Unused Code introduced by
The parameter $having is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1241
    {
1242 1
        $args   = func_get_args();
1243 1
        $having = $this->getDQLPart('having');
1244
1245 1
        if ($having instanceof Expr\Orx) {
1246
            $having->addMultiple($args);
1247
        } else {
1248 1
            array_unshift($args, $having);
1249 1
            $having = new Expr\Orx($args);
1250
        }
1251
1252 1
        return $this->add('having', $having);
1253
    }
1254
1255
    /**
1256
     * Specifies an ordering for the query results.
1257
     * Replaces any previously specified orderings, if any.
1258
     *
1259
     * @param string|Expr\OrderBy $sort  The ordering expression.
1260
     * @param string              $order The ordering direction.
1261
     *
1262
     * @return self
1263
     */
1264 10
    public function orderBy($sort, $order = null)
1265
    {
1266 10
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1267
1268 10
        return $this->add('orderBy', $orderBy);
1269
    }
1270
1271
    /**
1272
     * Adds an ordering to the query results.
1273
     *
1274
     * @param string|Expr\OrderBy $sort  The ordering expression.
1275
     * @param string              $order The ordering direction.
1276
     *
1277
     * @return self
1278
     */
1279 4
    public function addOrderBy($sort, $order = null)
1280
    {
1281 4
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1282
1283 4
        return $this->add('orderBy', $orderBy, true);
1284
    }
1285
1286
    /**
1287
     * Adds criteria to the query.
1288
     *
1289
     * Adds where expressions with AND operator.
1290
     * Adds orderings.
1291
     * Overrides firstResult and maxResults if they're set.
1292
     *
1293
     * @param Criteria $criteria
1294
     *
1295
     * @return self
1296
     *
1297
     * @throws Query\QueryException
1298
     */
1299 13
    public function addCriteria(Criteria $criteria)
1300
    {
1301 13
        $allAliases = $this->getAllAliases();
1302 13
        if ( ! isset($allAliases[0])) {
1303
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1304
        }
1305
1306 13
        $visitor = new QueryExpressionVisitor($this->getAllAliases());
1307
1308 13
        if ($whereExpression = $criteria->getWhereExpression()) {
1309 9
            $this->andWhere($visitor->dispatch($whereExpression));
1310 9
            foreach ($visitor->getParameters() as $parameter) {
1311 9
                $this->parameters->add($parameter);
1312
            }
1313
        }
1314
1315 13
        if ($criteria->getOrderings()) {
1316 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1317
1318 2
                $hasValidAlias = false;
1319 2
                foreach($allAliases as $alias) {
1320 2
                    if(strpos($sort . '.', $alias . '.') === 0) {
1321 1
                        $hasValidAlias = true;
1322 2
                        break;
1323
                    }
1324
                }
1325
1326 2
                if(!$hasValidAlias) {
1327 1
                    $sort = $allAliases[0] . '.' . $sort;
1328
                }
1329
1330 2
                $this->addOrderBy($sort, $order);
1331
            }
1332
        }
1333
1334
        // Overwrite limits only if they was set in criteria
1335 13
        if (($firstResult = $criteria->getFirstResult()) !== null) {
1336 1
            $this->setFirstResult($firstResult);
1337
        }
1338 13
        if (($maxResults = $criteria->getMaxResults()) !== null) {
1339 1
            $this->setMaxResults($maxResults);
1340
        }
1341
1342 13
        return $this;
1343
    }
1344
1345
    /**
1346
     * Gets a query part by its name.
1347
     *
1348
     * @param string $queryPartName
1349
     *
1350
     * @return mixed $queryPart
1351
     *
1352
     * @todo Rename: getQueryPart (or remove?)
1353
     */
1354 97
    public function getDQLPart($queryPartName)
1355
    {
1356 97
        return $this->_dqlParts[$queryPartName];
1357
    }
1358
1359
    /**
1360
     * Gets all query parts.
1361
     *
1362
     * @return array $dqlParts
1363
     *
1364
     * @todo Rename: getQueryParts (or remove?)
1365
     */
1366 1
    public function getDQLParts()
1367
    {
1368 1
        return $this->_dqlParts;
1369
    }
1370
1371
    /**
1372
     * @return string
1373
     */
1374 1
    private function _getDQLForDelete()
1375
    {
1376
         return 'DELETE'
1377 1
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1378 1
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1379 1
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1380
    }
1381
1382
    /**
1383
     * @return string
1384
     */
1385 3
    private function _getDQLForUpdate()
1386
    {
1387
         return 'UPDATE'
1388 3
              . $this->_getReducedDQLQueryPart('from', ['pre' => ' ', 'separator' => ', '])
1389 3
              . $this->_getReducedDQLQueryPart('set', ['pre' => ' SET ', 'separator' => ', '])
1390 3
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1391 3
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1392
    }
1393
1394
    /**
1395
     * @return string
1396
     */
1397 79
    private function _getDQLForSelect()
1398
    {
1399
        $dql = 'SELECT'
1400 79
             . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
1401 79
             . $this->_getReducedDQLQueryPart('select', ['pre' => ' ', 'separator' => ', ']);
1402
1403 79
        $fromParts   = $this->getDQLPart('from');
1404 79
        $joinParts   = $this->getDQLPart('join');
1405 79
        $fromClauses = [];
1406
1407
        // Loop through all FROM clauses
1408 79
        if ( ! empty($fromParts)) {
1409 78
            $dql .= ' FROM ';
1410
1411 78
            foreach ($fromParts as $from) {
1412 78
                $fromClause = (string) $from;
1413
1414 78
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1415 25
                    foreach ($joinParts[$from->getAlias()] as $join) {
1416 25
                        $fromClause .= ' ' . ((string) $join);
1417
                    }
1418
                }
1419
1420 78
                $fromClauses[] = $fromClause;
1421
            }
1422
        }
1423
1424 79
        $dql .= implode(', ', $fromClauses)
1425 79
              . $this->_getReducedDQLQueryPart('where', ['pre' => ' WHERE '])
1426 79
              . $this->_getReducedDQLQueryPart('groupBy', ['pre' => ' GROUP BY ', 'separator' => ', '])
1427 79
              . $this->_getReducedDQLQueryPart('having', ['pre' => ' HAVING '])
1428 79
              . $this->_getReducedDQLQueryPart('orderBy', ['pre' => ' ORDER BY ', 'separator' => ', ']);
1429
1430 79
        return $dql;
1431
    }
1432
1433
    /**
1434
     * @param string $queryPartName
1435
     * @param array  $options
1436
     *
1437
     * @return string
1438
     */
1439 82
    private function _getReducedDQLQueryPart($queryPartName, $options = [])
1440
    {
1441 82
        $queryPart = $this->getDQLPart($queryPartName);
1442
1443 82
        if (empty($queryPart)) {
1444 82
            return (isset($options['empty']) ? $options['empty'] : '');
1445
        }
1446
1447 82
        return (isset($options['pre']) ? $options['pre'] : '')
1448 82
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1449 82
             . (isset($options['post']) ? $options['post'] : '');
1450
    }
1451
1452
    /**
1453
     * Resets DQL parts.
1454
     *
1455
     * @param array|null $parts
1456
     *
1457
     * @return self
1458
     */
1459 2
    public function resetDQLParts($parts = null)
1460
    {
1461 2
        if (null === $parts) {
1462 1
            $parts = array_keys($this->_dqlParts);
1463
        }
1464
1465 2
        foreach ($parts as $part) {
1466 2
            $this->resetDQLPart($part);
1467
        }
1468
1469 2
        return $this;
1470
    }
1471
1472
    /**
1473
     * Resets single DQL part.
1474
     *
1475
     * @param string $part
1476
     *
1477
     * @return self
1478
     */
1479 3
    public function resetDQLPart($part)
1480
    {
1481 3
        $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? [] : null;
1482 3
        $this->_state           = self::STATE_DIRTY;
1483
1484 3
        return $this;
1485
    }
1486
1487
    /**
1488
     * Gets a string representation of this QueryBuilder which corresponds to
1489
     * the final DQL query being constructed.
1490
     *
1491
     * @return string The string representation of this QueryBuilder.
1492
     */
1493 5
    public function __toString()
1494
    {
1495 5
        return $this->getDQL();
1496
    }
1497
1498
    /**
1499
     * Deep clones all expression objects in the DQL parts.
1500
     *
1501
     * @return void
1502
     */
1503 3
    public function __clone()
1504
    {
1505 3
        foreach ($this->_dqlParts as $part => $elements) {
1506 3
            if (is_array($this->_dqlParts[$part])) {
1507 3
                foreach ($this->_dqlParts[$part] as $idx => $element) {
1508 2
                    if (is_object($element)) {
1509 3
                        $this->_dqlParts[$part][$idx] = clone $element;
1510
                    }
1511
                }
1512 3
            } else if (is_object($elements)) {
1513 3
                $this->_dqlParts[$part] = clone $elements;
1514
            }
1515
        }
1516
1517 3
        $parameters = [];
1518
1519 3
        foreach ($this->parameters as $parameter) {
1520 1
            $parameters[] = clone $parameter;
1521
        }
1522
1523 3
        $this->parameters = new ArrayCollection($parameters);
1524 3
    }
1525
}
1526