Completed
Push — master ( b47a39...b7cace )
by Luís
18s
created

QueryBuilder::setParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 13
rs 9.4285
ccs 7
cts 7
cp 1
crap 2
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM;
21
22
use Doctrine\Common\Collections\ArrayCollection;
23
use Doctrine\Common\Collections\Criteria;
24
25
use Doctrine\ORM\Query\Expr;
26
use Doctrine\ORM\Query\QueryExpressionVisitor;
27
28
/**
29
 * This class is responsible for building DQL query strings via an object oriented
30
 * PHP interface.
31
 *
32
 * @since 2.0
33
 * @author Guilherme Blanco <[email protected]>
34
 * @author Jonathan Wage <[email protected]>
35
 * @author Roman Borschel <[email protected]>
36
 */
37
class QueryBuilder
38
{
39
    /* The query types. */
40
    const SELECT = 0;
41
    const DELETE = 1;
42
    const UPDATE = 2;
43
44
    /* The builder states. */
45
    const STATE_DIRTY = 0;
46
    const STATE_CLEAN = 1;
47
48
    /**
49
     * The EntityManager used by this QueryBuilder.
50
     *
51
     * @var EntityManagerInterface
52
     */
53
    private $_em;
54
55
    /**
56
     * The array of DQL parts collected.
57
     *
58
     * @var array
59
     */
60
    private $_dqlParts = [
61
        'distinct' => false,
62
        'select'  => [],
63
        'from'    => [],
64
        'join'    => [],
65
        'set'     => [],
66
        'where'   => null,
67
        'groupBy' => [],
68
        'having'  => null,
69
        'orderBy' => []
70
    ];
71
72
    /**
73
     * The type of query this is. Can be select, update or delete.
74
     *
75
     * @var integer
76
     */
77
    private $_type = self::SELECT;
78
79
    /**
80
     * The state of the query object. Can be dirty or clean.
81
     *
82
     * @var integer
83
     */
84
    private $_state = self::STATE_CLEAN;
85
86
    /**
87
     * The complete DQL string for this query.
88
     *
89
     * @var string
90
     */
91
    private $_dql;
92
93
    /**
94
     * The query parameters.
95
     *
96
     * @var \Doctrine\Common\Collections\ArrayCollection
97
     */
98
    private $parameters;
99
100
    /**
101
     * The index of the first result to retrieve.
102
     *
103
     * @var integer
104
     */
105
    private $_firstResult = null;
106
107
    /**
108
     * The maximum number of results to retrieve.
109
     *
110
     * @var integer|null
111
     */
112
    private $_maxResults = null;
113
114
    /**
115
     * Keeps root entity alias names for join entities.
116
     *
117
     * @var array
118
     */
119
    private $joinRootAliases = [];
120
121
     /**
122
     * Whether to use second level cache, if available.
123
     *
124
     * @var boolean
125
     */
126
    protected $cacheable = false;
127
128
    /**
129
     * Second level cache region name.
130
     *
131
     * @var string|null
132
     */
133
    protected $cacheRegion;
134
135
    /**
136
     * Second level query cache mode.
137
     *
138
     * @var integer|null
139
     */
140
    protected $cacheMode;
141
142
    /**
143
     * @var integer
144
     */
145
    protected $lifetime = 0;
146
147
    /**
148
     * Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
149
     *
150
     * @param EntityManagerInterface $em The EntityManager to use.
151
     */
152 128
    public function __construct(EntityManagerInterface $em)
153
    {
154 128
        $this->_em = $em;
155 128
        $this->parameters = new ArrayCollection();
156 128
    }
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 88
    public function getDQL()
310
    {
311 88
        if ($this->_dql !== null && $this->_state === self::STATE_CLEAN) {
312 44
            return $this->_dql;
313
        }
314
315 88
        switch ($this->_type) {
316 88
            case self::DELETE:
317 1
                $dql = $this->_getDQLForDelete();
318 1
                break;
319
320 87
            case self::UPDATE:
321 3
                $dql = $this->_getDQLForUpdate();
322 3
                break;
323
324 85
            case self::SELECT:
325
            default:
326 85
                $dql = $this->_getDQLForSelect();
327 85
                break;
328
        }
329
330 88
        $this->_state = self::STATE_CLEAN;
331 88
        $this->_dql   = $dql;
332
333 88
        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 77
    public function getQuery()
350
    {
351 77
        $parameters = clone $this->parameters;
352 77
        $query      = $this->_em->createQuery($this->getDQL())
353 77
            ->setParameters($parameters)
354 77
            ->setFirstResult($this->_firstResult)
355 77
            ->setMaxResults($this->_maxResults);
356
357 77
        if ($this->lifetime) {
358 1
            $query->setLifetime($this->lifetime);
359
        }
360
361 77
        if ($this->cacheMode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheMode of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
362 1
            $query->setCacheMode($this->cacheMode);
363
        }
364
365 77
        if ($this->cacheable) {
366 1
            $query->setCacheable($this->cacheable);
367
        }
368
369 77
        if ($this->cacheRegion) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheRegion of type null|string 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 77
        return $query;
374
    }
375
376
    /**
377
     * Finds the root entity alias of the joined entity.
378
     *
379
     * @param string $alias       The alias of the new join entity
380
     * @param string $parentAlias The parent entity alias of the join relationship
381
     *
382
     * @return string
383
     */
384 30
    private function findRootAlias($alias, $parentAlias)
385
    {
386 30
        $rootAlias = null;
387
388 30
        if (in_array($parentAlias, $this->getRootAliases())) {
389 29
            $rootAlias = $parentAlias;
390 7
        } elseif (isset($this->joinRootAliases[$parentAlias])) {
391 6
            $rootAlias = $this->joinRootAliases[$parentAlias];
392
        } else {
393
            // Should never happen with correct joining order. Might be
394
            // thoughtful to throw exception instead.
395 1
            $rootAlias = $this->getRootAlias();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\QueryBuilder::getRootAlias() has been deprecated: Please use $qb->getRootAliases() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

395
            $rootAlias = /** @scrutinizer ignore-deprecated */ $this->getRootAlias();

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

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

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

701
                $k = is_numeric($k) ? /** @scrutinizer ignore-deprecated */ $this->getRootAlias() : $k;

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

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

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