Completed
Pull Request — master (#5883)
by Sebastian
19:21
created

QueryBuilder::getRootAlias()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 4
cts 5
cp 0.8
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 2.032
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 = array(
61
        'distinct' => false,
62
        'select'  => array(),
63
        'from'    => array(),
64
        'join'    => array(),
65
        'set'     => array(),
66
        'where'   => null,
67
        'groupBy' => array(),
68
        'having'  => null,
69
        'orderBy' => array()
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 = array();
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 113
    public function __construct(EntityManagerInterface $em)
153
    {
154 113
        $this->_em = $em;
155 113
        $this->parameters = new ArrayCollection();
156 113
    }
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 77
    public function getDQL()
310
    {
311 77
        if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
312 44
            return $this->_dql;
313
        }
314
315 77
        switch ($this->_type) {
316 77
            case self::DELETE:
317 1
                $dql = $this->_getDQLForDelete();
318 1
                break;
319
320 76
            case self::UPDATE:
321 2
                $dql = $this->_getDQLForUpdate();
322 2
                break;
323
324 74
            case self::SELECT:
325
            default:
326 74
                $dql = $this->_getDQLForSelect();
327 74
                break;
328
        }
329
330 77
        $this->_state = self::STATE_CLEAN;
331 77
        $this->_dql   = $dql;
332
333 77
        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 66
    public function getQuery()
350
    {
351 66
        $parameters = clone $this->parameters;
352 66
        $query      = $this->_em->createQuery($this->getDQL())
353 66
            ->setParameters($parameters)
354 66
            ->setFirstResult($this->_firstResult)
355 66
            ->setMaxResults($this->_maxResults);
356
357 66
        if ($this->lifetime) {
358 1
            $query->setLifetime($this->lifetime);
359
        }
360
361 66
        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 66
        if ($this->cacheable) {
366 1
            $query->setCacheable($this->cacheable);
367
        }
368
369 66
        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 66
        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 27
    private function findRootAlias($alias, $parentAlias)
385
    {
386 27
        $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 27
        if (in_array($parentAlias, $this->getRootAliases())) {
389 27
            $rootAlias = $parentAlias;
390 5
        } elseif (isset($this->joinRootAliases[$parentAlias])) {
391 5
            $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 27
        $this->joinRootAliases[$alias] = $rootAlias;
399
400 27
        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 43
    public function getRootAliases()
446
    {
447 43
        $aliases = array();
448
449 43
        foreach ($this->_dqlParts['from'] as &$fromClause) {
450 43
            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 43
            $aliases[] = $fromClause->getAlias();
459
        }
460
461 43
        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 15
        return array_merge($this->getRootAliases(),array_keys($this->joinRootAliases));
480
    }
481
482
    /**
483
     * Gets the root entities of the query. This is the entity aliases involved
484
     * in the construction of the query.
485
     *
486
     * <code>
487
     *     $qb = $em->createQueryBuilder()
488
     *         ->select('u')
489
     *         ->from('User', 'u');
490
     *
491
     *     $qb->getRootEntities(); // array('User')
492
     * </code>
493
     *
494
     * @return array
495
     */
496 1
    public function getRootEntities()
497
    {
498 1
        $entities = array();
499
500 1
        foreach ($this->_dqlParts['from'] as &$fromClause) {
501 1
            if (is_string($fromClause)) {
502
                $spacePos = strrpos($fromClause, ' ');
503
                $from     = substr($fromClause, 0, $spacePos);
504
                $alias    = substr($fromClause, $spacePos + 1);
505
506
                $fromClause = new Query\Expr\From($from, $alias);
507
            }
508
509 1
            $entities[] = $fromClause->getFrom();
510
        }
511
512 1
        return $entities;
513
    }
514
515
    /**
516
     * Sets a query parameter for the query being constructed.
517
     *
518
     * <code>
519
     *     $qb = $em->createQueryBuilder()
520
     *         ->select('u')
521
     *         ->from('User', 'u')
522
     *         ->where('u.id = :user_id')
523
     *         ->setParameter('user_id', 1);
524
     * </code>
525
     *
526
     * @param string|integer $key   The parameter position or name.
527
     * @param mixed          $value The parameter value.
528
     * @param string|null    $type  PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
529
     *
530
     * @return self
531
     */
532 8
    public function setParameter($key, $value, $type = null)
533
    {
534 8
        $filteredParameters = $this->parameters->filter(
535
            function ($parameter) use ($key)
536
            {
537
                /* @var Query\Parameter $parameter */
538
                // Must not be identical because of string to integer conversion
539 2
                return ($key == $parameter->getName());
540 8
            }
541
        );
542
543 8
        if (count($filteredParameters)) {
544
            /* @var Query\Parameter $parameter */
545
            $parameter = $filteredParameters->first();
546
            $parameter->setValue($value, $type);
547
548
            return $this;
549
        }
550
551 8
        $parameter = new Query\Parameter($key, $value, $type);
552
553 8
        $this->parameters->add($parameter);
554
555 8
        return $this;
556
    }
557
558
    /**
559
     * Sets a collection of query parameters for the query being constructed.
560
     *
561
     * <code>
562
     *     $qb = $em->createQueryBuilder()
563
     *         ->select('u')
564
     *         ->from('User', 'u')
565
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
566
     *         ->setParameters(new ArrayCollection(array(
567
     *             new Parameter('user_id1', 1),
568
     *             new Parameter('user_id2', 2)
569
     *        )));
570
     * </code>
571
     *
572
     * @param \Doctrine\Common\Collections\ArrayCollection|array $parameters The query parameters to set.
573
     *
574
     * @return self
575
     */
576 4
    public function setParameters($parameters)
577
    {
578
        // BC compatibility with 2.3-
579 4
        if (is_array($parameters)) {
580 1
            $parameterCollection = new ArrayCollection();
581
582 1
            foreach ($parameters as $key => $value) {
583 1
                $parameter = new Query\Parameter($key, $value);
584
585 1
                $parameterCollection->add($parameter);
586
            }
587
588 1
            $parameters = $parameterCollection;
589
        }
590
591 4
        $this->parameters = $parameters;
592
593 4
        return $this;
594
    }
595
596
    /**
597
     * Gets all defined query parameters for the query being constructed.
598
     *
599
     * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
600
     */
601 2
    public function getParameters()
602
    {
603 2
        return $this->parameters;
604
    }
605
606
    /**
607
     * Gets a (previously set) query parameter of the query being constructed.
608
     *
609
     * @param mixed $key The key (index or name) of the bound parameter.
610
     *
611
     * @return Query\Parameter|null The value of the bound parameter.
612
     */
613 12
    public function getParameter($key)
614
    {
615 12
        $filteredParameters = $this->parameters->filter(
616 12
            function ($parameter) use ($key)
617
            {
618
                /* @var Query\Parameter $parameter */
619
                // Must not be identical because of string to integer conversion
620 12
                return ($key == $parameter->getName());
621 12
            }
622
        );
623
624 12
        return count($filteredParameters) ? $filteredParameters->first() : null;
625
    }
626
627
    /**
628
     * Sets the position of the first result to retrieve (the "offset").
629
     *
630
     * @param integer $firstResult The first result to return.
631
     *
632
     * @return self
633
     */
634 2
    public function setFirstResult($firstResult)
635
    {
636 2
        $this->_firstResult = $firstResult;
637
638 2
        return $this;
639
    }
640
641
    /**
642
     * Gets the position of the first result the query object was set to retrieve (the "offset").
643
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
644
     *
645
     * @return integer The position of the first result.
646
     */
647 2
    public function getFirstResult()
648
    {
649 2
        return $this->_firstResult;
650
    }
651
652
    /**
653
     * Sets the maximum number of results to retrieve (the "limit").
654
     *
655
     * @param integer $maxResults The maximum number of results to retrieve.
656
     *
657
     * @return self
658
     */
659 2
    public function setMaxResults($maxResults)
660
    {
661 2
        $this->_maxResults = $maxResults;
662
663 2
        return $this;
664
    }
665
666
    /**
667
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
668
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
669
     *
670
     * @return integer Maximum number of results.
671
     */
672 2
    public function getMaxResults()
673
    {
674 2
        return $this->_maxResults;
675
    }
676
677
    /**
678
     * Either appends to or replaces a single, generic query part.
679
     *
680
     * The available parts are: 'select', 'from', 'join', 'set', 'where',
681
     * 'groupBy', 'having' and 'orderBy'.
682
     *
683
     * @param string       $dqlPartName The DQL part name.
684
     * @param object|array $dqlPart     An Expr object.
685
     * @param bool         $append      Whether to append (true) or replace (false).
686
     *
687
     * @return self
688
     */
689 109
    public function add($dqlPartName, $dqlPart, $append = false)
690
    {
691 109
        if ($append && ($dqlPartName === "where" || $dqlPartName === "having")) {
692 1
            throw new \InvalidArgumentException(
693
                "Using \$append = true does not have an effect with 'where' or 'having' ".
694 1
                "parts. See QueryBuilder#andWhere() for an example for correct usage."
695
            );
696
        }
697
698 109
        $isMultiple = is_array($this->_dqlParts[$dqlPartName])
699 109
            && !($dqlPartName == 'join' && !$append);
700
701
        // Allow adding any part retrieved from self::getDQLParts().
702 109
        if (is_array($dqlPart) && $dqlPartName != 'join') {
703 1
            $dqlPart = reset($dqlPart);
704
        }
705
706
        // This is introduced for backwards compatibility reasons.
707
        // TODO: Remove for 3.0
708 109
        if ($dqlPartName == 'join') {
709 28
            $newDqlPart = array();
710
711 28
            foreach ($dqlPart as $k => $v) {
712 28
                $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...
713
714 28
                $newDqlPart[$k] = $v;
715
            }
716
717 28
            $dqlPart = $newDqlPart;
718
        }
719
720 109
        if ($append && $isMultiple) {
721 103
            if (is_array($dqlPart)) {
722 28
                $key = key($dqlPart);
723
724 28
                $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
725
            } else {
726 103
                $this->_dqlParts[$dqlPartName][] = $dqlPart;
727
            }
728
        } else {
729 107
            $this->_dqlParts[$dqlPartName] = ($isMultiple) ? array($dqlPart) : $dqlPart;
730
        }
731
732 109
        $this->_state = self::STATE_DIRTY;
733
734 109
        return $this;
735
    }
736
737
    /**
738
     * Specifies an item that is to be returned in the query result.
739
     * Replaces any previously specified selections, if any.
740
     *
741
     * <code>
742
     *     $qb = $em->createQueryBuilder()
743
     *         ->select('u', 'p')
744
     *         ->from('User', 'u')
745
     *         ->leftJoin('u.Phonenumbers', 'p');
746
     * </code>
747
     *
748
     * @param mixed $select The selection expressions.
749
     *
750
     * @return self
751
     */
752 102
    public function select($select = null)
753
    {
754 102
        $this->_type = self::SELECT;
755
756 102
        if (empty($select)) {
757 1
            return $this;
758
        }
759
760 101
        $selects = is_array($select) ? $select : func_get_args();
761
762 101
        return $this->add('select', new Expr\Select($selects), false);
763
    }
764
765
    /**
766
     * Adds a DISTINCT flag to this query.
767
     *
768
     * <code>
769
     *     $qb = $em->createQueryBuilder()
770
     *         ->select('u')
771
     *         ->distinct()
772
     *         ->from('User', 'u');
773
     * </code>
774
     *
775
     * @param bool $flag
776
     *
777
     * @return self
778
     */
779 1
    public function distinct($flag = true)
780
    {
781 1
        $this->_dqlParts['distinct'] = (bool) $flag;
782
783 1
        return $this;
784
    }
785
786
    /**
787
     * Adds an item that is to be returned in the query result.
788
     *
789
     * <code>
790
     *     $qb = $em->createQueryBuilder()
791
     *         ->select('u')
792
     *         ->addSelect('p')
793
     *         ->from('User', 'u')
794
     *         ->leftJoin('u.Phonenumbers', 'p');
795
     * </code>
796
     *
797
     * @param mixed $select The selection expression.
798
     *
799
     * @return self
800
     */
801 1
    public function addSelect($select = null)
802
    {
803 1
        $this->_type = self::SELECT;
804
805 1
        if (empty($select)) {
806
            return $this;
807
        }
808
809 1
        $selects = is_array($select) ? $select : func_get_args();
810
811 1
        return $this->add('select', new Expr\Select($selects), true);
812
    }
813
814
    /**
815
     * Turns the query being built into a bulk delete query that ranges over
816
     * a certain entity type.
817
     *
818
     * <code>
819
     *     $qb = $em->createQueryBuilder()
820
     *         ->delete('User', 'u')
821
     *         ->where('u.id = :user_id')
822
     *         ->setParameter('user_id', 1);
823
     * </code>
824
     *
825
     * @param string $delete The class/type whose instances are subject to the deletion.
826
     * @param string $alias  The class/type alias used in the constructed query.
827
     *
828
     * @return self
829
     */
830 4
    public function delete($delete = null, $alias = null)
831
    {
832 4
        $this->_type = self::DELETE;
833
834 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...
835 1
            return $this;
836
        }
837
838 3
        return $this->add('from', new Expr\From($delete, $alias));
839
    }
840
841
    /**
842
     * Turns the query being built into a bulk update query that ranges over
843
     * a certain entity type.
844
     *
845
     * <code>
846
     *     $qb = $em->createQueryBuilder()
847
     *         ->update('User', 'u')
848
     *         ->set('u.password', '?1')
849
     *         ->where('u.id = ?2');
850
     * </code>
851
     *
852
     * @param string $update The class/type whose instances are subject to the update.
853
     * @param string $alias  The class/type alias used in the constructed query.
854
     *
855
     * @return self
856
     */
857 3
    public function update($update = null, $alias = null)
858
    {
859 3
        $this->_type = self::UPDATE;
860
861 3
        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...
862 1
            return $this;
863
        }
864
865 2
        return $this->add('from', new Expr\From($update, $alias));
866
    }
867
868
    /**
869
     * Creates and adds a query root corresponding to the entity identified by the given alias,
870
     * forming a cartesian product with any existing query roots.
871
     *
872
     * <code>
873
     *     $qb = $em->createQueryBuilder()
874
     *         ->select('u')
875
     *         ->from('User', 'u');
876
     * </code>
877
     *
878
     * @param string $from    The class name.
879
     * @param string $alias   The alias of the class.
880
     * @param string $indexBy The index for the from.
881
     *
882
     * @return self
883
     */
884 101
    public function from($from, $alias, $indexBy = null)
885
    {
886 101
        return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
887
    }
888
889
    /**
890
     * Updates a query root corresponding to an entity setting its index by. This method is intended to be used with
891
     * EntityRepository->createQueryBuilder(), which creates the initial FROM clause and do not allow you to update it
892
     * setting an index by.
893
     *
894
     * <code>
895
     *     $qb = $userRepository->createQueryBuilder('u')
896
     *         ->indexBy('u', 'u.id');
897
     *
898
     *     // Is equivalent to...
899
     *
900
     *     $qb = $em->createQueryBuilder()
901
     *         ->select('u')
902
     *         ->from('User', 'u', 'u.id');
903
     * </code>
904
     *
905
     * @param string $alias   The root alias of the class.
906
     * @param string $indexBy The index for the from.
907
     *
908
     * @return self
909
     *
910
     * @throws Query\QueryException
911
     */
912 2
    public function indexBy($alias, $indexBy)
913
    {
914 2
        $rootAliases = $this->getRootAliases();
915
916 2
        if (!in_array($alias, $rootAliases)) {
917
            throw new Query\QueryException(
918
                sprintf('Specified root alias %s must be set before invoking indexBy().', $alias)
919
            );
920
        }
921
922 2
        foreach ($this->_dqlParts['from'] as &$fromClause) {
923
            /* @var Expr\From $fromClause */
924 2
            if ($fromClause->getAlias() !== $alias) {
925 1
                continue;
926
            }
927
928 2
            $fromClause = new Expr\From($fromClause->getFrom(), $fromClause->getAlias(), $indexBy);
929
        }
930
931 2
        return $this;
932
    }
933
934
    /**
935
     * Creates and adds a join over an entity association to the query.
936
     *
937
     * The entities in the joined association will be fetched as part of the query
938
     * result if the alias used for the joined association is placed in the select
939
     * expressions.
940
     *
941
     * <code>
942
     *     $qb = $em->createQueryBuilder()
943
     *         ->select('u')
944
     *         ->from('User', 'u')
945
     *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
946
     * </code>
947
     *
948
     * @param string      $join          The relationship to join.
949
     * @param string      $alias         The alias of the join.
950
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
951
     * @param string|null $condition     The condition for the join.
952
     * @param string|null $indexBy       The index for the join.
953
     *
954
     * @return self
955
     */
956 8
    public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
957
    {
958 8
        return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
959
    }
960
961
    /**
962
     * Creates and adds a join over an entity association to the query.
963
     *
964
     * The entities in the joined association will be fetched as part of the query
965
     * result if the alias used for the joined association is placed in the select
966
     * expressions.
967
     *
968
     *     [php]
969
     *     $qb = $em->createQueryBuilder()
970
     *         ->select('u')
971
     *         ->from('User', 'u')
972
     *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
973
     *
974
     * @param string      $join          The relationship to join.
975
     * @param string      $alias         The alias of the join.
976
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
977
     * @param string|null $condition     The condition for the join.
978
     * @param string|null $indexBy       The index for the join.
979
     *
980
     * @return self
981
     */
982 15
    public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
983
    {
984 15
        $parentAlias = substr($join, 0, strpos($join, '.'));
985
986 15
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
987
988 15
        $join = new Expr\Join(
989 15
            Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
990
        );
991
992 15
        return $this->add('join', array($rootAlias => $join), true);
993
    }
994
995
    /**
996
     * Creates and adds a left join over an entity association to the query.
997
     *
998
     * The entities in the joined association will be fetched as part of the query
999
     * result if the alias used for the joined association is placed in the select
1000
     * expressions.
1001
     *
1002
     * <code>
1003
     *     $qb = $em->createQueryBuilder()
1004
     *         ->select('u')
1005
     *         ->from('User', 'u')
1006
     *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
1007
     * </code>
1008
     *
1009
     * @param string      $join          The relationship to join.
1010
     * @param string      $alias         The alias of the join.
1011
     * @param string|null $conditionType The condition type constant. Either ON or WITH.
1012
     * @param string|null $condition     The condition for the join.
1013
     * @param string|null $indexBy       The index for the join.
1014
     *
1015
     * @return self
1016
     */
1017 13
    public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
1018
    {
1019 13
        $parentAlias = substr($join, 0, strpos($join, '.'));
1020
1021 13
        $rootAlias = $this->findRootAlias($alias, $parentAlias);
1022
1023 13
        $join = new Expr\Join(
1024 13
            Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
1025
        );
1026
1027 13
        return $this->add('join', array($rootAlias => $join), true);
1028
    }
1029
1030
    /**
1031
     * Sets a new value for a field in a bulk update query.
1032
     *
1033
     * <code>
1034
     *     $qb = $em->createQueryBuilder()
1035
     *         ->update('User', 'u')
1036
     *         ->set('u.password', '?1')
1037
     *         ->where('u.id = ?2');
1038
     * </code>
1039
     *
1040
     * @param string $key   The key/field to set.
1041
     * @param string $value The value, expression, placeholder, etc.
1042
     *
1043
     * @return self
1044
     */
1045 2
    public function set($key, $value)
1046
    {
1047 2
        return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
1048
    }
1049
1050
    /**
1051
     * Specifies one or more restrictions to the query result.
1052
     * Replaces any previously specified restrictions, if any.
1053
     *
1054
     * <code>
1055
     *     $qb = $em->createQueryBuilder()
1056
     *         ->select('u')
1057
     *         ->from('User', 'u')
1058
     *         ->where('u.id = ?');
1059
     *
1060
     *     // You can optionally programmatically build and/or expressions
1061
     *     $qb = $em->createQueryBuilder();
1062
     *
1063
     *     $or = $qb->expr()->orX();
1064
     *     $or->add($qb->expr()->eq('u.id', 1));
1065
     *     $or->add($qb->expr()->eq('u.id', 2));
1066
     *
1067
     *     $qb->update('User', 'u')
1068
     *         ->set('u.password', '?')
1069
     *         ->where($or);
1070
     * </code>
1071
     *
1072
     * @param mixed $predicates The restriction predicates.
1073
     *
1074
     * @return self
1075
     */
1076 37
    public function where($predicates)
1077
    {
1078 37
        if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
1079 34
            $predicates = new Expr\Andx(func_get_args());
1080
        }
1081
1082 37
        return $this->add('where', $predicates);
1083
    }
1084
1085
    /**
1086
     * Adds one or more restrictions to the query results, forming a logical
1087
     * conjunction with any previously specified restrictions.
1088
     *
1089
     * <code>
1090
     *     $qb = $em->createQueryBuilder()
1091
     *         ->select('u')
1092
     *         ->from('User', 'u')
1093
     *         ->where('u.username LIKE ?')
1094
     *         ->andWhere('u.is_active = 1');
1095
     * </code>
1096
     *
1097
     * @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...
1098
     *
1099
     * @return self
1100
     *
1101
     * @see where()
1102
     */
1103 17
    public function andWhere()
1104
    {
1105 17
        $args  = func_get_args();
1106 17
        $where = $this->getDQLPart('where');
1107
1108 17
        if ($where instanceof Expr\Andx) {
1109 6
            $where->addMultiple($args);
1110
        } else {
1111 12
            array_unshift($args, $where);
1112 12
            $where = new Expr\Andx($args);
1113
        }
1114
1115 17
        return $this->add('where', $where);
1116
    }
1117
1118
    /**
1119
     * Adds one or more restrictions to the query results, forming a logical
1120
     * disjunction with any previously specified restrictions.
1121
     *
1122
     * <code>
1123
     *     $qb = $em->createQueryBuilder()
1124
     *         ->select('u')
1125
     *         ->from('User', 'u')
1126
     *         ->where('u.id = 1')
1127
     *         ->orWhere('u.id = 2');
1128
     * </code>
1129
     *
1130
     * @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...
1131
     *
1132
     * @return self
1133
     *
1134
     * @see where()
1135
     */
1136 5
    public function orWhere()
1137
    {
1138 5
        $args  = func_get_args();
1139 5
        $where = $this->getDQLPart('where');
1140
1141 5
        if ($where instanceof Expr\Orx) {
1142
            $where->addMultiple($args);
1143
        } else {
1144 5
            array_unshift($args, $where);
1145 5
            $where = new Expr\Orx($args);
1146
        }
1147
1148 5
        return $this->add('where', $where);
1149
    }
1150
1151
    /**
1152
     * Specifies a grouping over the results of the query.
1153
     * Replaces any previously specified groupings, if any.
1154
     *
1155
     * <code>
1156
     *     $qb = $em->createQueryBuilder()
1157
     *         ->select('u')
1158
     *         ->from('User', 'u')
1159
     *         ->groupBy('u.id');
1160
     * </code>
1161
     *
1162
     * @param string $groupBy The grouping expression.
1163
     *
1164
     * @return self
1165
     */
1166 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...
1167
    {
1168 7
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
1169
    }
1170
1171
    /**
1172
     * Adds a grouping expression to the query.
1173
     *
1174
     * <code>
1175
     *     $qb = $em->createQueryBuilder()
1176
     *         ->select('u')
1177
     *         ->from('User', 'u')
1178
     *         ->groupBy('u.lastLogin')
1179
     *         ->addGroupBy('u.createdAt');
1180
     * </code>
1181
     *
1182
     * @param string $groupBy The grouping expression.
1183
     *
1184
     * @return self
1185
     */
1186 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...
1187
    {
1188 1
        return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
1189
    }
1190
1191
    /**
1192
     * Specifies a restriction over the groups of the query.
1193
     * Replaces any previous having restrictions, if any.
1194
     *
1195
     * @param mixed $having The restriction over the groups.
1196
     *
1197
     * @return self
1198
     */
1199 3
    public function having($having)
1200
    {
1201 3
        if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
1202 3
            $having = new Expr\Andx(func_get_args());
1203
        }
1204
1205 3
        return $this->add('having', $having);
1206
    }
1207
1208
    /**
1209
     * Adds a restriction over the groups of the query, forming a logical
1210
     * conjunction with any existing having restrictions.
1211
     *
1212
     * @param mixed $having The restriction to append.
1213
     *
1214
     * @return self
1215
     */
1216 2
    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...
1217
    {
1218 2
        $args   = func_get_args();
1219 2
        $having = $this->getDQLPart('having');
1220
1221 2
        if ($having instanceof Expr\Andx) {
1222 2
            $having->addMultiple($args);
1223
        } else {
1224
            array_unshift($args, $having);
1225
            $having = new Expr\Andx($args);
1226
        }
1227
1228 2
        return $this->add('having', $having);
1229
    }
1230
1231
    /**
1232
     * Adds a restriction over the groups of the query, forming a logical
1233
     * disjunction with any existing having restrictions.
1234
     *
1235
     * @param mixed $having The restriction to add.
1236
     *
1237
     * @return self
1238
     */
1239 1
    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...
1240
    {
1241 1
        $args   = func_get_args();
1242 1
        $having = $this->getDQLPart('having');
1243
1244 1
        if ($having instanceof Expr\Orx) {
1245
            $having->addMultiple($args);
1246
        } else {
1247 1
            array_unshift($args, $having);
1248 1
            $having = new Expr\Orx($args);
1249
        }
1250
1251 1
        return $this->add('having', $having);
1252
    }
1253
1254
    /**
1255
     * Specifies an ordering for the query results.
1256
     * Replaces any previously specified orderings, if any.
1257
     *
1258
     * @param string|Expr\OrderBy $sort  The ordering expression.
1259
     * @param string              $order The ordering direction.
1260
     *
1261
     * @return self
1262
     */
1263 10
    public function orderBy($sort, $order = null)
1264
    {
1265 10
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1266
1267 10
        return $this->add('orderBy', $orderBy);
1268
    }
1269
1270
    /**
1271
     * Adds an ordering to the query results.
1272
     *
1273
     * @param string|Expr\OrderBy $sort  The ordering expression.
1274
     * @param string              $order The ordering direction.
1275
     *
1276
     * @return self
1277
     */
1278 4
    public function addOrderBy($sort, $order = null)
1279
    {
1280 4
        $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1281
1282 4
        return $this->add('orderBy', $orderBy, true);
1283
    }
1284
1285
    /**
1286
     * Adds criteria to the query.
1287
     *
1288
     * Adds where expressions with AND operator.
1289
     * Adds orderings.
1290
     * Overrides firstResult and maxResults if they're set.
1291
     *
1292
     * @param Criteria $criteria
1293
     *
1294
     * @return self
1295
     *
1296
     * @throws Query\QueryException
1297
     */
1298 13
    public function addCriteria(Criteria $criteria)
1299
    {
1300 13
        $allAliases = $this->getAllAliases();
1301 13
        if ( ! isset($allAliases[0])) {
1302
            throw new Query\QueryException('No aliases are set before invoking addCriteria().');
1303
        }
1304
1305 13
        $visitor = new QueryExpressionVisitor($this->getAllAliases());
1306
1307 13
        if ($whereExpression = $criteria->getWhereExpression()) {
1308 9
            $this->andWhere($visitor->dispatch($whereExpression));
1309 9
            foreach ($visitor->getParameters() as $parameter) {
1310 9
                $this->parameters->add($parameter);
1311
            }
1312
        }
1313
1314 13
        if ($criteria->getOrderings()) {
1315 2
            foreach ($criteria->getOrderings() as $sort => $order) {
1316
1317 2
                $hasValidAlias = false;
1318 2
                foreach($allAliases as $alias) {
1319 2
                    if(strpos($sort . '.', $alias . '.') === 0) {
1320 1
                        $hasValidAlias = true;
1321 2
                        break;
1322
                    }
1323
                }
1324
1325 2
                if(!$hasValidAlias) {
1326 1
                    $sort = $allAliases[0] . '.' . $sort;
1327
                }
1328
1329 2
                $this->addOrderBy($sort, $order);
1330
            }
1331
        }
1332
1333
        // Overwrite limits only if they was set in criteria
1334 13
        if (($firstResult = $criteria->getFirstResult()) !== null) {
1335 1
            $this->setFirstResult($firstResult);
1336
        }
1337 13
        if (($maxResults = $criteria->getMaxResults()) !== null) {
1338 1
            $this->setMaxResults($maxResults);
1339
        }
1340
1341 13
        return $this;
1342
    }
1343
1344
    /**
1345
     * Gets a query part by its name.
1346
     *
1347
     * @param string $queryPartName
1348
     *
1349
     * @return mixed $queryPart
1350
     *
1351
     * @todo Rename: getQueryPart (or remove?)
1352
     */
1353 92
    public function getDQLPart($queryPartName)
1354
    {
1355 92
        return $this->_dqlParts[$queryPartName];
1356
    }
1357
1358
    /**
1359
     * Gets all query parts.
1360
     *
1361
     * @return array $dqlParts
1362
     *
1363
     * @todo Rename: getQueryParts (or remove?)
1364
     */
1365 1
    public function getDQLParts()
1366
    {
1367 1
        return $this->_dqlParts;
1368
    }
1369
1370
    /**
1371
     * @return string
1372
     */
1373 1
    private function _getDQLForDelete()
1374
    {
1375
         return 'DELETE'
1376 1
              . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', '))
1377 1
              . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
1378 1
              . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
1379
    }
1380
1381
    /**
1382
     * @return string
1383
     */
1384 2
    private function _getDQLForUpdate()
1385
    {
1386
         return 'UPDATE'
1387 2
              . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', '))
1388 2
              . $this->_getReducedDQLQueryPart('set', array('pre' => ' SET ', 'separator' => ', '))
1389 2
              . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
1390 2
              . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
1391
    }
1392
1393
    /**
1394
     * @return string
1395
     */
1396 74
    private function _getDQLForSelect()
1397
    {
1398
        $dql = 'SELECT'
1399 74
             . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
1400 74
             . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '));
1401
1402 74
        $fromParts   = $this->getDQLPart('from');
1403 74
        $joinParts   = $this->getDQLPart('join');
1404 74
        $fromClauses = array();
1405
1406
        // Loop through all FROM clauses
1407 74
        if ( ! empty($fromParts)) {
1408 73
            $dql .= ' FROM ';
1409
1410 73
            foreach ($fromParts as $from) {
1411 73
                $fromClause = (string) $from;
1412
1413 73
                if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1414 23
                    foreach ($joinParts[$from->getAlias()] as $join) {
1415 23
                        $fromClause .= ' ' . ((string) $join);
1416
                    }
1417
                }
1418
1419 73
                $fromClauses[] = $fromClause;
1420
            }
1421
        }
1422
1423 74
        $dql .= implode(', ', $fromClauses)
1424 74
              . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
1425 74
              . $this->_getReducedDQLQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', '))
1426 74
              . $this->_getReducedDQLQueryPart('having', array('pre' => ' HAVING '))
1427 74
              . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
1428
1429 74
        return $dql;
1430
    }
1431
1432
    /**
1433
     * @param string $queryPartName
1434
     * @param array  $options
1435
     *
1436
     * @return string
1437
     */
1438 77
    private function _getReducedDQLQueryPart($queryPartName, $options = array())
1439
    {
1440 77
        $queryPart = $this->getDQLPart($queryPartName);
1441
1442 77
        if (empty($queryPart)) {
1443 77
            return (isset($options['empty']) ? $options['empty'] : '');
1444
        }
1445
1446 77
        return (isset($options['pre']) ? $options['pre'] : '')
1447 77
             . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1448 77
             . (isset($options['post']) ? $options['post'] : '');
1449
    }
1450
1451
    /**
1452
     * Resets DQL parts.
1453
     *
1454
     * @param array|null $parts
1455
     *
1456
     * @return self
1457
     */
1458 2
    public function resetDQLParts($parts = null)
1459
    {
1460 2
        if (is_null($parts)) {
1461 1
            $parts = array_keys($this->_dqlParts);
1462
        }
1463
1464 2
        foreach ($parts as $part) {
1465 2
            $this->resetDQLPart($part);
1466
        }
1467
1468 2
        return $this;
1469
    }
1470
1471
    /**
1472
     * Resets single DQL part.
1473
     *
1474
     * @param string $part
1475
     *
1476
     * @return self
1477
     */
1478 3
    public function resetDQLPart($part)
1479
    {
1480 3
        $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? array() : null;
1481 3
        $this->_state           = self::STATE_DIRTY;
1482
1483 3
        return $this;
1484
    }
1485
1486
    /**
1487
     * Gets a string representation of this QueryBuilder which corresponds to
1488
     * the final DQL query being constructed.
1489
     *
1490
     * @return string The string representation of this QueryBuilder.
1491
     */
1492 5
    public function __toString()
1493
    {
1494 5
        return $this->getDQL();
1495
    }
1496
1497
    /**
1498
     * Deep clones all expression objects in the DQL parts.
1499
     *
1500
     * @return void
1501
     */
1502 3
    public function __clone()
1503
    {
1504 3
        foreach ($this->_dqlParts as $part => $elements) {
1505 3
            if (is_array($this->_dqlParts[$part])) {
1506 3
                foreach ($this->_dqlParts[$part] as $idx => $element) {
1507 2
                    if (is_object($element)) {
1508 3
                        $this->_dqlParts[$part][$idx] = clone $element;
1509
                    }
1510
                }
1511 3
            } else if (is_object($elements)) {
1512 3
                $this->_dqlParts[$part] = clone $elements;
1513
            }
1514
        }
1515
1516 3
        $parameters = array();
1517
1518 3
        foreach ($this->parameters as $parameter) {
1519 1
            $parameters[] = clone $parameter;
1520
        }
1521
1522 3
        $this->parameters = new ArrayCollection($parameters);
1523 3
    }
1524
}
1525