Completed
Pull Request — master (#3340)
by Michael
63:56
created

QueryBuilder::distinct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 4
cts 4
cp 1
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Doctrine\DBAL\Query;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\ParameterType;
8
use Doctrine\DBAL\Query\Expression\CompositeExpression;
9
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
10
use function array_key_exists;
11
use function array_keys;
12
use function array_unshift;
13
use function func_get_args;
14
use function func_num_args;
15
use function implode;
16
use function is_array;
17
use function is_object;
18
use function key;
19
use function strtoupper;
20
use function substr;
21
22
/**
23
 * QueryBuilder class is responsible to dynamically create SQL queries.
24
 *
25
 * Important: Verify that every feature you use will work with your database vendor.
26
 * SQL Query Builder does not attempt to validate the generated SQL at all.
27
 *
28
 * The query builder does no validation whatsoever if certain features even work with the
29
 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
30
 * even if some vendors such as MySQL support it.
31
 */
32
class QueryBuilder
33
{
34
    /*
35
     * The query types.
36
     */
37
    public const SELECT = 0;
38
    public const DELETE = 1;
39
    public const UPDATE = 2;
40
    public const INSERT = 3;
41
42
    /*
43
     * The builder states.
44
     */
45
    public const STATE_DIRTY = 0;
46
    public const STATE_CLEAN = 1;
47
48
    /**
49
     * The DBAL Connection.
50
     *
51
     * @var Connection
52
     */
53
    private $connection;
54
55
    /**
56
     * The array of SQL parts collected.
57
     *
58
     * @var mixed[]
59
     */
60
    private $sqlParts = [
61
        'select'  => [],
62
        'from'    => [],
63
        'join'    => [],
64
        'set'     => [],
65
        'where'   => null,
66
        'groupBy' => [],
67
        'having'  => null,
68
        'orderBy' => [],
69
        'values'  => [],
70
    ];
71
72
    /**
73
     * The complete SQL string for this query.
74
     *
75
     * @var string
76
     */
77
    private $sql;
78
79
    /**
80
     * The query parameters.
81
     *
82
     * @var mixed[]
83
     */
84
    private $params = [];
85
86
    /**
87
     * The parameter type map of this query.
88
     *
89
     * @var int[]|string[]
90
     */
91
    private $paramTypes = [];
92
93
    /**
94
     * The type of query this is. Can be select, update or delete.
95
     *
96
     * @var int
97
     */
98
    private $type = self::SELECT;
99
100
    /**
101
     * The state of the query object. Can be dirty or clean.
102
     *
103
     * @var int
104
     */
105
    private $state = self::STATE_CLEAN;
106
107
    /**
108
     * The index of the first result to retrieve.
109
     *
110
     * @var int
111
     */
112
    private $firstResult = null;
113
114
    /**
115
     * The maximum number of results to retrieve.
116
     *
117
     * @var int
118
     */
119
    private $maxResults = null;
120
121
    /**
122
     * The counter of bound parameters used with {@see bindValue).
123
     *
124
     * @var int
125
     */
126
    private $boundCounter = 0;
127
128
    /**
129
     * Whether or not to include a distinct flag in the SELECT clause
130
     *
131
     * @var bool
132
     */
133 1254
    private $isDistinct = false;
134
135 1254
    /**
136 1254
     * Initializes a new <tt>QueryBuilder</tt>.
137
     *
138
     * @param Connection $connection The DBAL Connection.
139
     */
140
    public function __construct(Connection $connection)
141
    {
142
        $this->connection = $connection;
143
    }
144
145
    /**
146
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
147
     * This producer method is intended for convenient inline usage. Example:
148
     *
149
     * <code>
150
     *     $qb = $conn->createQueryBuilder()
151
     *         ->select('u')
152
     *         ->from('users', 'u')
153
     *         ->where($qb->expr()->eq('u.id', 1));
154 532
     * </code>
155
     *
156 532
     * For more complex expression construction, consider storing the expression
157
     * builder object in a local variable.
158
     *
159
     * @return ExpressionBuilder
160
     */
161
    public function expr()
162
    {
163
        return $this->connection->getExpressionBuilder();
164 228
    }
165
166 228
    /**
167
     * Gets the type of the currently built query.
168
     *
169
     * @return int
170
     */
171
    public function getType()
172
    {
173
        return $this->type;
174 19
    }
175
176 19
    /**
177
     * Gets the associated DBAL Connection for this query builder.
178
     *
179
     * @return Connection
180
     */
181
    public function getConnection()
182
    {
183
        return $this->connection;
184 57
    }
185
186 57
    /**
187
     * Gets the state of this query builder instance.
188
     *
189
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
190
     */
191
    public function getState()
192
    {
193
        return $this->state;
194
    }
195
196
    /**
197
     * Executes this query using the bound parameters and their types.
198
     *
199
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
200
     * for insert, update and delete statements.
201
     *
202
     * @return Statement|int
203
     */
204
    public function execute()
205
    {
206
        if ($this->type === self::SELECT) {
207
            return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
208
        }
209
210
        return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes);
211
    }
212
213
    /**
214
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
215
     *
216
     * <code>
217
     *     $qb = $em->createQueryBuilder()
218 1064
     *         ->select('u')
219
     *         ->from('User', 'u')
220 1064
     *     echo $qb->getSQL(); // SELECT u FROM User u
221 19
     * </code>
222
     *
223
     * @return string The SQL query string.
224 1064
     */
225 1064
    public function getSQL()
226 76
    {
227 76
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
228 988
            return $this->sql;
229 57
        }
230 57
231
        switch ($this->type) {
232 931
            case self::INSERT:
233 57
                $sql = $this->getSQLForInsert();
234 57
                break;
235
            case self::DELETE:
236 874
                $sql = $this->getSQLForDelete();
237
                break;
238 874
239 836
            case self::UPDATE:
240
                $sql = $this->getSQLForUpdate();
241
                break;
242 1026
243 1026
            case self::SELECT:
244
            default:
245 1026
                $sql = $this->getSQLForSelect();
246
                break;
247
        }
248
249
        $this->state = self::STATE_CLEAN;
250
        $this->sql   = $sql;
251
252
        return $sql;
253
    }
254
255
    /**
256
     * Sets a query parameter for the query being constructed.
257
     *
258
     * <code>
259
     *     $qb = $conn->createQueryBuilder()
260
     *         ->select('u')
261
     *         ->from('users', 'u')
262
     *         ->where('u.id = :user_id')
263
     *         ->setParameter(':user_id', 1);
264
     * </code>
265 114
     *
266
     * @param string|int      $key   The parameter position or name.
267 114
     * @param mixed           $value The parameter value.
268 95
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
269
     *
270
     * @return $this This QueryBuilder instance.
271 114
     */
272
    public function setParameter($key, $value, $type = null)
273 114
    {
274
        if ($type !== null) {
275
            $this->paramTypes[$key] = $type;
276
        }
277
278
        $this->params[$key] = $value;
279
280
        return $this;
281
    }
282
283
    /**
284
     * Sets a collection of query parameters for the query being constructed.
285
     *
286
     * <code>
287
     *     $qb = $conn->createQueryBuilder()
288
     *         ->select('u')
289
     *         ->from('users', 'u')
290
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
291
     *         ->setParameters(array(
292
     *             ':user_id1' => 1,
293
     *             ':user_id2' => 2
294
     *         ));
295
     * </code>
296
     *
297
     * @param mixed[]        $params The query parameters to set.
298
     * @param int[]|string[] $types  The query parameters types to set.
299
     *
300
     * @return $this This QueryBuilder instance.
301
     */
302
    public function setParameters(array $params, array $types = [])
303
    {
304
        $this->paramTypes = $types;
305
        $this->params     = $params;
306
307
        return $this;
308 19
    }
309
310 19
    /**
311
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
312
     *
313
     * @return mixed[] The currently defined query parameters indexed by parameter index or name.
314
     */
315
    public function getParameters()
316
    {
317
        return $this->params;
318
    }
319
320 57
    /**
321
     * Gets a (previously set) query parameter of the query being constructed.
322 57
     *
323
     * @param mixed $key The key (index or name) of the bound parameter.
324
     *
325
     * @return mixed The value of the bound parameter.
326
     */
327
    public function getParameter($key)
328
    {
329
        return $this->params[$key] ?? null;
330 19
    }
331
332 19
    /**
333
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
334
     *
335
     * @return int[]|string[] The currently defined query parameter types indexed by parameter index or name.
336
     */
337
    public function getParameterTypes()
338
    {
339
        return $this->paramTypes;
340
    }
341
342 76
    /**
343
     * Gets a (previously set) query parameter type of the query being constructed.
344 76
     *
345
     * @param mixed $key The key (index or name) of the bound parameter type.
346
     *
347
     * @return mixed The value of the bound parameter type.
348
     */
349
    public function getParameterType($key)
350
    {
351
        return $this->paramTypes[$key] ?? null;
352
    }
353
354 19
    /**
355
     * Sets the position of the first result to retrieve (the "offset").
356 19
     *
357 19
     * @param int $firstResult The first result to return.
358
     *
359 19
     * @return $this This QueryBuilder instance.
360
     */
361
    public function setFirstResult($firstResult)
362
    {
363
        $this->state       = self::STATE_DIRTY;
364
        $this->firstResult = $firstResult;
365
366
        return $this;
367
    }
368 19
369
    /**
370 19
     * Gets the position of the first result the query object was set to retrieve (the "offset").
371
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
372
     *
373
     * @return int The position of the first result.
374
     */
375
    public function getFirstResult()
376
    {
377
        return $this->firstResult;
378
    }
379
380 19
    /**
381
     * Sets the maximum number of results to retrieve (the "limit").
382 19
     *
383 19
     * @param int $maxResults The maximum number of results to retrieve.
384
     *
385 19
     * @return $this This QueryBuilder instance.
386
     */
387
    public function setMaxResults($maxResults)
388
    {
389
        $this->state      = self::STATE_DIRTY;
390
        $this->maxResults = $maxResults;
391
392
        return $this;
393
    }
394 19
395
    /**
396 19
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
397
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
398
     *
399
     * @return int The maximum number of results.
400
     */
401
    public function getMaxResults()
402
    {
403
        return $this->maxResults;
404
    }
405
406
    /**
407
     * Sets the flag to only retrieve distinct results
408
     *
409
     * @param bool $isDistinct Set true to add the distinct flag, set to false to remove it
410
     *
411 1102
     * @return $this This QueryBuilder instance.
412
     */
413 1102
    public function distinct($isDistinct = true)
414 1102
    {
415
        $this->state      = self::STATE_DIRTY;
416 1102
        $this->isDistinct = $isDistinct;
417 133
418
        return $this;
419
    }
420 1102
421
    /**
422 1102
     * Returns whether or not the query object is set to return only distinct results
423 950
     *
424 171
     * @return bool
425 171
     */
426
    public function isDistinct()
427 893
    {
428 228
        return $this->isDistinct;
429 228
    }
430 893
431 893
    /**
432
     * Either appends to or replaces a single, generic query part.
433 152
     *
434
     * The available parts are: 'select', 'from', 'set', 'where',
435
     * 'groupBy', 'having' and 'orderBy'.
436 950
     *
437
     * @param string $sqlPartName
438
     * @param string $sqlPart
439 1102
     * @param bool   $append
440
     *
441 1102
     * @return $this This QueryBuilder instance.
442
     */
443
    public function add($sqlPartName, $sqlPart, $append = false)
444
    {
445
        $isArray    = is_array($sqlPart);
446
        $isMultiple = is_array($this->sqlParts[$sqlPartName]);
447
448
        if ($isMultiple && ! $isArray) {
449
            $sqlPart = [$sqlPart];
450
        }
451
452
        $this->state = self::STATE_DIRTY;
453
454
        if ($append) {
455
            if ($sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set') {
456
                foreach ($sqlPart as $part) {
457
                    $this->sqlParts[$sqlPartName][] = $part;
458
                }
459 931
            } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) {
0 ignored issues
show
Bug introduced by
It seems like $sqlPart can also be of type string; however, parameter $array of key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

459
            } elseif ($isArray && is_array($sqlPart[key(/** @scrutinizer ignore-type */ $sqlPart)])) {
Loading history...
460
                $key                                  = key($sqlPart);
461 931
                $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key];
462
            } elseif ($isMultiple) {
463 931
                $this->sqlParts[$sqlPartName][] = $sqlPart;
464 19
            } else {
465
                $this->sqlParts[$sqlPartName] = $sqlPart;
466
            }
467 912
468
            return $this;
469 912
        }
470
471
        $this->sqlParts[$sqlPartName] = $sqlPart;
472
473
        return $this;
474
    }
475
476
    /**
477
     * Specifies an item that is to be returned in the query result.
478
     * Replaces any previously specified selections, if any.
479
     *
480
     * <code>
481
     *     $qb = $conn->createQueryBuilder()
482
     *         ->select('u.id', 'p.id')
483
     *         ->from('users', 'u')
484
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
485
     * </code>
486
     *
487 57
     * @param mixed $select The selection expressions.
488
     *
489 57
     * @return $this This QueryBuilder instance.
490
     */
491 57
    public function select($select = null)
492 19
    {
493
        $this->type = self::SELECT;
494
495 38
        if (empty($select)) {
496
            return $this;
497 38
        }
498
499
        $selects = is_array($select) ? $select : func_get_args();
500
501
        return $this->add('select', $selects);
0 ignored issues
show
Bug introduced by
$selects of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

501
        return $this->add('select', /** @scrutinizer ignore-type */ $selects);
Loading history...
502
    }
503
504
    /**
505
     * Adds an item that is to be returned in the query result.
506
     *
507
     * <code>
508
     *     $qb = $conn->createQueryBuilder()
509
     *         ->select('u.id')
510
     *         ->addSelect('p.id')
511
     *         ->from('users', 'u')
512
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
513
     * </code>
514
     *
515
     * @param mixed $select The selection expression.
516 76
     *
517
     * @return $this This QueryBuilder instance.
518 76
     */
519
    public function addSelect($select = null)
520 76
    {
521 19
        $this->type = self::SELECT;
522
523
        if (empty($select)) {
524 57
            return $this;
525 57
        }
526 57
527
        $selects = is_array($select) ? $select : func_get_args();
528
529
        return $this->add('select', $selects, true);
0 ignored issues
show
Bug introduced by
$selects of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

529
        return $this->add('select', /** @scrutinizer ignore-type */ $selects, true);
Loading history...
530
    }
531
532
    /**
533
     * Turns the query being built into a bulk delete query that ranges over
534
     * a certain table.
535
     *
536
     * <code>
537
     *     $qb = $conn->createQueryBuilder()
538
     *         ->delete('users', 'u')
539
     *         ->where('u.id = :user_id');
540
     *         ->setParameter(':user_id', 1);
541
     * </code>
542
     *
543
     * @param string $delete The table whose rows are subject to the deletion.
544
     * @param string $alias  The table alias used in the constructed query.
545
     *
546 76
     * @return $this This QueryBuilder instance.
547
     */
548 76
    public function delete($delete = null, $alias = null)
549
    {
550 76
        $this->type = self::DELETE;
551 19
552
        if (! $delete) {
553
            return $this;
554 57
        }
555 57
556 57
        return $this->add('from', [
0 ignored issues
show
Bug introduced by
array('table' => $delete, 'alias' => $alias) of type array<string,null|string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

556
        return $this->add('from', /** @scrutinizer ignore-type */ [
Loading history...
557
            'table' => $delete,
558
            'alias' => $alias,
559
        ]);
560
    }
561
562
    /**
563
     * Turns the query being built into a bulk update query that ranges over
564
     * a certain table
565
     *
566
     * <code>
567
     *     $qb = $conn->createQueryBuilder()
568
     *         ->update('counters', 'c')
569
     *         ->set('c.value', 'c.value + 1')
570
     *         ->where('c.id = ?');
571
     * </code>
572
     *
573
     * @param string $update The table whose rows are subject to the update.
574
     * @param string $alias  The table alias used in the constructed query.
575
     *
576
     * @return $this This QueryBuilder instance.
577
     */
578
    public function update($update = null, $alias = null)
579 95
    {
580
        $this->type = self::UPDATE;
581 95
582
        if (! $update) {
583 95
            return $this;
584 19
        }
585
586
        return $this->add('from', [
0 ignored issues
show
Bug introduced by
array('table' => $update, 'alias' => $alias) of type array<string,null|string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

586
        return $this->add('from', /** @scrutinizer ignore-type */ [
Loading history...
587 76
            'table' => $update,
588
            'alias' => $alias,
589
        ]);
590
    }
591
592
    /**
593
     * Turns the query being built into an insert query that inserts into
594
     * a certain table
595
     *
596
     * <code>
597
     *     $qb = $conn->createQueryBuilder()
598
     *         ->insert('users')
599
     *         ->values(
600
     *             array(
601
     *                 'name' => '?',
602
     *                 'password' => '?'
603
     *             )
604
     *         );
605 893
     * </code>
606
     *
607 893
     * @param string $insert The table into which the rows should be inserted.
608 893
     *
609 893
     * @return $this This QueryBuilder instance.
610 893
     */
611
    public function insert($insert = null)
612
    {
613
        $this->type = self::INSERT;
614
615
        if (! $insert) {
616
            return $this;
617
        }
618
619
        return $this->add('from', ['table' => $insert]);
0 ignored issues
show
Bug introduced by
array('table' => $insert) of type array<string,string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

619
        return $this->add('from', /** @scrutinizer ignore-type */ ['table' => $insert]);
Loading history...
620
    }
621
622
    /**
623
     * Creates and adds a query root corresponding to the table identified by the
624
     * given alias, forming a cartesian product with any existing query roots.
625
     *
626
     * <code>
627
     *     $qb = $conn->createQueryBuilder()
628
     *         ->select('u.id')
629
     *         ->from('users', 'u')
630 76
     * </code>
631
     *
632 76
     * @param string      $from  The table.
633
     * @param string|null $alias The alias of the table.
634
     *
635
     * @return $this This QueryBuilder instance.
636
     */
637
    public function from($from, $alias = null)
638
    {
639
        return $this->add('from', [
0 ignored issues
show
Bug introduced by
array('table' => $from, 'alias' => $alias) of type array<string,null|string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

639
        return $this->add('from', /** @scrutinizer ignore-type */ [
Loading history...
640
            'table' => $from,
641
            'alias' => $alias,
642
        ], true);
643
    }
644
645
    /**
646
     * Creates and adds a join to the query.
647
     *
648
     * <code>
649
     *     $qb = $conn->createQueryBuilder()
650
     *         ->select('u.name')
651
     *         ->from('users', 'u')
652 190
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
653
     * </code>
654 190
     *
655
     * @param string $fromAlias The alias that points to a from clause.
656 190
     * @param string $join      The table name to join.
657 190
     * @param string $alias     The alias of the join table.
658 190
     * @param string $condition The condition for the join.
659 190
     *
660
     * @return $this This QueryBuilder instance.
661 190
     */
662
    public function join($fromAlias, $join, $alias, $condition = null)
663
    {
664
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
665
    }
666
667
    /**
668
     * Creates and adds a join to the query.
669
     *
670
     * <code>
671
     *     $qb = $conn->createQueryBuilder()
672
     *         ->select('u.name')
673
     *         ->from('users', 'u')
674
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
675
     * </code>
676
     *
677
     * @param string $fromAlias The alias that points to a from clause.
678
     * @param string $join      The table name to join.
679
     * @param string $alias     The alias of the join table.
680
     * @param string $condition The condition for the join.
681 19
     *
682
     * @return $this This QueryBuilder instance.
683 19
     */
684
    public function innerJoin($fromAlias, $join, $alias, $condition = null)
685 19
    {
686 19
        return $this->add('join', [
0 ignored issues
show
Bug introduced by
array($fromAlias => arra...dition' => $condition)) of type array<string,array<string,null|string>> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

686
        return $this->add('join', /** @scrutinizer ignore-type */ [
Loading history...
687 19
            $fromAlias => [
688 19
                'joinType'      => 'inner',
689
                'joinTable'     => $join,
690 19
                'joinAlias'     => $alias,
691
                'joinCondition' => $condition,
692
            ],
693
        ], true);
694
    }
695
696
    /**
697
     * Creates and adds a left join to the query.
698
     *
699
     * <code>
700
     *     $qb = $conn->createQueryBuilder()
701
     *         ->select('u.name')
702
     *         ->from('users', 'u')
703
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
704
     * </code>
705
     *
706
     * @param string $fromAlias The alias that points to a from clause.
707
     * @param string $join      The table name to join.
708
     * @param string $alias     The alias of the join table.
709
     * @param string $condition The condition for the join.
710 19
     *
711
     * @return $this This QueryBuilder instance.
712 19
     */
713
    public function leftJoin($fromAlias, $join, $alias, $condition = null)
714 19
    {
715 19
        return $this->add('join', [
0 ignored issues
show
Bug introduced by
array($fromAlias => arra...dition' => $condition)) of type array<string,array<string,null|string>> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

715
        return $this->add('join', /** @scrutinizer ignore-type */ [
Loading history...
716 19
            $fromAlias => [
717 19
                'joinType'      => 'left',
718
                'joinTable'     => $join,
719 19
                'joinAlias'     => $alias,
720
                'joinCondition' => $condition,
721
            ],
722
        ], true);
723
    }
724
725
    /**
726
     * Creates and adds a right join to the query.
727
     *
728
     * <code>
729
     *     $qb = $conn->createQueryBuilder()
730
     *         ->select('u.name')
731
     *         ->from('users', 'u')
732
     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
733
     * </code>
734
     *
735
     * @param string $fromAlias The alias that points to a from clause.
736
     * @param string $join      The table name to join.
737 57
     * @param string $alias     The alias of the join table.
738
     * @param string $condition The condition for the join.
739 57
     *
740
     * @return $this This QueryBuilder instance.
741
     */
742
    public function rightJoin($fromAlias, $join, $alias, $condition = null)
743
    {
744
        return $this->add('join', [
0 ignored issues
show
Bug introduced by
array($fromAlias => arra...dition' => $condition)) of type array<string,array<string,null|string>> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

744
        return $this->add('join', /** @scrutinizer ignore-type */ [
Loading history...
745
            $fromAlias => [
746
                'joinType'      => 'right',
747
                'joinTable'     => $join,
748
                'joinAlias'     => $alias,
749
                'joinCondition' => $condition,
750
            ],
751
        ], true);
752
    }
753
754
    /**
755
     * Sets a new value for a column in a bulk update query.
756
     *
757
     * <code>
758
     *     $qb = $conn->createQueryBuilder()
759
     *         ->update('counters', 'c')
760
     *         ->set('c.value', 'c.value + 1')
761
     *         ->where('c.id = ?');
762
     * </code>
763
     *
764
     * @param string $key   The column to set.
765
     * @param string $value The value, expression, placeholder, etc.
766
     *
767
     * @return $this This QueryBuilder instance.
768 361
     */
769
    public function set($key, $value)
770 361
    {
771 342
        return $this->add('set', $key . ' = ' . $value, true);
772
    }
773
774 361
    /**
775
     * Specifies one or more restrictions to the query result.
776
     * Replaces any previously specified restrictions, if any.
777
     *
778
     * <code>
779
     *     $qb = $conn->createQueryBuilder()
780
     *         ->select('c.value')
781
     *         ->from('counters', 'c')
782
     *         ->where('c.id = ?');
783
     *
784
     *     // You can optionally programatically build and/or expressions
785
     *     $qb = $conn->createQueryBuilder();
786
     *
787
     *     $or = $qb->expr()->orx();
788
     *     $or->add($qb->expr()->eq('c.id', 1));
789
     *     $or->add($qb->expr()->eq('c.id', 2));
790
     *
791
     *     $qb->update('counters', 'c')
792
     *         ->set('c.value', 'c.value + 1')
793
     *         ->where($or);
794
     * </code>
795 114
     *
796
     * @param mixed $predicates The restriction predicates.
797 114
     *
798 114
     * @return $this This QueryBuilder instance.
799
     */
800 114
    public function where($predicates)
801 114
    {
802
        if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) {
803 19
            $predicates = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
804 19
        }
805
806
        return $this->add('where', $predicates);
807 114
    }
808
809
    /**
810
     * Adds one or more restrictions to the query results, forming a logical
811
     * conjunction with any previously specified restrictions.
812
     *
813
     * <code>
814
     *     $qb = $conn->createQueryBuilder()
815
     *         ->select('u')
816
     *         ->from('users', 'u')
817
     *         ->where('u.username LIKE ?')
818
     *         ->andWhere('u.is_active = 1');
819
     * </code>
820
     *
821
     * @see where()
822
     *
823
     * @param mixed $where The query restrictions.
824
     *
825
     * @return $this This QueryBuilder instance.
826
     */
827
    public function andWhere($where)
828 57
    {
829
        $args  = func_get_args();
830 57
        $where = $this->getQueryPart('where');
831 57
832
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) {
833 57
            $where->addMultiple($args);
834 19
        } else {
835
            array_unshift($args, $where);
836 57
            $where = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
837 57
        }
838
839
        return $this->add('where', $where, true);
840 57
    }
841
842
    /**
843
     * Adds one or more restrictions to the query results, forming a logical
844
     * disjunction with any previously specified restrictions.
845
     *
846
     * <code>
847
     *     $qb = $em->createQueryBuilder()
848
     *         ->select('u.name')
849
     *         ->from('users', 'u')
850
     *         ->where('u.id = 1')
851
     *         ->orWhere('u.id = 2');
852
     * </code>
853
     *
854
     * @see where()
855
     *
856
     * @param mixed $where The WHERE statement.
857
     *
858 190
     * @return $this This QueryBuilder instance.
859
     */
860 190
    public function orWhere($where)
861 19
    {
862
        $args  = func_get_args();
863
        $where = $this->getQueryPart('where');
864 171
865
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) {
866 171
            $where->addMultiple($args);
867
        } else {
868
            array_unshift($args, $where);
869
            $where = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
870
        }
871
872
        return $this->add('where', $where, true);
873
    }
874
875
    /**
876
     * Specifies a grouping over the results of the query.
877
     * Replaces any previously specified groupings, if any.
878
     *
879
     * <code>
880
     *     $qb = $conn->createQueryBuilder()
881
     *         ->select('u.name')
882
     *         ->from('users', 'u')
883
     *         ->groupBy('u.id');
884
     * </code>
885 57
     *
886
     * @param mixed $groupBy The grouping expression.
887 57
     *
888 19
     * @return $this This QueryBuilder instance.
889
     */
890
    public function groupBy($groupBy)
891 38
    {
892
        if (empty($groupBy)) {
893 38
            return $this;
894
        }
895
896
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
897
898
        return $this->add('groupBy', $groupBy, false);
0 ignored issues
show
Bug introduced by
$groupBy of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

898
        return $this->add('groupBy', /** @scrutinizer ignore-type */ $groupBy, false);
Loading history...
899
    }
900
901
902
    /**
903
     * Adds a grouping expression to the query.
904
     *
905
     * <code>
906
     *     $qb = $conn->createQueryBuilder()
907
     *         ->select('u.name')
908
     *         ->from('users', 'u')
909
     *         ->groupBy('u.lastLogin');
910
     *         ->addGroupBy('u.createdAt')
911
     * </code>
912
     *
913
     * @param mixed $groupBy The grouping expression.
914
     *
915 38
     * @return $this This QueryBuilder instance.
916
     */
917 38
    public function addGroupBy($groupBy)
918
    {
919 38
        if (empty($groupBy)) {
920
            return $this;
921
        }
922
923
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
924
925
        return $this->add('groupBy', $groupBy, true);
0 ignored issues
show
Bug introduced by
$groupBy of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

925
        return $this->add('groupBy', /** @scrutinizer ignore-type */ $groupBy, true);
Loading history...
926
    }
927
928
    /**
929
     * Sets a value for a column in an insert query.
930
     *
931
     * <code>
932
     *     $qb = $conn->createQueryBuilder()
933
     *         ->insert('users')
934
     *         ->values(
935
     *             array(
936
     *                 'name' => '?'
937
     *             )
938
     *         )
939
     *         ->setValue('password', '?');
940
     * </code>
941 57
     *
942
     * @param string $column The column into which the value should be inserted.
943 57
     * @param string $value  The value that should be inserted into the column.
944
     *
945
     * @return $this This QueryBuilder instance.
946
     */
947
    public function setValue($column, $value)
948
    {
949
        $this->sqlParts['values'][$column] = $value;
950
951
        return $this;
952
    }
953
954 76
    /**
955
     * Specifies values for an insert query indexed by column names.
956 76
     * Replaces any previous values, if any.
957 76
     *
958
     * <code>
959
     *     $qb = $conn->createQueryBuilder()
960 76
     *         ->insert('users')
961
     *         ->values(
962
     *             array(
963
     *                 'name' => '?',
964
     *                 'password' => '?'
965
     *             )
966
     *         );
967
     * </code>
968
     *
969
     * @param mixed[] $values The values to specify for the insert query indexed by column names.
970
     *
971 57
     * @return $this This QueryBuilder instance.
972
     */
973 57
    public function values(array $values)
974 57
    {
975
        return $this->add('values', $values);
0 ignored issues
show
Bug introduced by
$values of type array<mixed,mixed> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

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

975
        return $this->add('values', /** @scrutinizer ignore-type */ $values);
Loading history...
976 57
    }
977 19
978
    /**
979 38
     * Specifies a restriction over the groups of the query.
980 38
     * Replaces any previous having restrictions, if any.
981
     *
982
     * @param mixed $having The restriction over the groups.
983 57
     *
984
     * @return $this This QueryBuilder instance.
985
     */
986
    public function having($having)
987
    {
988
        if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) {
989
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
990
        }
991
992
        return $this->add('having', $having);
993
    }
994 57
995
    /**
996 57
     * Adds a restriction over the groups of the query, forming a logical
997 57
     * conjunction with any existing having restrictions.
998
     *
999 57
     * @param mixed $having The restriction to append.
1000 19
     *
1001
     * @return $this This QueryBuilder instance.
1002 57
     */
1003 57
    public function andHaving($having)
1004
    {
1005
        $args   = func_get_args();
1006 57
        $having = $this->getQueryPart('having');
1007
1008
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) {
1009
            $having->addMultiple($args);
1010
        } else {
1011
            array_unshift($args, $having);
1012
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
1013
        }
1014
1015
        return $this->add('having', $having);
1016
    }
1017
1018 57
    /**
1019
     * Adds a restriction over the groups of the query, forming a logical
1020 57
     * disjunction with any existing having restrictions.
1021
     *
1022
     * @param mixed $having The restriction to add.
1023
     *
1024
     * @return $this This QueryBuilder instance.
1025
     */
1026
    public function orHaving($having)
1027
    {
1028
        $args   = func_get_args();
1029
        $having = $this->getQueryPart('having');
1030
1031 38
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) {
1032
            $having->addMultiple($args);
1033 38
        } else {
1034
            array_unshift($args, $having);
1035
            $having = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
1036
        }
1037
1038
        return $this->add('having', $having);
1039
    }
1040
1041
    /**
1042
     * Specifies an ordering for the query results.
1043 247
     * Replaces any previously specified orderings, if any.
1044
     *
1045 247
     * @param string $sort  The ordering expression.
1046
     * @param string $order The ordering direction.
1047
     *
1048
     * @return $this This QueryBuilder instance.
1049
     */
1050
    public function orderBy($sort, $order = null)
1051
    {
1052
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false);
1053 19
    }
1054
1055 19
    /**
1056
     * Adds an ordering to the query results.
1057
     *
1058
     * @param string $sort  The ordering expression.
1059
     * @param string $order The ordering direction.
1060
     *
1061
     * @return $this This QueryBuilder instance.
1062
     */
1063
    public function addOrderBy($sort, $order = null)
1064
    {
1065 19
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true);
1066
    }
1067 19
1068
    /**
1069
     * Gets a query part by its name.
1070
     *
1071 19
     * @param string $queryPartName
1072 19
     *
1073
     * @return mixed
1074
     */
1075 19
    public function getQueryPart($queryPartName)
1076
    {
1077
        return $this->sqlParts[$queryPartName];
1078
    }
1079
1080
    /**
1081
     * Gets all query parts.
1082
     *
1083
     * @return mixed[]
1084
     */
1085 38
    public function getQueryParts()
1086
    {
1087 38
        return $this->sqlParts;
1088 38
    }
1089
1090 38
    /**
1091
     * Resets SQL parts.
1092 38
     *
1093
     * @param string[]|null $queryPartNames
1094
     *
1095
     * @return $this This QueryBuilder instance.
1096
     */
1097
    public function resetQueryParts($queryPartNames = null)
1098
    {
1099
        if ($queryPartNames === null) {
1100 874
            $queryPartNames = array_keys($this->sqlParts);
1101
        }
1102 874
1103
        foreach ($queryPartNames as $queryPartName) {
1104 874
            $this->resetQueryPart($queryPartName);
1105 836
        }
1106 836
1107 836
        return $this;
1108 836
    }
1109
1110 836
    /**
1111
     * Resets a single SQL part.
1112
     *
1113
     * @param string $queryPartName
1114
     *
1115
     * @return $this This QueryBuilder instance.
1116
     */
1117
    public function resetQueryPart($queryPartName)
1118 836
    {
1119
        $this->sqlParts[$queryPartName] = is_array($this->sqlParts[$queryPartName])
1120
            ? [] : null;
1121
1122
        $this->state = self::STATE_DIRTY;
1123
1124 855
        return $this;
1125
    }
1126 855
1127 855
    /**
1128
     * @return string
1129
     *
1130 855
     * @throws QueryException
1131 855
     */
1132 114
    private function getSQLForSelect()
1133 114
    {
1134
        $query = 'SELECT' . ($this->isDistinct ? ' DISTINCT ' : ' ') . implode(', ', $this->sqlParts['select']);
1135 760
1136 760
        $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '')
1137
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
1138
            . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '')
1139 855
            . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '')
1140
            . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');
1141 855
1142
        if ($this->isLimitQuery()) {
1143
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1144 836
                $query,
1145
                $this->maxResults,
1146 817
                $this->firstResult
1147
            );
1148
        }
1149
1150
        return $query;
1151
    }
1152
1153
    /**
1154 836
     * @return string[]
1155
     */
1156 836
    private function getFromClauses()
1157 209
    {
1158 209
        $fromClauses  = [];
1159
        $knownAliases = [];
1160
1161 817
        // Loop through all FROM clauses
1162
        foreach ($this->sqlParts['from'] as $from) {
1163
            if ($from['alias'] === null) {
1164
                $tableSql       = $from['table'];
1165
                $tableReference = $from['table'];
1166 836
            } else {
1167
                $tableSql       = $from['table'] . ' ' . $from['alias'];
1168 836
                $tableReference = $from['alias'];
1169
            }
1170
1171
            $knownAliases[$tableReference] = true;
1172
1173
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1174
        }
1175
1176 76
        $this->verifyAllAliasesAreKnown($knownAliases);
1177
1178 76
        return $fromClauses;
1179 76
    }
1180 76
1181
    /**
1182
     * @param string[] $knownAliases
1183
     *
1184
     * @throws QueryException
1185
     */
1186
    private function verifyAllAliasesAreKnown(array $knownAliases)
1187
    {
1188 57
        foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
1189
            if (! isset($knownAliases[$fromAlias])) {
1190 57
                throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases));
1191 57
            }
1192 57
        }
1193 57
    }
1194
1195
    /**
1196
     * @return bool
1197
     */
1198
    private function isLimitQuery()
1199
    {
1200
        return $this->maxResults !== null || $this->firstResult !== null;
1201 57
    }
1202
1203 57
    /**
1204 57
     * Converts this instance into an INSERT string in SQL.
1205
     *
1206
     * @return string
1207
     */
1208
    private function getSQLForInsert()
1209
    {
1210
        return 'INSERT INTO ' . $this->sqlParts['from']['table'] .
1211
        ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' .
1212
        ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')';
1213 931
    }
1214
1215 931
    /**
1216
     * Converts this instance into an UPDATE string in SQL.
1217
     *
1218
     * @return string
1219
     */
1220
    private function getSQLForUpdate()
1221
    {
1222
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1223
        return 'UPDATE ' . $table
1224
            . ' SET ' . implode(', ', $this->sqlParts['set'])
1225
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1226
    }
1227
1228
    /**
1229
     * Converts this instance into a DELETE string in SQL.
1230
     *
1231
     * @return string
1232
     */
1233
    private function getSQLForDelete()
1234
    {
1235
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1236
        return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1237
    }
1238
1239
    /**
1240
     * Gets a string representation of this QueryBuilder which corresponds to
1241
     * the final SQL query being constructed.
1242
     *
1243
     * @return string The string representation of this QueryBuilder.
1244
     */
1245
    public function __toString()
1246 38
    {
1247
        return $this->getSQL();
1248 38
    }
1249 19
1250 19
    /**
1251
     * Creates a new named parameter and bind the value $value to it.
1252 38
     *
1253
     * This method provides a shortcut for PDOStatement::bindValue
1254 38
     * when using prepared statements.
1255
     *
1256
     * The parameter $value specifies the value that you want to bind. If
1257
     * $placeholder is not provided bindValue() will automatically create a
1258
     * placeholder for you. An automatic placeholder will be of the name
1259
     * ':dcValue1', ':dcValue2' etc.
1260
     *
1261
     * For more information see {@link http://php.net/pdostatement-bindparam}
1262
     *
1263
     * Example:
1264
     * <code>
1265
     * $value = 2;
1266
     * $q->eq( 'id', $q->bindValue( $value ) );
1267
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1268
     * </code>
1269
     *
1270
     * @link http://www.zetacomponents.org
1271
     *
1272
     * @param mixed  $value
1273
     * @param mixed  $type
1274
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1275
     *
1276
     * @return string the placeholder name used.
1277
     */
1278
    public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null)
1279 19
    {
1280
        if ($placeHolder === null) {
1281 19
            $this->boundCounter++;
1282 19
            $placeHolder = ':dcValue' . $this->boundCounter;
1283
        }
1284 19
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1285
1286
        return $placeHolder;
1287
    }
1288
1289
    /**
1290
     * Creates a new positional parameter and bind the given value to it.
1291
     *
1292
     * Attention: If you are using positional parameters with the query builder you have
1293
     * to be very careful to bind all parameters in the order they appear in the SQL
1294
     * statement , otherwise they get bound in the wrong order which can lead to serious
1295 855
     * bugs in your code.
1296
     *
1297 855
     * Example:
1298
     * <code>
1299 855
     *  $qb = $conn->createQueryBuilder();
1300 228
     *  $qb->select('u.*')
1301 228
     *     ->from('users', 'u')
1302 19
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1303
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1304 209
     * </code>
1305 209
     *
1306 209
     * @param mixed $value
1307 209
     * @param int   $type
1308
     *
1309
     * @return string
1310 209
     */
1311 209
    public function createPositionalParameter($value, $type = ParameterType::STRING)
1312
    {
1313
        $this->boundCounter++;
1314
        $this->setParameter($this->boundCounter, $value, $type);
1315 836
1316
        return '?';
1317
    }
1318
1319
    /**
1320
     * @param string   $fromAlias
1321
     * @param string[] $knownAliases
1322
     *
1323 19
     * @return string
1324
     *
1325 19
     * @throws QueryException
1326 19
     */
1327 19
    private function getSQLForJoins($fromAlias, array &$knownAliases)
1328 19
    {
1329 19
        $sql = '';
1330
1331
        if (isset($this->sqlParts['join'][$fromAlias])) {
1332 19
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1333
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
1334 19
                    throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases));
1335 19
                }
1336
                $sql                             .= ' ' . strtoupper($join['joinType'])
1337
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
1338
                    . ' ON ' . ((string) $join['joinCondition']);
1339 19
                $knownAliases[$join['joinAlias']] = true;
1340 19
            }
1341
1342
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1343
                $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases);
1344 19
            }
1345
        }
1346 19
1347
        return $sql;
1348
    }
1349
1350
    /**
1351
     * Deep clone of all expression objects in the SQL parts.
1352
     *
1353
     * @return void
1354
     */
1355
    public function __clone()
1356
    {
1357
        foreach ($this->sqlParts as $part => $elements) {
1358
            if (is_array($this->sqlParts[$part])) {
1359
                foreach ($this->sqlParts[$part] as $idx => $element) {
1360
                    if (! is_object($element)) {
1361
                        continue;
1362
                    }
1363
1364
                    $this->sqlParts[$part][$idx] = clone $element;
1365
                }
1366
            } elseif (is_object($elements)) {
1367
                $this->sqlParts[$part] = clone $elements;
1368
            }
1369
        }
1370
1371
        foreach ($this->params as $name => $param) {
1372
            if (! is_object($param)) {
1373
                continue;
1374
            }
1375
1376
            $this->params[$name] = clone $param;
1377
        }
1378
    }
1379
}
1380