Completed
Pull Request — master (#3833)
by Benjamin
17:15
created

QueryBuilder::set()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Query;
6
7
use Doctrine\DBAL\Connection;
8
use Doctrine\DBAL\Driver\Statement;
9
use Doctrine\DBAL\ParameterType;
10
use Doctrine\DBAL\Query\Exception\NonUniqueAlias;
11
use Doctrine\DBAL\Query\Exception\UnknownAlias;
12
use Doctrine\DBAL\Query\Expression\CompositeExpression;
13
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
14
use function array_key_exists;
15
use function array_keys;
16
use function array_unshift;
17
use function func_get_args;
18
use function func_num_args;
19
use function implode;
20
use function is_array;
21
use function is_object;
22
use function key;
23
use function substr;
24
25
/**
26
 * QueryBuilder class is responsible to dynamically create SQL queries.
27
 *
28
 * Important: Verify that every feature you use will work with your database vendor.
29
 * SQL Query Builder does not attempt to validate the generated SQL at all.
30
 *
31
 * The query builder does no validation whatsoever if certain features even work with the
32
 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
33
 * even if some vendors such as MySQL support it.
34
 */
35
class QueryBuilder
36
{
37
    /*
38
     * The query types.
39
     */
40
    public const SELECT = 0;
41
    public const DELETE = 1;
42
    public const UPDATE = 2;
43
    public const INSERT = 3;
44
45
    /*
46
     * The builder states.
47
     */
48
    public const STATE_DIRTY = 0;
49
    public const STATE_CLEAN = 1;
50
51
    /**
52
     * The DBAL Connection.
53
     *
54
     * @var Connection
55
     */
56
    private $connection;
57
58
    /*
59
     * The default values of SQL parts collection
60
     */
61
    private const SQL_PARTS_DEFAULTS = [
62
        'select'   => [],
63
        'distinct' => false,
64
        'from'     => [],
65
        'table'    => null,
66
        'join'     => [],
67
        'set'      => [],
68
        'where'    => null,
69
        'groupBy'  => [],
70
        'having'   => null,
71
        'orderBy'  => [],
72
        'values'   => [],
73
    ];
74
75
    /**
76
     * The array of SQL parts collected.
77
     *
78
     * @var array<string, mixed>
79
     */
80
    private $sqlParts = self::SQL_PARTS_DEFAULTS;
81
82
    /**
83
     * The complete SQL string for this query.
84
     *
85
     * @var string
86
     */
87
    private $sql;
88
89
    /**
90
     * The query parameters.
91
     *
92
     * @var array<int, mixed>|array<string, mixed>
93
     */
94
    private $params = [];
95
96
    /**
97
     * The parameter type map of this query.
98
     *
99
     * @var array<int, mixed>|array<string, mixed>
100
     */
101
    private $paramTypes = [];
102
103
    /**
104
     * The type of query this is. Can be select, update or delete.
105
     *
106
     * @var int
107
     */
108
    private $type = self::SELECT;
109
110
    /**
111
     * The state of the query object. Can be dirty or clean.
112
     *
113
     * @var int
114
     */
115
    private $state = self::STATE_CLEAN;
116
117
    /**
118
     * The index of the first result to retrieve.
119
     *
120
     * @var int
121
     */
122
    private $firstResult = 0;
123
124
    /**
125
     * The maximum number of results to retrieve.
126
     *
127
     * @var int|null
128
     */
129
    private $maxResults;
130
131
    /**
132
     * The counter of bound parameters used with {@see bindValue).
133
     *
134
     * @var int
135
     */
136
    private $boundCounter = 0;
137
138
    /**
139
     * Initializes a new <tt>QueryBuilder</tt>.
140
     *
141
     * @param Connection $connection The DBAL Connection.
142
     */
143 1638
    public function __construct(Connection $connection)
144
    {
145 1638
        $this->connection = $connection;
146 1638
    }
147
148
    /**
149
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
150
     * This producer method is intended for convenient inline usage. Example:
151
     *
152
     * <code>
153
     *     $qb = $conn->createQueryBuilder()
154
     *         ->select('u')
155
     *         ->from('users', 'u')
156
     *         ->where($qb->expr()->eq('u.id', 1));
157
     * </code>
158
     *
159
     * For more complex expression construction, consider storing the expression
160
     * builder object in a local variable.
161
     */
162 728
    public function expr() : ExpressionBuilder
163
    {
164 728
        return $this->connection->getExpressionBuilder();
165
    }
166
167
    /**
168
     * Gets the type of the currently built query.
169
     */
170 208
    public function getType() : int
171
    {
172 208
        return $this->type;
173
    }
174
175
    /**
176
     * Gets the associated DBAL Connection for this query builder.
177
     */
178 26
    public function getConnection() : Connection
179
    {
180 26
        return $this->connection;
181
    }
182
183
    /**
184
     * Gets the state of this query builder instance.
185
     *
186
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
187
     */
188 78
    public function getState() : int
189
    {
190 78
        return $this->state;
191
    }
192
193
    /**
194
     * Executes this query using the bound parameters and their types.
195
     *
196
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
197
     * for insert, update and delete statements.
198
     *
199
     * @return Statement|int
200
     */
201
    public function execute()
202
    {
203
        if ($this->type === self::SELECT) {
204
            return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
205
        }
206
207
        return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes);
208
    }
209
210
    /**
211
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
212
     *
213
     * <code>
214
     *     $qb = $em->createQueryBuilder()
215
     *         ->select('u')
216
     *         ->from('User', 'u')
217
     *     echo $qb->getSQL(); // SELECT u FROM User u
218
     * </code>
219
     *
220
     * @return string The SQL query string.
221
     */
222 1456
    public function getSQL() : string
223
    {
224 1456
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
225 26
            return $this->sql;
226
        }
227
228 1456
        switch ($this->type) {
229 1456
            case self::INSERT:
230 104
                $sql = $this->getSQLForInsert();
231 104
                break;
232 1352
            case self::DELETE:
233 52
                $sql = $this->getSQLForDelete();
234 52
                break;
235
236 1300
            case self::UPDATE:
237 52
                $sql = $this->getSQLForUpdate();
238 52
                break;
239
240 1248
            case self::SELECT:
241
            default:
242 1248
                $sql = $this->getSQLForSelect();
243 1196
                break;
244
        }
245
246 1404
        $this->state = self::STATE_CLEAN;
247 1404
        $this->sql   = $sql;
248
249 1404
        return $sql;
250
    }
251
252
    /**
253
     * Sets a query parameter for the query being constructed.
254
     *
255
     * <code>
256
     *     $qb = $conn->createQueryBuilder()
257
     *         ->select('u')
258
     *         ->from('users', 'u')
259
     *         ->where('u.id = :user_id')
260
     *         ->setParameter(':user_id', 1);
261
     * </code>
262
     *
263
     * @param string|int      $key   The parameter position or name.
264
     * @param mixed           $value The parameter value.
265
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
266
     *
267
     * @return $this This QueryBuilder instance.
268
     */
269 156
    public function setParameter($key, $value, $type = null) : self
270
    {
271 156
        if ($type !== null) {
272 130
            $this->paramTypes[$key] = $type;
273
        }
274
275 156
        $this->params[$key] = $value;
276
277 156
        return $this;
278
    }
279
280
    /**
281
     * Sets a collection of query parameters for the query being constructed.
282
     *
283
     * <code>
284
     *     $qb = $conn->createQueryBuilder()
285
     *         ->select('u')
286
     *         ->from('users', 'u')
287
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
288
     *         ->setParameters(array(
289
     *             ':user_id1' => 1,
290
     *             ':user_id2' => 2
291
     *         ));
292
     * </code>
293
     *
294
     * @param array<int, mixed>|array<string, mixed> $params The query parameters to set.
295
     * @param array<int, mixed>|array<string, mixed> $types  The query parameters types to set.
296
     *
297
     * @return $this This QueryBuilder instance.
298
     */
299
    public function setParameters(array $params, array $types = []) : self
300
    {
301
        $this->paramTypes = $types;
302
        $this->params     = $params;
303
304
        return $this;
305
    }
306
307
    /**
308
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
309
     *
310
     * @return array<string|int, mixed> The currently defined query parameters indexed by parameter index or name.
311
     */
312 26
    public function getParameters() : array
313
    {
314 26
        return $this->params;
315
    }
316
317
    /**
318
     * Gets a (previously set) query parameter of the query being constructed.
319
     *
320
     * @param string|int $key The key (index or name) of the bound parameter.
321
     *
322
     * @return mixed The value of the bound parameter.
323
     */
324 78
    public function getParameter($key)
325
    {
326 78
        return $this->params[$key] ?? null;
327
    }
328
329
    /**
330
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
331
     *
332
     * @return array<string|int, mixed> The currently defined query parameter types indexed by parameter index or name.
333
     */
334 26
    public function getParameterTypes() : array
335
    {
336 26
        return $this->paramTypes;
337
    }
338
339
    /**
340
     * Gets a (previously set) query parameter type of the query being constructed.
341
     *
342
     * @param string|int $key The key (index or name) of the bound parameter type.
343
     *
344
     * @return mixed The value of the bound parameter type.
345
     */
346 104
    public function getParameterType($key)
347
    {
348 104
        return $this->paramTypes[$key] ?? null;
349
    }
350
351
    /**
352
     * Sets the position of the first result to retrieve (the "offset").
353
     *
354
     * @param int $firstResult The first result to return.
355
     *
356
     * @return $this This QueryBuilder instance.
357
     */
358 26
    public function setFirstResult(int $firstResult) : self
359
    {
360 26
        $this->state       = self::STATE_DIRTY;
361 26
        $this->firstResult = $firstResult;
362
363 26
        return $this;
364
    }
365
366
    /**
367
     * Gets the position of the first result the query object was set to retrieve (the "offset").
368
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
369
     *
370
     * @return int The position of the first result.
371
     */
372 26
    public function getFirstResult() : int
373
    {
374 26
        return $this->firstResult;
375
    }
376
377
    /**
378
     * Sets the maximum number of results to retrieve (the "limit").
379
     *
380
     * @param int $maxResults The maximum number of results to retrieve.
381
     *
382
     * @return $this This QueryBuilder instance.
383
     */
384 26
    public function setMaxResults(int $maxResults) : self
385
    {
386 26
        $this->state      = self::STATE_DIRTY;
387 26
        $this->maxResults = $maxResults;
388
389 26
        return $this;
390
    }
391
392
    /**
393
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
394
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
395
     *
396
     * @return int|null The maximum number of results.
397
     */
398 26
    public function getMaxResults() : ?int
399
    {
400 26
        return $this->maxResults;
401
    }
402
403
    /**
404
     * Either appends to or replaces a single, generic query part.
405
     *
406
     * The available parts are: 'select', 'from', 'set', 'where',
407
     * 'groupBy', 'having' and 'orderBy'.
408
     *
409
     * @param mixed $sqlPart
410
     *
411
     * @return $this This QueryBuilder instance.
412
     */
413 1508
    public function add(string $sqlPartName, $sqlPart, bool $append = false) : self
414
    {
415 1508
        $isArray    = is_array($sqlPart);
416 1508
        $isMultiple = is_array($this->sqlParts[$sqlPartName]);
417
418 1508
        if ($isMultiple && ! $isArray) {
419 1326
            $sqlPart = [$sqlPart];
420
        }
421
422 1508
        $this->state = self::STATE_DIRTY;
423
424 1508
        if ($append) {
425 1326
            if ($sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set' || $sqlPartName === 'from') {
426 1326
                foreach ($sqlPart as $part) {
427 1326
                    $this->sqlParts[$sqlPartName][] = $part;
428
                }
429 442
            } elseif ($isArray && (is_array($sqlPart[key($sqlPart)]) || is_object($sqlPart[key($sqlPart)]))) {
430 312
                $key                                  = key($sqlPart);
431 312
                $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key];
432 208
            } elseif ($isMultiple) {
433
                $this->sqlParts[$sqlPartName][] = $sqlPart;
434
            } else {
435 208
                $this->sqlParts[$sqlPartName] = $sqlPart;
436
            }
437
438 1326
            return $this;
439
        }
440
441 1508
        $this->sqlParts[$sqlPartName] = $sqlPart;
442
443 1508
        return $this;
444
    }
445
446
    /**
447
     * Specifies an item that is to be returned in the query result.
448
     * Replaces any previously specified selections, if any.
449
     *
450
     * <code>
451
     *     $qb = $conn->createQueryBuilder()
452
     *         ->select('u.id', 'p.id')
453
     *         ->from('users', 'u')
454
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
455
     * </code>
456
     *
457
     * @param mixed $select The selection expressions.
458
     *
459
     * @return $this This QueryBuilder instance.
460
     */
461 1326
    public function select($select = null) : self
462
    {
463 1326
        $this->type = self::SELECT;
464
465 1326
        if (empty($select)) {
466 26
            return $this;
467
        }
468
469 1300
        $selects = is_array($select) ? $select : func_get_args();
470
471 1300
        return $this->add('select', $selects);
472
    }
473
474
    /**
475
     * Adds DISTINCT to the query.
476
     *
477
     * <code>
478
     *     $qb = $conn->createQueryBuilder()
479
     *         ->select('u.id')
480
     *         ->distinct()
481
     *         ->from('users', 'u')
482
     * </code>
483
     *
484
     * @return $this This QueryBuilder instance.
485
     */
486 26
    public function distinct() : self
487
    {
488 26
        $this->sqlParts['distinct'] = true;
489
490 26
        return $this;
491
    }
492
493
    /**
494
     * Adds an item that is to be returned in the query result.
495
     *
496
     * <code>
497
     *     $qb = $conn->createQueryBuilder()
498
     *         ->select('u.id')
499
     *         ->addSelect('p.id')
500
     *         ->from('users', 'u')
501
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
502
     * </code>
503
     *
504
     * @param mixed $select The selection expression.
505
     *
506
     * @return $this This QueryBuilder instance.
507
     */
508 78
    public function addSelect($select = null) : self
509
    {
510 78
        $this->type = self::SELECT;
511
512 78
        if (empty($select)) {
513 26
            return $this;
514
        }
515
516 52
        $selects = is_array($select) ? $select : func_get_args();
517
518 52
        return $this->add('select', $selects, true);
519
    }
520
521
    /**
522
     * Turns the query being built into a bulk delete query that ranges over
523
     * a certain table.
524
     *
525
     * <code>
526
     *     $qb = $conn->createQueryBuilder()
527
     *         ->delete('users', 'u')
528
     *         ->where('u.id = :user_id')
529
     *         ->setParameter(':user_id', 1);
530
     * </code>
531
     *
532
     * @param string $table The table whose rows are subject to the deletion.
533
     *
534
     * @return $this This QueryBuilder instance.
535
     */
536 52
    public function delete(string $table) : self
537
    {
538 52
        $this->type = self::DELETE;
539
540 52
        return $this->add('table', $table);
541
    }
542
543
    /**
544
     * Turns the query being built into a bulk update query that ranges over
545
     * a certain table
546
     *
547
     * <code>
548
     *     $qb = $conn->createQueryBuilder()
549
     *         ->update('counters', 'c')
550
     *         ->set('c.value', 'c.value + 1')
551
     *         ->where('c.id = ?');
552
     * </code>
553
     *
554
     * @param string $table The table whose rows are subject to the update.
555
     *
556
     * @return $this This QueryBuilder instance.
557
     */
558 52
    public function update(string $table) : self
559
    {
560 52
        $this->type = self::UPDATE;
561
562 52
        return $this->add('table', $table);
563
    }
564
565
    /**
566
     * Turns the query being built into an insert query that inserts into
567
     * a certain table
568
     *
569
     * <code>
570
     *     $qb = $conn->createQueryBuilder()
571
     *         ->insert('users')
572
     *         ->values(
573
     *             array(
574
     *                 'name' => '?',
575
     *                 'password' => '?'
576
     *             )
577
     *         );
578
     * </code>
579
     *
580
     * @param string $table The table into which the rows should be inserted.
581
     *
582
     * @return $this This QueryBuilder instance.
583
     */
584 104
    public function insert(string $table) : self
585
    {
586 104
        $this->type = self::INSERT;
587
588 104
        return $this->add('table', $table);
589
    }
590
591
    /**
592
     * Creates and adds a query root corresponding to the table identified by the
593
     * given alias, forming a cartesian product with any existing query roots.
594
     *
595
     * <code>
596
     *     $qb = $conn->createQueryBuilder()
597
     *         ->select('u.id')
598
     *         ->from('users', 'u')
599
     * </code>
600
     *
601
     * @param string      $from  The table.
602
     * @param string|null $alias The alias of the table.
603
     *
604
     * @return $this This QueryBuilder instance.
605
     */
606 1274
    public function from(string $from, ?string $alias = null)
607
    {
608 1274
        return $this->add('from', new From($from, $alias), true);
609
    }
610
611
    /**
612
     * Creates and adds a join to the query.
613
     *
614
     * <code>
615
     *     $qb = $conn->createQueryBuilder()
616
     *         ->select('u.name')
617
     *         ->from('users', 'u')
618
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
619
     * </code>
620
     *
621
     * @param string $fromAlias The alias that points to a from clause.
622
     * @param string $join      The table name to join.
623
     * @param string $alias     The alias of the join table.
624
     * @param string $condition The condition for the join.
625
     *
626
     * @return $this This QueryBuilder instance.
627
     */
628 104
    public function join(string $fromAlias, string $join, string $alias, ?string $condition = null)
629
    {
630 104
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
631
    }
632
633
    /**
634
     * Creates and adds a join to the query.
635
     *
636
     * <code>
637
     *     $qb = $conn->createQueryBuilder()
638
     *         ->select('u.name')
639
     *         ->from('users', 'u')
640
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
641
     * </code>
642
     *
643
     * @param string $fromAlias The alias that points to a from clause.
644
     * @param string $join      The table name to join.
645
     * @param string $alias     The alias of the join table.
646
     * @param string $condition The condition for the join.
647
     *
648
     * @return $this This QueryBuilder instance.
649
     */
650 260
    public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null)
651
    {
652 260
        return $this->add('join', [
653 260
            $fromAlias => Join::inner($join, $alias, $condition),
654 260
        ], true);
655
    }
656
657
    /**
658
     * Creates and adds a left join to the query.
659
     *
660
     * <code>
661
     *     $qb = $conn->createQueryBuilder()
662
     *         ->select('u.name')
663
     *         ->from('users', 'u')
664
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
665
     * </code>
666
     *
667
     * @param string $fromAlias The alias that points to a from clause.
668
     * @param string $join      The table name to join.
669
     * @param string $alias     The alias of the join table.
670
     * @param string $condition The condition for the join.
671
     *
672
     * @return $this This QueryBuilder instance.
673
     */
674 26
    public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null)
675
    {
676 26
        return $this->add('join', [
677 26
            $fromAlias => Join::left($join, $alias, $condition),
678 26
        ], true);
679
    }
680
681
    /**
682
     * Creates and adds a right join to the query.
683
     *
684
     * <code>
685
     *     $qb = $conn->createQueryBuilder()
686
     *         ->select('u.name')
687
     *         ->from('users', 'u')
688
     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
689
     * </code>
690
     *
691
     * @param string $fromAlias The alias that points to a from clause.
692
     * @param string $join      The table name to join.
693
     * @param string $alias     The alias of the join table.
694
     * @param string $condition The condition for the join.
695
     *
696
     * @return $this This QueryBuilder instance.
697
     */
698 26
    public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null)
699
    {
700 26
        return $this->add('join', [
701 26
            $fromAlias => Join::right($join, $alias, $condition),
702 26
        ], true);
703
    }
704
705
    /**
706
     * Sets a new value for a column in a bulk update query.
707
     *
708
     * <code>
709
     *     $qb = $conn->createQueryBuilder()
710
     *         ->update('counters', 'c')
711
     *         ->set('c.value', 'c.value + 1')
712
     *         ->where('c.id = ?');
713
     * </code>
714
     *
715
     * @param string $key   The column to set.
716
     * @param string $value The value, expression, placeholder, etc.
717
     *
718
     * @return $this This QueryBuilder instance.
719
     */
720 52
    public function set(string $key, string $value)
721
    {
722 52
        return $this->add('set', $key . ' = ' . $value, true);
723
    }
724
725
    /**
726
     * Specifies one or more restrictions to the query result.
727
     * Replaces any previously specified restrictions, if any.
728
     *
729
     * <code>
730
     *     $qb = $conn->createQueryBuilder()
731
     *         ->select('c.value')
732
     *         ->from('counters', 'c')
733
     *         ->where('c.id = ?');
734
     *
735
     *     // You can optionally programmatically build and/or expressions
736
     *     $qb = $conn->createQueryBuilder();
737
     *
738
     *     $or = $qb->expr()->orx();
739
     *     $or->add($qb->expr()->eq('c.id', 1));
740
     *     $or->add($qb->expr()->eq('c.id', 2));
741
     *
742
     *     $qb->update('counters', 'c')
743
     *         ->set('c.value', 'c.value + 1')
744
     *         ->where($or);
745
     * </code>
746
     *
747
     * @param mixed $predicates The restriction predicates.
748
     *
749
     * @return $this This QueryBuilder instance.
750
     */
751 494
    public function where($predicates)
752
    {
753 494
        if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) {
754 468
            $predicates = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
755
        }
756
757 494
        return $this->add('where', $predicates);
758
    }
759
760
    /**
761
     * Adds one or more restrictions to the query results, forming a logical
762
     * conjunction with any previously specified restrictions.
763
     *
764
     * <code>
765
     *     $qb = $conn->createQueryBuilder()
766
     *         ->select('u')
767
     *         ->from('users', 'u')
768
     *         ->where('u.username LIKE ?')
769
     *         ->andWhere('u.is_active = 1');
770
     * </code>
771
     *
772
     * @see where()
773
     *
774
     * @param mixed $where The query restrictions.
775
     *
776
     * @return $this This QueryBuilder instance.
777
     */
778 156
    public function andWhere($where)
779
    {
780 156
        $args  = func_get_args();
781 156
        $where = $this->getQueryPart('where');
782
783 156
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) {
784 156
            $where->addMultiple($args);
785
        } else {
786 26
            array_unshift($args, $where);
787 26
            $where = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
788
        }
789
790 156
        return $this->add('where', $where, true);
791
    }
792
793
    /**
794
     * Adds one or more restrictions to the query results, forming a logical
795
     * disjunction with any previously specified restrictions.
796
     *
797
     * <code>
798
     *     $qb = $em->createQueryBuilder()
799
     *         ->select('u.name')
800
     *         ->from('users', 'u')
801
     *         ->where('u.id = 1')
802
     *         ->orWhere('u.id = 2');
803
     * </code>
804
     *
805
     * @see where()
806
     *
807
     * @param mixed $where The WHERE statement.
808
     *
809
     * @return $this This QueryBuilder instance.
810
     */
811 78
    public function orWhere($where)
812
    {
813 78
        $args  = func_get_args();
814 78
        $where = $this->getQueryPart('where');
815
816 78
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) {
817 26
            $where->addMultiple($args);
818
        } else {
819 78
            array_unshift($args, $where);
820 78
            $where = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
821
        }
822
823 78
        return $this->add('where', $where, true);
824
    }
825
826
    /**
827
     * Specifies a grouping over the results of the query.
828
     * Replaces any previously specified groupings, if any.
829
     *
830
     * <code>
831
     *     $qb = $conn->createQueryBuilder()
832
     *         ->select('u.name')
833
     *         ->from('users', 'u')
834
     *         ->groupBy('u.id');
835
     * </code>
836
     *
837
     * @param mixed $groupBy The grouping expression.
838
     *
839
     * @return $this This QueryBuilder instance.
840
     */
841 260
    public function groupBy($groupBy) : self
842
    {
843 260
        if (empty($groupBy)) {
844 26
            return $this;
845
        }
846
847 234
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
848
849 234
        return $this->add('groupBy', $groupBy, false);
850
    }
851
852
    /**
853
     * Adds a grouping expression to the query.
854
     *
855
     * <code>
856
     *     $qb = $conn->createQueryBuilder()
857
     *         ->select('u.name')
858
     *         ->from('users', 'u')
859
     *         ->groupBy('u.lastLogin')
860
     *         ->addGroupBy('u.createdAt');
861
     * </code>
862
     *
863
     * @param mixed $groupBy The grouping expression.
864
     *
865
     * @return $this This QueryBuilder instance.
866
     */
867 78
    public function addGroupBy($groupBy) : self
868
    {
869 78
        if (empty($groupBy)) {
870 26
            return $this;
871
        }
872
873 52
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
874
875 52
        return $this->add('groupBy', $groupBy, true);
876
    }
877
878
    /**
879
     * Sets a value for a column in an insert query.
880
     *
881
     * <code>
882
     *     $qb = $conn->createQueryBuilder()
883
     *         ->insert('users')
884
     *         ->values(
885
     *             array(
886
     *                 'name' => '?'
887
     *             )
888
     *         )
889
     *         ->setValue('password', '?');
890
     * </code>
891
     *
892
     * @param string $column The column into which the value should be inserted.
893
     * @param string $value  The value that should be inserted into the column.
894
     *
895
     * @return $this This QueryBuilder instance.
896
     */
897 52
    public function setValue(string $column, string $value) : self
898
    {
899 52
        $this->sqlParts['values'][$column] = $value;
900
901 52
        return $this;
902
    }
903
904
    /**
905
     * Specifies values for an insert query indexed by column names.
906
     * Replaces any previous values, if any.
907
     *
908
     * <code>
909
     *     $qb = $conn->createQueryBuilder()
910
     *         ->insert('users')
911
     *         ->values(
912
     *             array(
913
     *                 'name' => '?',
914
     *                 'password' => '?'
915
     *             )
916
     *         );
917
     * </code>
918
     *
919
     * @param array<string, mixed> $values The values to specify for the insert query indexed by column names.
920
     *
921
     * @return $this This QueryBuilder instance.
922
     */
923 78
    public function values(array $values)
924
    {
925 78
        return $this->add('values', $values);
926
    }
927
928
    /**
929
     * Specifies a restriction over the groups of the query.
930
     * Replaces any previous having restrictions, if any.
931
     *
932
     * @param mixed $having The restriction over the groups.
933
     *
934
     * @return $this This QueryBuilder instance.
935
     */
936 104
    public function having($having)
937
    {
938 104
        if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) {
939 104
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
940
        }
941
942 104
        return $this->add('having', $having);
943
    }
944
945
    /**
946
     * Adds a restriction over the groups of the query, forming a logical
947
     * conjunction with any existing having restrictions.
948
     *
949
     * @param mixed $having The restriction to append.
950
     *
951
     * @return $this This QueryBuilder instance.
952
     */
953 78
    public function andHaving($having)
954
    {
955 78
        $args   = func_get_args();
956 78
        $having = $this->getQueryPart('having');
957
958 78
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) {
959 26
            $having->addMultiple($args);
960
        } else {
961 52
            array_unshift($args, $having);
962 52
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
963
        }
964
965 78
        return $this->add('having', $having);
966
    }
967
968
    /**
969
     * Adds a restriction over the groups of the query, forming a logical
970
     * disjunction with any existing having restrictions.
971
     *
972
     * @param mixed $having The restriction to add.
973
     *
974
     * @return $this This QueryBuilder instance.
975
     */
976 78
    public function orHaving($having)
977
    {
978 78
        $args   = func_get_args();
979 78
        $having = $this->getQueryPart('having');
980
981 78
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) {
982 26
            $having->addMultiple($args);
983
        } else {
984 78
            array_unshift($args, $having);
985 78
            $having = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
986
        }
987
988 78
        return $this->add('having', $having);
989
    }
990
991
    /**
992
     * Specifies an ordering for the query results.
993
     * Replaces any previously specified orderings, if any.
994
     *
995
     * @param string $sort  The ordering expression.
996
     * @param string $order The ordering direction.
997
     *
998
     * @return $this This QueryBuilder instance.
999
     */
1000 78
    public function orderBy(string $sort, ?string $order = null)
1001
    {
1002 78
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false);
1003
    }
1004
1005
    /**
1006
     * Adds an ordering to the query results.
1007
     *
1008
     * @param string $sort  The ordering expression.
1009
     * @param string $order The ordering direction.
1010
     *
1011
     * @return $this This QueryBuilder instance.
1012
     */
1013 52
    public function addOrderBy(string $sort, ?string $order = null)
1014
    {
1015 52
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true);
1016
    }
1017
1018
    /**
1019
     * Gets a query part by its name.
1020
     *
1021
     * @return mixed
1022
     */
1023 338
    public function getQueryPart(string $queryPartName)
1024
    {
1025 338
        return $this->sqlParts[$queryPartName];
1026
    }
1027
1028
    /**
1029
     * Gets all query parts.
1030
     *
1031
     * @return array<string, mixed>
1032
     */
1033 26
    public function getQueryParts() : array
1034
    {
1035 26
        return $this->sqlParts;
1036
    }
1037
1038
    /**
1039
     * Resets SQL parts.
1040
     *
1041
     * @param array<int, string>|null $queryPartNames
1042
     *
1043
     * @return $this This QueryBuilder instance.
1044
     */
1045 26
    public function resetQueryParts(?array $queryPartNames = null) : self
1046
    {
1047 26
        if ($queryPartNames === null) {
1048
            $queryPartNames = array_keys($this->sqlParts);
1049
        }
1050
1051 26
        foreach ($queryPartNames as $queryPartName) {
1052 26
            $this->resetQueryPart($queryPartName);
1053
        }
1054
1055 26
        return $this;
1056
    }
1057
1058
    /**
1059
     * Resets a single SQL part.
1060
     *
1061
     * @return $this This QueryBuilder instance.
1062
     */
1063 52
    public function resetQueryPart(string $queryPartName) : self
1064
    {
1065 52
        $this->sqlParts[$queryPartName] = self::SQL_PARTS_DEFAULTS[$queryPartName];
1066
1067 52
        $this->state = self::STATE_DIRTY;
1068
1069 52
        return $this;
1070
    }
1071
1072
    /**
1073
     * @throws QueryException
1074
     */
1075 1248
    private function getSQLForSelect() : string
1076
    {
1077 1248
        $query = 'SELECT ' . ($this->sqlParts['distinct'] ? 'DISTINCT ' : '') .
1078 1248
                  implode(', ', $this->sqlParts['select']);
1079
1080 1248
        $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '')
1081 1196
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
1082 1196
            . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '')
1083 1196
            . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '')
1084 1196
            . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');
1085
1086 1196
        if ($this->isLimitQuery()) {
1087
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1088
                $query,
1089
                $this->maxResults,
1090
                $this->firstResult
1091
            );
1092
        }
1093
1094 1196
        return $query;
1095
    }
1096
1097
    /**
1098
     * @return array<string, string>
1099
     */
1100 1222
    private function getFromClauses() : array
1101
    {
1102 1222
        $fromClauses  = [];
1103 1222
        $knownAliases = [];
1104
1105
        // Loop through all FROM clauses
1106
        /** @var From $from */
1107 1222
        foreach ($this->sqlParts['from'] as $from) {
1108 1222
            if ($from->alias === null || $from->alias === $from->table) {
1109 182
                $tableSql       = $from->table;
1110 182
                $tableReference = $from->table;
1111
            } else {
1112 1066
                $tableSql       = $from->table . ' ' . $from->alias;
1113 1066
                $tableReference = $from->alias;
1114
            }
1115
1116 1222
            $knownAliases[$tableReference] = true;
1117
1118 1222
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1119
        }
1120
1121 1196
        $this->verifyAllAliasesAreKnown($knownAliases);
1122
1123 1170
        return $fromClauses;
1124
    }
1125
1126
    /**
1127
     * @param array<string, true> $knownAliases
1128
     *
1129
     * @throws QueryException
1130
     */
1131 1196
    private function verifyAllAliasesAreKnown(array $knownAliases) : void
1132
    {
1133 1196
        foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
1134 286
            if (! isset($knownAliases[$fromAlias])) {
1135 26
                throw UnknownAlias::new($fromAlias, array_keys($knownAliases));
1136
            }
1137
        }
1138 1170
    }
1139
1140 1196
    private function isLimitQuery() : bool
1141
    {
1142 1196
        return $this->maxResults !== null || $this->firstResult !== 0;
1143
    }
1144
1145
    /**
1146
     * Converts this instance into an INSERT string in SQL.
1147
     */
1148 104
    private function getSQLForInsert() : string
1149
    {
1150 104
        return 'INSERT INTO ' . $this->sqlParts['table'] .
1151 104
        ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' .
1152 104
        ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')';
1153
    }
1154
1155
    /**
1156
     * Converts this instance into an UPDATE string in SQL.
1157
     */
1158 52
    private function getSQLForUpdate() : string
1159
    {
1160 52
        return 'UPDATE ' . $this->sqlParts['table']
1161 52
            . ' SET ' . implode(', ', $this->sqlParts['set'])
1162 52
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1163
    }
1164
1165
    /**
1166
     * Converts this instance into a DELETE string in SQL.
1167
     */
1168 52
    private function getSQLForDelete() : string
1169
    {
1170 52
        return 'DELETE FROM ' . $this->sqlParts['table'] . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1171
    }
1172
1173
    /**
1174
     * Gets a string representation of this QueryBuilder which corresponds to
1175
     * the final SQL query being constructed.
1176
     *
1177
     * @return string The string representation of this QueryBuilder.
1178
     */
1179 1274
    public function __toString() : string
1180
    {
1181 1274
        return $this->getSQL();
1182
    }
1183
1184
    /**
1185
     * Creates a new named parameter and bind the value $value to it.
1186
     *
1187
     * This method provides a shortcut for PDOStatement::bindValue
1188
     * when using prepared statements.
1189
     *
1190
     * The parameter $value specifies the value that you want to bind. If
1191
     * $placeholder is not provided bindValue() will automatically create a
1192
     * placeholder for you. An automatic placeholder will be of the name
1193
     * ':dcValue1', ':dcValue2' etc.
1194
     *
1195
     * For more information see {@link http://php.net/pdostatement-bindparam}
1196
     *
1197
     * Example:
1198
     * <code>
1199
     * $value = 2;
1200
     * $q->eq( 'id', $q->bindValue( $value ) );
1201
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1202
     * </code>
1203
     *
1204
     * @link http://www.zetacomponents.org
1205
     *
1206
     * @param mixed  $value
1207
     * @param mixed  $type
1208
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1209
     *
1210
     * @return string the placeholder name used.
1211
     */
1212 52
    public function createNamedParameter($value, $type = ParameterType::STRING, ?string $placeHolder = null) : string
1213
    {
1214 52
        if ($placeHolder === null) {
1215 26
            $this->boundCounter++;
1216 26
            $placeHolder = ':dcValue' . $this->boundCounter;
1217
        }
1218 52
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1219
1220 52
        return $placeHolder;
1221
    }
1222
1223
    /**
1224
     * Creates a new positional parameter and bind the given value to it.
1225
     *
1226
     * Attention: If you are using positional parameters with the query builder you have
1227
     * to be very careful to bind all parameters in the order they appear in the SQL
1228
     * statement , otherwise they get bound in the wrong order which can lead to serious
1229
     * bugs in your code.
1230
     *
1231
     * Example:
1232
     * <code>
1233
     *  $qb = $conn->createQueryBuilder();
1234
     *  $qb->select('u.*')
1235
     *     ->from('users', 'u')
1236
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1237
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1238
     * </code>
1239
     *
1240
     * @param mixed $value
1241
     */
1242 26
    public function createPositionalParameter($value, int $type = ParameterType::STRING) : string
1243
    {
1244 26
        $this->boundCounter++;
1245 26
        $this->setParameter($this->boundCounter, $value, $type);
1246
1247 26
        return '?';
1248
    }
1249
1250
    /**
1251
     * @param array<string, true> $knownAliases
1252
     *
1253
     * @throws QueryException
1254
     */
1255 1222
    private function getSQLForJoins(string $fromAlias, array &$knownAliases) : string
1256
    {
1257 1222
        $sql = '';
1258
1259 1222
        if (isset($this->sqlParts['join'][$fromAlias])) {
1260
            /** @var Join $join */
1261 312
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1262 312
                if (array_key_exists($join->alias, $knownAliases)) {
1263 26
                    throw NonUniqueAlias::new($join->alias, array_keys($knownAliases));
1264
                }
1265 286
                $sql                       .= ' ' . $join->type
1266 286
                    . ' JOIN ' . $join->table . ' ' . $join->alias
1267 286
                    . ' ON ' . ((string) $join->condition);
1268 286
                $knownAliases[$join->alias] = true;
1269
            }
1270
1271 286
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1272
                /** @var Join $join */
1273 286
                $sql .= $this->getSQLForJoins($join->alias, $knownAliases);
1274
            }
1275
        }
1276
1277 1196
        return $sql;
1278
    }
1279
1280
    /**
1281
     * Deep clone of all expression objects in the SQL parts.
1282
     */
1283 26
    public function __clone()
1284
    {
1285 26
        foreach ($this->sqlParts as $part => $elements) {
1286 26
            if (is_array($this->sqlParts[$part])) {
1287 26
                foreach ($this->sqlParts[$part] as $idx => $element) {
1288 26
                    if (! is_object($element)) {
1289 26
                        continue;
1290
                    }
1291
1292 26
                    $this->sqlParts[$part][$idx] = clone $element;
1293
                }
1294 26
            } elseif (is_object($elements)) {
1295 26
                $this->sqlParts[$part] = clone $elements;
1296
            }
1297
        }
1298
1299 26
        foreach ($this->params as $name => $param) {
1300 26
            if (! is_object($param)) {
1301
                continue;
1302
            }
1303
1304 26
            $this->params[$name] = clone $param;
1305
        }
1306 26
    }
1307
}
1308