Completed
Pull Request — develop (#3695)
by
unknown
60:36
created

QueryBuilder::distinct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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