Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / QueryBuilder.php
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
24 use Doctrine\ORM\Query\Expr;
25
26 /**
27  * This class is responsible for building DQL query strings via an object oriented
28  * PHP interface.
29  *
30  * @since 2.0
31  * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
32  * @author Jonathan Wage <jonwage@gmail.com>
33  * @author Roman Borschel <roman@code-factory.org>
34  */
35 class QueryBuilder
36 {
37     /* The query types. */
38     const SELECT = 0;
39     const DELETE = 1;
40     const UPDATE = 2;
41
42     /** The builder states. */
43     const STATE_DIRTY = 0;
44     const STATE_CLEAN = 1;
45
46     /**
47      * @var EntityManager The EntityManager used by this QueryBuilder.
48      */
49     private $_em;
50
51     /**
52      * @var array The array of DQL parts collected.
53      */
54     private $_dqlParts = array(
55         'distinct' => false,
56         'select'  => array(),
57         'from'    => array(),
58         'join'    => array(),
59         'set'     => array(),
60         'where'   => null,
61         'groupBy' => array(),
62         'having'  => null,
63         'orderBy' => array()
64     );
65
66     /**
67      * @var integer The type of query this is. Can be select, update or delete.
68      */
69     private $_type = self::SELECT;
70
71     /**
72      * @var integer The state of the query object. Can be dirty or clean.
73      */
74     private $_state = self::STATE_CLEAN;
75
76     /**
77      * @var string The complete DQL string for this query.
78      */
79     private $_dql;
80
81     /**
82      * @var \Doctrine\Common\Collections\ArrayCollection The query parameters.
83      */
84     private $parameters = array();
85
86     /**
87      * @var integer The index of the first result to retrieve.
88      */
89     private $_firstResult = null;
90
91     /**
92      * @var integer The maximum number of results to retrieve.
93      */
94     private $_maxResults = null;
95
96     /**
97      * @var array Keeps root entity alias names for join entities.
98      */
99     private $joinRootAliases = array();
100
101     /**
102      * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
103      *
104      * @param EntityManager $em The EntityManager to use.
105      */
106     public function __construct(EntityManager $em)
107     {
108         $this->_em = $em;
109         $this->parameters = new ArrayCollection();
110     }
111
112     /**
113      * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
114      * This producer method is intended for convenient inline usage. Example:
115      *
116      * <code>
117      *     $qb = $em->createQueryBuilder()
118      *         ->select('u')
119      *         ->from('User', 'u')
120      *         ->where($qb->expr()->eq('u.id', 1));
121      * </code>
122      *
123      * For more complex expression construction, consider storing the expression
124      * builder object in a local variable.
125      *
126      * @return Query\Expr
127      */
128     public function expr()
129     {
130         return $this->_em->getExpressionBuilder();
131     }
132
133     /**
134      * Get the type of the currently built query.
135      *
136      * @return integer
137      */
138     public function getType()
139     {
140         return $this->_type;
141     }
142
143     /**
144      * Get the associated EntityManager for this query builder.
145      *
146      * @return EntityManager
147      */
148     public function getEntityManager()
149     {
150         return $this->_em;
151     }
152
153     /**
154      * Get the state of this query builder instance.
155      *
156      * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
157      */
158     public function getState()
159     {
160         return $this->_state;
161     }
162
163     /**
164      * Get the complete DQL string formed by the current specifications of this QueryBuilder.
165      *
166      * <code>
167      *     $qb = $em->createQueryBuilder()
168      *         ->select('u')
169      *         ->from('User', 'u')
170      *     echo $qb->getDql(); // SELECT u FROM User u
171      * </code>
172      *
173      * @return string The DQL query string.
174      */
175     public function getDQL()
176     {
177         if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
178             return $this->_dql;
179         }
180
181         $dql = '';
182
183         switch ($this->_type) {
184             case self::DELETE:
185                 $dql = $this->_getDQLForDelete();
186                 break;
187
188             case self::UPDATE:
189                 $dql = $this->_getDQLForUpdate();
190                 break;
191
192             case self::SELECT:
193             default:
194                 $dql = $this->_getDQLForSelect();
195                 break;
196         }
197
198         $this->_state = self::STATE_CLEAN;
199         $this->_dql   = $dql;
200
201         return $dql;
202     }
203
204     /**
205      * Constructs a Query instance from the current specifications of the builder.
206      *
207      * <code>
208      *     $qb = $em->createQueryBuilder()
209      *         ->select('u')
210      *         ->from('User', 'u');
211      *     $q = $qb->getQuery();
212      *     $results = $q->execute();
213      * </code>
214      *
215      * @return Query
216      */
217     public function getQuery()
218     {
219         $parameters = clone $this->parameters;
220
221         return $this->_em->createQuery($this->getDQL())
222             ->setParameters($parameters)
223             ->setFirstResult($this->_firstResult)
224             ->setMaxResults($this->_maxResults);
225     }
226
227     /**
228      * Finds the root entity alias of the joined entity.
229      *
230      * @param string $alias The alias of the new join entity
231      * @param string $parentAlias The parent entity alias of the join relationship
232      * @return string
233      */
234     private function findRootAlias($alias, $parentAlias)
235     {
236         $rootAlias = null;
237
238         if (in_array($parentAlias, $this->getRootAliases())) {
239             $rootAlias = $parentAlias;
240         } elseif (isset($this->joinRootAliases[$parentAlias])) {
241             $rootAlias = $this->joinRootAliases[$parentAlias];
242         } else {
243             // Should never happen with correct joining order. Might be
244             // thoughtful to throw exception instead.
245             $rootAlias = $this->getRootAlias();
246         }
247
248         $this->joinRootAliases[$alias] = $rootAlias;
249
250         return $rootAlias;
251     }
252
253     /**
254      * Gets the FIRST root alias of the query. This is the first entity alias involved
255      * in the construction of the query.
256      *
257      * <code>
258      * $qb = $em->createQueryBuilder()
259      *     ->select('u')
260      *     ->from('User', 'u');
261      *
262      * echo $qb->getRootAlias(); // u
263      * </code>
264      *
265      * @deprecated Please use $qb->getRootAliases() instead.
266      * @return string $rootAlias
267      */
268     public function getRootAlias()
269     {
270         $aliases = $this->getRootAliases();
271         return $aliases[0];
272     }
273
274     /**
275      * Gets the root aliases of the query. This is the entity aliases involved
276      * in the construction of the query.
277      *
278      * <code>
279      *     $qb = $em->createQueryBuilder()
280      *         ->select('u')
281      *         ->from('User', 'u');
282      *
283      *     $qb->getRootAliases(); // array('u')
284      * </code>
285      *
286      * @return array $rootAliases
287      */
288     public function getRootAliases()
289     {
290         $aliases = array();
291
292         foreach ($this->_dqlParts['from'] as &$fromClause) {
293             if (is_string($fromClause)) {
294                 $spacePos = strrpos($fromClause, ' ');
295                 $from     = substr($fromClause, 0, $spacePos);
296                 $alias    = substr($fromClause, $spacePos + 1);
297
298                 $fromClause = new Query\Expr\From($from, $alias);
299             }
300
301             $aliases[] = $fromClause->getAlias();
302         }
303
304         return $aliases;
305     }
306
307     /**
308      * Gets the root entities of the query. This is the entity aliases involved
309      * in the construction of the query.
310      *
311      * <code>
312      *     $qb = $em->createQueryBuilder()
313      *         ->select('u')
314      *         ->from('User', 'u');
315      *
316      *     $qb->getRootEntities(); // array('User')
317      * </code>
318      *
319      * @return array $rootEntities
320      */
321     public function getRootEntities()
322     {
323         $entities = array();
324
325         foreach ($this->_dqlParts['from'] as &$fromClause) {
326             if (is_string($fromClause)) {
327                 $spacePos = strrpos($fromClause, ' ');
328                 $from     = substr($fromClause, 0, $spacePos);
329                 $alias    = substr($fromClause, $spacePos + 1);
330
331                 $fromClause = new Query\Expr\From($from, $alias);
332             }
333
334             $entities[] = $fromClause->getFrom();
335         }
336
337         return $entities;
338     }
339
340     /**
341      * Sets a query parameter for the query being constructed.
342      *
343      * <code>
344      *     $qb = $em->createQueryBuilder()
345      *         ->select('u')
346      *         ->from('User', 'u')
347      *         ->where('u.id = :user_id')
348      *         ->setParameter('user_id', 1);
349      * </code>
350      *
351      * @param string|integer $key The parameter position or name.
352      * @param mixed $value The parameter value.
353      * @param string|null $type PDO::PARAM_* or \Doctrine\DBAL\Types\Type::* constant
354      * @return QueryBuilder This QueryBuilder instance.
355      */
356     public function setParameter($key, $value, $type = null)
357     {
358         $filteredParameters = $this->parameters->filter(
359             function ($parameter) use ($key)
360             {
361                 // Must not be identical because of string to integer conversion
362                 return ($key == $parameter->getName());
363             }
364         );
365
366         if (count($filteredParameters)) {
367             $parameter = $filteredParameters->first();
368             $parameter->setValue($value, $type);
369
370             return $this;
371         }
372
373         $parameter = new Query\Parameter($key, $value, $type);
374
375         $this->parameters->add($parameter);
376
377         return $this;
378     }
379
380     /**
381      * Sets a collection of query parameters for the query being constructed.
382      *
383      * <code>
384      *     $qb = $em->createQueryBuilder()
385      *         ->select('u')
386      *         ->from('User', 'u')
387      *         ->where('u.id = :user_id1 OR u.id = :user_id2')
388      *         ->setParameters(new ArrayCollection(array(
389      *             new Parameter('user_id1', 1),
390      *             new Parameter('user_id2', 2)
391               )));
392      * </code>
393      *
394      * @param \Doctrine\Common\Collections\ArrayCollection|array $params The query parameters to set.
395      * @return QueryBuilder This QueryBuilder instance.
396      */
397     public function setParameters($parameters)
398     {
399         // BC compatibility with 2.3-
400         if (is_array($parameters)) {
401             $parameterCollection = new ArrayCollection();
402
403             foreach ($parameters as $key => $value) {
404                 $parameter = new Query\Parameter($key, $value);
405
406                 $parameterCollection->add($parameter);
407             }
408
409             $parameters = $parameterCollection;
410         }
411
412         $this->parameters = $parameters;
413
414         return $this;
415     }
416
417     /**
418      * Gets all defined query parameters for the query being constructed.
419      *
420      * @return \Doctrine\Common\Collections\ArrayCollection The currently defined query parameters.
421      */
422     public function getParameters()
423     {
424         return $this->parameters;
425     }
426
427     /**
428      * Gets a (previously set) query parameter of the query being constructed.
429      *
430      * @param mixed $key The key (index or name) of the bound parameter.
431      *
432      * @return Query\Parameter|null The value of the bound parameter.
433      */
434     public function getParameter($key)
435     {
436         $filteredParameters = $this->parameters->filter(
437             function ($parameter) use ($key)
438             {
439                 // Must not be identical because of string to integer conversion
440                 return ($key == $parameter->getName());
441             }
442         );
443
444         return count($filteredParameters) ? $filteredParameters->first() : null;
445     }
446
447     /**
448      * Sets the position of the first result to retrieve (the "offset").
449      *
450      * @param integer $firstResult The first result to return.
451      * @return QueryBuilder This QueryBuilder instance.
452      */
453     public function setFirstResult($firstResult)
454     {
455         $this->_firstResult = $firstResult;
456
457         return $this;
458     }
459
460     /**
461      * Gets the position of the first result the query object was set to retrieve (the "offset").
462      * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
463      *
464      * @return integer The position of the first result.
465      */
466     public function getFirstResult()
467     {
468         return $this->_firstResult;
469     }
470
471     /**
472      * Sets the maximum number of results to retrieve (the "limit").
473      *
474      * @param integer $maxResults The maximum number of results to retrieve.
475      * @return QueryBuilder This QueryBuilder instance.
476      */
477     public function setMaxResults($maxResults)
478     {
479         $this->_maxResults = $maxResults;
480
481         return $this;
482     }
483
484     /**
485      * Gets the maximum number of results the query object was set to retrieve (the "limit").
486      * Returns NULL if {@link setMaxResults} was not applied to this query builder.
487      *
488      * @return integer Maximum number of results.
489      */
490     public function getMaxResults()
491     {
492         return $this->_maxResults;
493     }
494
495     /**
496      * Either appends to or replaces a single, generic query part.
497      *
498      * The available parts are: 'select', 'from', 'join', 'set', 'where',
499      * 'groupBy', 'having' and 'orderBy'.
500      *
501      * @param string $dqlPartName
502      * @param string $dqlPart
503      * @param string $append
504      * @return QueryBuilder This QueryBuilder instance.
505      */
506     public function add($dqlPartName, $dqlPart, $append = false)
507     {
508         $isMultiple = is_array($this->_dqlParts[$dqlPartName]);
509
510         // This is introduced for backwards compatibility reasons.
511         // TODO: Remove for 3.0
512         if ($dqlPartName == 'join') {
513             $newDqlPart = array();
514
515             foreach ($dqlPart as $k => $v) {
516                 $k = is_numeric($k) ? $this->getRootAlias() : $k;
517
518                 $newDqlPart[$k] = $v;
519             }
520
521             $dqlPart = $newDqlPart;
522         }
523
524         if ($append && $isMultiple) {
525             if (is_array($dqlPart)) {
526                 $key = key($dqlPart);
527
528                 $this->_dqlParts[$dqlPartName][$key][] = $dqlPart[$key];
529             } else {
530                 $this->_dqlParts[$dqlPartName][] = $dqlPart;
531             }
532         } else {
533             $this->_dqlParts[$dqlPartName] = ($isMultiple) ? array($dqlPart) : $dqlPart;
534         }
535
536         $this->_state = self::STATE_DIRTY;
537
538         return $this;
539     }
540
541     /**
542      * Specifies an item that is to be returned in the query result.
543      * Replaces any previously specified selections, if any.
544      *
545      * <code>
546      *     $qb = $em->createQueryBuilder()
547      *         ->select('u', 'p')
548      *         ->from('User', 'u')
549      *         ->leftJoin('u.Phonenumbers', 'p');
550      * </code>
551      *
552      * @param mixed $select The selection expressions.
553      * @return QueryBuilder This QueryBuilder instance.
554      */
555     public function select($select = null)
556     {
557         $this->_type = self::SELECT;
558
559         if (empty($select)) {
560             return $this;
561         }
562
563         $selects = is_array($select) ? $select : func_get_args();
564
565         return $this->add('select', new Expr\Select($selects), false);
566     }
567
568     /**
569      * Add a DISTINCT flag to this query.
570      *
571      * <code>
572      *     $qb = $em->createQueryBuilder()
573      *         ->select('u')
574      *         ->distinct()
575      *         ->from('User', 'u');
576      * </code>
577      *
578      * @param bool
579      * @return QueryBuilder
580      */
581     public function distinct($flag = true)
582     {
583         $this->_dqlParts['distinct'] = (bool) $flag;
584
585         return $this;
586     }
587
588     /**
589      * Adds an item that is to be returned in the query result.
590      *
591      * <code>
592      *     $qb = $em->createQueryBuilder()
593      *         ->select('u')
594      *         ->addSelect('p')
595      *         ->from('User', 'u')
596      *         ->leftJoin('u.Phonenumbers', 'p');
597      * </code>
598      *
599      * @param mixed $select The selection expression.
600      * @return QueryBuilder This QueryBuilder instance.
601      */
602     public function addSelect($select = null)
603     {
604         $this->_type = self::SELECT;
605
606         if (empty($select)) {
607             return $this;
608         }
609
610         $selects = is_array($select) ? $select : func_get_args();
611
612         return $this->add('select', new Expr\Select($selects), true);
613     }
614
615     /**
616      * Turns the query being built into a bulk delete query that ranges over
617      * a certain entity type.
618      *
619      * <code>
620      *     $qb = $em->createQueryBuilder()
621      *         ->delete('User', 'u')
622      *         ->where('u.id = :user_id');
623      *         ->setParameter('user_id', 1);
624      * </code>
625      *
626      * @param string $delete The class/type whose instances are subject to the deletion.
627      * @param string $alias The class/type alias used in the constructed query.
628      * @return QueryBuilder This QueryBuilder instance.
629      */
630     public function delete($delete = null, $alias = null)
631     {
632         $this->_type = self::DELETE;
633
634         if ( ! $delete) {
635             return $this;
636         }
637
638         return $this->add('from', new Expr\From($delete, $alias));
639     }
640
641     /**
642      * Turns the query being built into a bulk update query that ranges over
643      * a certain entity type.
644      *
645      * <code>
646      *     $qb = $em->createQueryBuilder()
647      *         ->update('User', 'u')
648      *         ->set('u.password', md5('password'))
649      *         ->where('u.id = ?');
650      * </code>
651      *
652      * @param string $update The class/type whose instances are subject to the update.
653      * @param string $alias The class/type alias used in the constructed query.
654      * @return QueryBuilder This QueryBuilder instance.
655      */
656     public function update($update = null, $alias = null)
657     {
658         $this->_type = self::UPDATE;
659
660         if ( ! $update) {
661             return $this;
662         }
663
664         return $this->add('from', new Expr\From($update, $alias));
665     }
666
667     /**
668      * Create and add a query root corresponding to the entity identified by the given alias,
669      * forming a cartesian product with any existing query roots.
670      *
671      * <code>
672      *     $qb = $em->createQueryBuilder()
673      *         ->select('u')
674      *         ->from('User', 'u')
675      * </code>
676      *
677      * @param string $from   The class name.
678      * @param string $alias  The alias of the class.
679      * @param string $indexBy The index for the from.
680      * @return QueryBuilder This QueryBuilder instance.
681      */
682     public function from($from, $alias, $indexBy = null)
683     {
684         return $this->add('from', new Expr\From($from, $alias, $indexBy), true);
685     }
686
687     /**
688      * Creates and adds a join over an entity association to the query.
689      *
690      * The entities in the joined association will be fetched as part of the query
691      * result if the alias used for the joined association is placed in the select
692      * expressions.
693      *
694      * <code>
695      *     $qb = $em->createQueryBuilder()
696      *         ->select('u')
697      *         ->from('User', 'u')
698      *         ->join('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
699      * </code>
700      *
701      * @param string $join The relationship to join
702      * @param string $alias The alias of the join
703      * @param string $conditionType The condition type constant. Either ON or WITH.
704      * @param string $condition The condition for the join
705      * @param string $indexBy The index for the join
706      * @return QueryBuilder This QueryBuilder instance.
707      */
708     public function join($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
709     {
710         return $this->innerJoin($join, $alias, $conditionType, $condition, $indexBy);
711     }
712
713     /**
714      * Creates and adds a join over an entity association to the query.
715      *
716      * The entities in the joined association will be fetched as part of the query
717      * result if the alias used for the joined association is placed in the select
718      * expressions.
719      *
720      *     [php]
721      *     $qb = $em->createQueryBuilder()
722      *         ->select('u')
723      *         ->from('User', 'u')
724      *         ->innerJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
725      *
726      * @param string $join The relationship to join
727      * @param string $alias The alias of the join
728      * @param string $conditionType The condition type constant. Either ON or WITH.
729      * @param string $condition The condition for the join
730      * @param string $indexBy The index for the join
731      * @return QueryBuilder This QueryBuilder instance.
732      */
733     public function innerJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
734     {
735         $parentAlias = substr($join, 0, strpos($join, '.'));
736
737         $rootAlias = $this->findRootAlias($alias, $parentAlias);
738
739         $join = new Expr\Join(
740             Expr\Join::INNER_JOIN, $join, $alias, $conditionType, $condition, $indexBy
741         );
742
743         return $this->add('join', array($rootAlias => $join), true);
744     }
745
746     /**
747      * Creates and adds a left join over an entity association to the query.
748      *
749      * The entities in the joined association will be fetched as part of the query
750      * result if the alias used for the joined association is placed in the select
751      * expressions.
752      *
753      * <code>
754      *     $qb = $em->createQueryBuilder()
755      *         ->select('u')
756      *         ->from('User', 'u')
757      *         ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1');
758      * </code>
759      *
760      * @param string $join The relationship to join
761      * @param string $alias The alias of the join
762      * @param string $conditionType The condition type constant. Either ON or WITH.
763      * @param string $condition The condition for the join
764      * @param string $indexBy The index for the join
765      * @return QueryBuilder This QueryBuilder instance.
766      */
767     public function leftJoin($join, $alias, $conditionType = null, $condition = null, $indexBy = null)
768     {
769         $parentAlias = substr($join, 0, strpos($join, '.'));
770
771         $rootAlias = $this->findRootAlias($alias, $parentAlias);
772
773         $join = new Expr\Join(
774             Expr\Join::LEFT_JOIN, $join, $alias, $conditionType, $condition, $indexBy
775         );
776
777         return $this->add('join', array($rootAlias => $join), true);
778     }
779
780     /**
781      * Sets a new value for a field in a bulk update query.
782      *
783      * <code>
784      *     $qb = $em->createQueryBuilder()
785      *         ->update('User', 'u')
786      *         ->set('u.password', md5('password'))
787      *         ->where('u.id = ?');
788      * </code>
789      *
790      * @param string $key The key/field to set.
791      * @param string $value The value, expression, placeholder, etc.
792      * @return QueryBuilder This QueryBuilder instance.
793      */
794     public function set($key, $value)
795     {
796         return $this->add('set', new Expr\Comparison($key, Expr\Comparison::EQ, $value), true);
797     }
798
799     /**
800      * Specifies one or more restrictions to the query result.
801      * Replaces any previously specified restrictions, if any.
802      *
803      * <code>
804      *     $qb = $em->createQueryBuilder()
805      *         ->select('u')
806      *         ->from('User', 'u')
807      *         ->where('u.id = ?');
808      *
809      *     // You can optionally programatically build and/or expressions
810      *     $qb = $em->createQueryBuilder();
811      *
812      *     $or = $qb->expr()->orx();
813      *     $or->add($qb->expr()->eq('u.id', 1));
814      *     $or->add($qb->expr()->eq('u.id', 2));
815      *
816      *     $qb->update('User', 'u')
817      *         ->set('u.password', md5('password'))
818      *         ->where($or);
819      * </code>
820      *
821      * @param mixed $predicates The restriction predicates.
822      * @return QueryBuilder This QueryBuilder instance.
823      */
824     public function where($predicates)
825     {
826         if ( ! (func_num_args() == 1 && $predicates instanceof Expr\Composite)) {
827             $predicates = new Expr\Andx(func_get_args());
828         }
829
830         return $this->add('where', $predicates);
831     }
832
833     /**
834      * Adds one or more restrictions to the query results, forming a logical
835      * conjunction with any previously specified restrictions.
836      *
837      * <code>
838      *     $qb = $em->createQueryBuilder()
839      *         ->select('u')
840      *         ->from('User', 'u')
841      *         ->where('u.username LIKE ?')
842      *         ->andWhere('u.is_active = 1');
843      * </code>
844      *
845      * @param mixed $where The query restrictions.
846      * @return QueryBuilder This QueryBuilder instance.
847      * @see where()
848      */
849     public function andWhere($where)
850     {
851         $where = $this->getDQLPart('where');
852         $args  = func_get_args();
853
854         if ($where instanceof Expr\Andx) {
855             $where->addMultiple($args);
856         } else {
857             array_unshift($args, $where);
858             $where = new Expr\Andx($args);
859         }
860
861         return $this->add('where', $where, true);
862     }
863
864     /**
865      * Adds one or more restrictions to the query results, forming a logical
866      * disjunction with any previously specified restrictions.
867      *
868      * <code>
869      *     $qb = $em->createQueryBuilder()
870      *         ->select('u')
871      *         ->from('User', 'u')
872      *         ->where('u.id = 1')
873      *         ->orWhere('u.id = 2');
874      * </code>
875      *
876      * @param mixed $where The WHERE statement
877      * @return QueryBuilder $qb
878      * @see where()
879      */
880     public function orWhere($where)
881     {
882         $where = $this->getDqlPart('where');
883         $args  = func_get_args();
884
885         if ($where instanceof Expr\Orx) {
886             $where->addMultiple($args);
887         } else {
888             array_unshift($args, $where);
889             $where = new Expr\Orx($args);
890         }
891
892         return $this->add('where', $where, true);
893     }
894
895     /**
896      * Specifies a grouping over the results of the query.
897      * Replaces any previously specified groupings, if any.
898      *
899      * <code>
900      *     $qb = $em->createQueryBuilder()
901      *         ->select('u')
902      *         ->from('User', 'u')
903      *         ->groupBy('u.id');
904      * </code>
905      *
906      * @param string $groupBy The grouping expression.
907      * @return QueryBuilder This QueryBuilder instance.
908      */
909     public function groupBy($groupBy)
910     {
911         return $this->add('groupBy', new Expr\GroupBy(func_get_args()));
912     }
913
914
915     /**
916      * Adds a grouping expression to the query.
917      *
918      * <code>
919      *     $qb = $em->createQueryBuilder()
920      *         ->select('u')
921      *         ->from('User', 'u')
922      *         ->groupBy('u.lastLogin');
923      *         ->addGroupBy('u.createdAt')
924      * </code>
925      *
926      * @param string $groupBy The grouping expression.
927      * @return QueryBuilder This QueryBuilder instance.
928      */
929     public function addGroupBy($groupBy)
930     {
931         return $this->add('groupBy', new Expr\GroupBy(func_get_args()), true);
932     }
933
934     /**
935      * Specifies a restriction over the groups of the query.
936      * Replaces any previous having restrictions, if any.
937      *
938      * @param mixed $having The restriction over the groups.
939      * @return QueryBuilder This QueryBuilder instance.
940      */
941     public function having($having)
942     {
943         if ( ! (func_num_args() == 1 && ($having instanceof Expr\Andx || $having instanceof Expr\Orx))) {
944             $having = new Expr\Andx(func_get_args());
945         }
946
947         return $this->add('having', $having);
948     }
949
950     /**
951      * Adds a restriction over the groups of the query, forming a logical
952      * conjunction with any existing having restrictions.
953      *
954      * @param mixed $having The restriction to append.
955      * @return QueryBuilder This QueryBuilder instance.
956      */
957     public function andHaving($having)
958     {
959         $having = $this->getDqlPart('having');
960         $args   = func_get_args();
961
962         if ($having instanceof Expr\Andx) {
963             $having->addMultiple($args);
964         } else {
965             array_unshift($args, $having);
966             $having = new Expr\Andx($args);
967         }
968
969         return $this->add('having', $having);
970     }
971
972     /**
973      * Adds a restriction over the groups of the query, forming a logical
974      * disjunction with any existing having restrictions.
975      *
976      * @param mixed $having The restriction to add.
977      * @return QueryBuilder This QueryBuilder instance.
978      */
979     public function orHaving($having)
980     {
981         $having = $this->getDqlPart('having');
982         $args   = func_get_args();
983
984         if ($having instanceof Expr\Orx) {
985             $having->addMultiple($args);
986         } else {
987             array_unshift($args, $having);
988             $having = new Expr\Orx($args);
989         }
990
991         return $this->add('having', $having);
992     }
993
994     /**
995      * Specifies an ordering for the query results.
996      * Replaces any previously specified orderings, if any.
997      *
998      * @param string $sort The ordering expression.
999      * @param string $order The ordering direction.
1000      * @return QueryBuilder This QueryBuilder instance.
1001      */
1002     public function orderBy($sort, $order = null)
1003     {
1004         $orderBy = ($sort instanceof Expr\OrderBy) ? $sort : new Expr\OrderBy($sort, $order);
1005
1006         return $this->add('orderBy', $orderBy);
1007     }
1008
1009     /**
1010      * Adds an ordering to the query results.
1011      *
1012      * @param string $sort The ordering expression.
1013      * @param string $order The ordering direction.
1014      * @return QueryBuilder This QueryBuilder instance.
1015      */
1016     public function addOrderBy($sort, $order = null)
1017     {
1018         return $this->add('orderBy', new Expr\OrderBy($sort, $order), true);
1019     }
1020
1021     /**
1022      * Get a query part by its name.
1023      *
1024      * @param string $queryPartName
1025      * @return mixed $queryPart
1026      * @todo Rename: getQueryPart (or remove?)
1027      */
1028     public function getDQLPart($queryPartName)
1029     {
1030         return $this->_dqlParts[$queryPartName];
1031     }
1032
1033     /**
1034      * Get all query parts.
1035      *
1036      * @return array $dqlParts
1037      * @todo Rename: getQueryParts (or remove?)
1038      */
1039     public function getDQLParts()
1040     {
1041         return $this->_dqlParts;
1042     }
1043
1044     private function _getDQLForDelete()
1045     {
1046          return 'DELETE'
1047               . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', '))
1048               . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
1049               . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
1050     }
1051
1052     private function _getDQLForUpdate()
1053     {
1054          return 'UPDATE'
1055               . $this->_getReducedDQLQueryPart('from', array('pre' => ' ', 'separator' => ', '))
1056               . $this->_getReducedDQLQueryPart('set', array('pre' => ' SET ', 'separator' => ', '))
1057               . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
1058               . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
1059     }
1060
1061     private function _getDQLForSelect()
1062     {
1063         $dql = 'SELECT'
1064              . ($this->_dqlParts['distinct']===true ? ' DISTINCT' : '')
1065              . $this->_getReducedDQLQueryPart('select', array('pre' => ' ', 'separator' => ', '));
1066
1067         $fromParts   = $this->getDQLPart('from');
1068         $joinParts   = $this->getDQLPart('join');
1069         $fromClauses = array();
1070
1071         // Loop through all FROM clauses
1072         if ( ! empty($fromParts)) {
1073             $dql .= ' FROM ';
1074
1075             foreach ($fromParts as $from) {
1076                 $fromClause = (string) $from;
1077
1078                 if ($from instanceof Expr\From && isset($joinParts[$from->getAlias()])) {
1079                     foreach ($joinParts[$from->getAlias()] as $join) {
1080                         $fromClause .= ' ' . ((string) $join);
1081                     }
1082                 }
1083
1084                 $fromClauses[] = $fromClause;
1085             }
1086         }
1087
1088         $dql .= implode(', ', $fromClauses)
1089               . $this->_getReducedDQLQueryPart('where', array('pre' => ' WHERE '))
1090               . $this->_getReducedDQLQueryPart('groupBy', array('pre' => ' GROUP BY ', 'separator' => ', '))
1091               . $this->_getReducedDQLQueryPart('having', array('pre' => ' HAVING '))
1092               . $this->_getReducedDQLQueryPart('orderBy', array('pre' => ' ORDER BY ', 'separator' => ', '));
1093
1094         return $dql;
1095     }
1096
1097     private function _getReducedDQLQueryPart($queryPartName, $options = array())
1098     {
1099         $queryPart = $this->getDQLPart($queryPartName);
1100
1101         if (empty($queryPart)) {
1102             return (isset($options['empty']) ? $options['empty'] : '');
1103         }
1104
1105         return (isset($options['pre']) ? $options['pre'] : '')
1106              . (is_array($queryPart) ? implode($options['separator'], $queryPart) : $queryPart)
1107              . (isset($options['post']) ? $options['post'] : '');
1108     }
1109
1110     /**
1111      * Reset DQL parts
1112      *
1113      * @param array $parts
1114      * @return QueryBuilder
1115      */
1116     public function resetDQLParts($parts = null)
1117     {
1118         if (is_null($parts)) {
1119             $parts = array_keys($this->_dqlParts);
1120         }
1121
1122         foreach ($parts as $part) {
1123             $this->resetDQLPart($part);
1124         }
1125
1126         return $this;
1127     }
1128
1129     /**
1130      * Reset single DQL part
1131      *
1132      * @param string $part
1133      * @return QueryBuilder;
1134      */
1135     public function resetDQLPart($part)
1136     {
1137         $this->_dqlParts[$part] = is_array($this->_dqlParts[$part]) ? array() : null;
1138         $this->_state           = self::STATE_DIRTY;
1139
1140         return $this;
1141     }
1142
1143     /**
1144      * Gets a string representation of this QueryBuilder which corresponds to
1145      * the final DQL query being constructed.
1146      *
1147      * @return string The string representation of this QueryBuilder.
1148      */
1149     public function __toString()
1150     {
1151         return $this->getDQL();
1152     }
1153
1154     /**
1155      * Deep clone of all expression objects in the DQL parts.
1156      *
1157      * @return void
1158      */
1159     public function __clone()
1160     {
1161         foreach ($this->_dqlParts as $part => $elements) {
1162             if (is_array($this->_dqlParts[$part])) {
1163                 foreach ($this->_dqlParts[$part] as $idx => $element) {
1164                     if (is_object($element)) {
1165                         $this->_dqlParts[$part][$idx] = clone $element;
1166                     }
1167                 }
1168             } else if (is_object($elements)) {
1169                 $this->_dqlParts[$part] = clone $elements;
1170             }
1171         }
1172
1173         $parameters = array();
1174
1175         foreach ($this->parameters as $parameter) {
1176             $parameters[] = clone $parameter;
1177         }
1178
1179         $this->parameters = new ArrayCollection($parameters);
1180     }
1181 }