Passed
Pull Request — master (#3836)
by Benjamin
11:25
created

QueryBuilder::getQueryPart()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 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_merge;
17
use function array_unshift;
18
use function count;
19
use function implode;
20
use function is_object;
21
use function substr;
22
23
/**
24
 * QueryBuilder class is responsible to dynamically create SQL queries.
25
 *
26
 * Important: Verify that every feature you use will work with your database vendor.
27
 * SQL Query Builder does not attempt to validate the generated SQL at all.
28
 *
29
 * The query builder does no validation whatsoever if certain features even work with the
30
 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
31
 * even if some vendors such as MySQL support it.
32
 */
33
class QueryBuilder
34
{
35
    /*
36
     * The query types.
37
     */
38
    public const SELECT = 0;
39
    public const DELETE = 1;
40
    public const UPDATE = 2;
41
    public const INSERT = 3;
42
43
    /*
44
     * The builder states.
45
     */
46
    public const STATE_DIRTY = 0;
47
    public const STATE_CLEAN = 1;
48
49
    /**
50
     * The DBAL Connection.
51
     *
52
     * @var Connection
53
     */
54
    private $connection;
55
56
    /**
57
     * The complete SQL string for this query.
58
     *
59
     * @var string
60
     */
61
    private $sql;
62
63
    /**
64
     * The query parameters.
65
     *
66
     * @var array<int, mixed>|array<string, mixed>
67
     */
68
    private $params = [];
69
70
    /**
71
     * The parameter type map of this query.
72
     *
73
     * @var array<int, mixed>|array<string, mixed>
74
     */
75
    private $paramTypes = [];
76
77
    /**
78
     * The type of query this is. Can be select, update or delete.
79
     *
80
     * @var int
81
     */
82
    private $type = self::SELECT;
83
84
    /**
85
     * The state of the query object. Can be dirty or clean.
86
     *
87
     * @var int
88
     */
89
    private $state = self::STATE_CLEAN;
90
91
    /**
92
     * The index of the first result to retrieve.
93
     *
94
     * @var int
95
     */
96
    private $firstResult = 0;
97
98
    /**
99
     * The maximum number of results to retrieve or NULL to retrieve all results.
100
     *
101
     * @var int|null
102
     */
103
    private $maxResults;
104
105
    /**
106
     * The counter of bound parameters used with {@see bindValue).
107
     *
108
     * @var int
109
     */
110
    private $boundCounter = 0;
111
112
    /**
113
     * The SELECT parts of the query.
114
     *
115
     * @var string[]
116
     */
117
    private $select = [];
118
119
    /**
120
     * Whether this is a SELECT DISTINCT query.
121
     *
122
     * @var bool
123
     */
124
    private $distinct = false;
125
126
    /**
127
     * The FROM parts of a SELECT query.
128
     *
129
     * @var From[]
130
     */
131
    private $from = [];
132
133
    /**
134
     * The table name for an INSERT, UPDATE or DELETE query.
135
     *
136
     * @var string|null
137
     */
138
    private $table;
139
140
    /**
141
     * The list of joins, indexed by from alias.
142
     *
143
     * @var array<string, Join[]>
144
     */
145
    private $join = [];
146
147
    /**
148
     * The SET parts of an UPDATE query.
149
     *
150
     * @var string[]
151
     */
152
    private $set = [];
153
154
    /**
155
     * The WHERE part of a SELECT, UPDATE or DELETE query.
156
     *
157
     * @var CompositeExpression|null
158
     */
159
    private $where;
160
161
    /**
162
     * The GROUP BY part of a SELECT query.
163
     *
164
     * @var string[]
165
     */
166
    private $groupBy = [];
167
168
    /**
169
     * The HAVING part of a SELECT query.
170
     *
171
     * @var CompositeExpression|null
172
     */
173
    private $having;
174
175
    /**
176
     * The ORDER BY parts of a SELECT query.
177
     *
178
     * @var string[]
179
     */
180
    private $orderBy = [];
181
182
    /**
183
     * The values of an INSERT query.
184
     *
185
     * @var array<string, mixed>
186
     */
187
    private $values = [];
188
189
    /**
190
     * Initializes a new <tt>QueryBuilder</tt>.
191
     *
192
     * @param Connection $connection The DBAL Connection.
193
     */
194 1593
    public function __construct(Connection $connection)
195
    {
196 1593
        $this->connection = $connection;
197 1593
    }
198
199
    /**
200
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
201
     * This producer method is intended for convenient inline usage. Example:
202
     *
203
     * <code>
204
     *     $qb = $conn->createQueryBuilder()
205
     *         ->select('u')
206
     *         ->from('users', 'u')
207
     *         ->where($qb->expr()->eq('u.id', 1));
208
     * </code>
209
     *
210
     * For more complex expression construction, consider storing the expression
211
     * builder object in a local variable.
212
     */
213 216
    public function expr() : ExpressionBuilder
214
    {
215 216
        return $this->connection->getExpressionBuilder();
216
    }
217
218
    /**
219
     * Gets the type of the currently built query.
220
     */
221 189
    public function getType() : int
222
    {
223 189
        return $this->type;
224
    }
225
226
    /**
227
     * Gets the associated DBAL Connection for this query builder.
228
     */
229 27
    public function getConnection() : Connection
230
    {
231 27
        return $this->connection;
232
    }
233
234
    /**
235
     * Gets the state of this query builder instance.
236
     *
237
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
238
     */
239 108
    public function getState() : int
240
    {
241 108
        return $this->state;
242
    }
243
244
    /**
245
     * Executes this query using the bound parameters and their types.
246
     *
247
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
248
     * for insert, update and delete statements.
249
     *
250
     * @return Statement|int
251
     */
252
    public function execute()
253
    {
254
        if ($this->type === self::SELECT) {
255
            return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
256
        }
257
258
        return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes);
259
    }
260
261
    /**
262
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
263
     *
264
     * <code>
265
     *     $qb = $em->createQueryBuilder()
266
     *         ->select('u')
267
     *         ->from('User', 'u')
268
     *     echo $qb->getSQL(); // SELECT u FROM User u
269
     * </code>
270
     *
271
     * @return string The SQL query string.
272
     */
273 1431
    public function getSQL() : string
274
    {
275 1431
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
276 27
            return $this->sql;
277
        }
278
279 1431
        switch ($this->type) {
280 1431
            case self::INSERT:
281 108
                $sql = $this->getSQLForInsert();
282 108
                break;
283 1323
            case self::DELETE:
284 54
                $sql = $this->getSQLForDelete();
285 54
                break;
286
287 1269
            case self::UPDATE:
288 54
                $sql = $this->getSQLForUpdate();
289 54
                break;
290
291 1215
            case self::SELECT:
292
            default:
293 1215
                $sql = $this->getSQLForSelect();
294 1134
                break;
295
        }
296
297 1350
        $this->state = self::STATE_CLEAN;
298 1350
        $this->sql   = $sql;
299
300 1350
        return $sql;
301
    }
302
303
    /**
304
     * Sets a query parameter for the query being constructed.
305
     *
306
     * <code>
307
     *     $qb = $conn->createQueryBuilder()
308
     *         ->select('u')
309
     *         ->from('users', 'u')
310
     *         ->where('u.id = :user_id')
311
     *         ->setParameter(':user_id', 1);
312
     * </code>
313
     *
314
     * @param string|int      $key   The parameter position or name.
315
     * @param mixed           $value The parameter value.
316
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
317
     *
318
     * @return $this This QueryBuilder instance.
319
     */
320 162
    public function setParameter($key, $value, $type = null) : self
321
    {
322 162
        if ($type !== null) {
323 135
            $this->paramTypes[$key] = $type;
324
        }
325
326 162
        $this->params[$key] = $value;
327
328 162
        return $this;
329
    }
330
331
    /**
332
     * Sets a collection of query parameters for the query being constructed.
333
     *
334
     * <code>
335
     *     $qb = $conn->createQueryBuilder()
336
     *         ->select('u')
337
     *         ->from('users', 'u')
338
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
339
     *         ->setParameters(array(
340
     *             ':user_id1' => 1,
341
     *             ':user_id2' => 2
342
     *         ));
343
     * </code>
344
     *
345
     * @param array<int, mixed>|array<string, mixed> $params The query parameters to set.
346
     * @param array<int, mixed>|array<string, mixed> $types  The query parameters types to set.
347
     *
348
     * @return $this This QueryBuilder instance.
349
     */
350
    public function setParameters(array $params, array $types = []) : self
351
    {
352
        $this->paramTypes = $types;
353
        $this->params     = $params;
354
355
        return $this;
356
    }
357
358
    /**
359
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
360
     *
361
     * @return array<string|int, mixed> The currently defined query parameters indexed by parameter index or name.
362
     */
363 27
    public function getParameters() : array
364
    {
365 27
        return $this->params;
366
    }
367
368
    /**
369
     * Gets a (previously set) query parameter of the query being constructed.
370
     *
371
     * @param string|int $key The key (index or name) of the bound parameter.
372
     *
373
     * @return mixed The value of the bound parameter.
374
     */
375 81
    public function getParameter($key)
376
    {
377 81
        return $this->params[$key] ?? null;
378
    }
379
380
    /**
381
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
382
     *
383
     * @return array<string|int, mixed> The currently defined query parameter types indexed by parameter index or name.
384
     */
385 27
    public function getParameterTypes() : array
386
    {
387 27
        return $this->paramTypes;
388
    }
389
390
    /**
391
     * Gets a (previously set) query parameter type of the query being constructed.
392
     *
393
     * @param string|int $key The key (index or name) of the bound parameter type.
394
     *
395
     * @return mixed The value of the bound parameter type.
396
     */
397 108
    public function getParameterType($key)
398
    {
399 108
        return $this->paramTypes[$key] ?? null;
400
    }
401
402
    /**
403
     * Sets the position of the first result to retrieve (the "offset").
404
     *
405
     * @param int $firstResult The first result to return.
406
     *
407
     * @return $this This QueryBuilder instance.
408
     */
409 27
    public function setFirstResult(int $firstResult) : self
410
    {
411 27
        $this->state       = self::STATE_DIRTY;
412 27
        $this->firstResult = $firstResult;
413
414 27
        return $this;
415
    }
416
417
    /**
418
     * Gets the position of the first result the query object was set to retrieve (the "offset").
419
     *
420
     * @return int The position of the first result.
421
     */
422 27
    public function getFirstResult() : int
423
    {
424 27
        return $this->firstResult;
425
    }
426
427
    /**
428
     * Sets the maximum number of results to retrieve (the "limit").
429
     *
430
     * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results.
431
     *
432
     * @return $this This QueryBuilder instance.
433
     */
434 54
    public function setMaxResults(?int $maxResults) : self
435
    {
436 54
        $this->state      = self::STATE_DIRTY;
437 54
        $this->maxResults = $maxResults;
438
439 54
        return $this;
440
    }
441
442
    /**
443
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
444
     * Returns NULL if all results will be returned.
445
     *
446
     * @return int|null The maximum number of results.
447
     */
448 54
    public function getMaxResults() : ?int
449
    {
450 54
        return $this->maxResults;
451
    }
452
453
    /**
454
     * Specifies an item that is to be returned in the query result.
455
     * Replaces any previously specified selections, if any.
456
     *
457
     * <code>
458
     *     $qb = $conn->createQueryBuilder()
459
     *         ->select('u.id', 'p.id')
460
     *         ->from('users', 'u')
461
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
462
     * </code>
463
     *
464
     * @param string ...$expressions The selection expressions.
465
     *
466
     * @return $this This QueryBuilder instance.
467
     */
468 1269
    public function select(string ...$expressions) : self
469
    {
470 1269
        $this->type = self::SELECT;
471
472 1269
        if (count($expressions) < 1) {
473 27
            return $this;
474
        }
475
476 1242
        $this->select = $expressions;
477
478 1242
        $this->state = self::STATE_DIRTY;
479
480 1242
        return $this;
481
    }
482
483
    /**
484
     * Adds DISTINCT to the query.
485
     *
486
     * <code>
487
     *     $qb = $conn->createQueryBuilder()
488
     *         ->select('u.id')
489
     *         ->distinct()
490
     *         ->from('users', 'u')
491
     * </code>
492
     *
493
     * @return $this This QueryBuilder instance.
494
     */
495 27
    public function distinct() : self
496
    {
497 27
        $this->distinct = true;
498
499 27
        $this->state = self::STATE_DIRTY;
500
501 27
        return $this;
502
    }
503
504
    /**
505
     * Adds an item that is to be returned in the query result.
506
     *
507
     * <code>
508
     *     $qb = $conn->createQueryBuilder()
509
     *         ->select('u.id')
510
     *         ->addSelect('p.id')
511
     *         ->from('users', 'u')
512
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
513
     * </code>
514
     *
515
     * @param string $expression     The selection expression.
516
     * @param string ...$expressions Additional selection expressions.
517
     *
518
     * @return $this This QueryBuilder instance.
519
     */
520 54
    public function addSelect(string $expression, string ...$expressions) : self
521
    {
522 54
        $this->type = self::SELECT;
523
524 54
        $this->select = array_merge($this->select, [$expression], $expressions);
525
526 54
        $this->state = self::STATE_DIRTY;
527
528 54
        return $this;
529
    }
530
531
    /**
532
     * Turns the query being built into a bulk delete query that ranges over
533
     * a certain table.
534
     *
535
     * <code>
536
     *     $qb = $conn->createQueryBuilder()
537
     *         ->delete('users', 'u')
538
     *         ->where('u.id = :user_id')
539
     *         ->setParameter(':user_id', 1);
540
     * </code>
541
     *
542
     * @param string $table The table whose rows are subject to the deletion.
543
     *
544
     * @return $this This QueryBuilder instance.
545
     */
546 54
    public function delete(string $table) : self
547
    {
548 54
        $this->type = self::DELETE;
549
550 54
        $this->table = $table;
551
552 54
        $this->state = self::STATE_DIRTY;
553
554 54
        return $this;
555
    }
556
557
    /**
558
     * Turns the query being built into a bulk update query that ranges over
559
     * a certain table
560
     *
561
     * <code>
562
     *     $qb = $conn->createQueryBuilder()
563
     *         ->update('counters', 'c')
564
     *         ->set('c.value', 'c.value + 1')
565
     *         ->where('c.id = ?');
566
     * </code>
567
     *
568
     * @param string $table The table whose rows are subject to the update.
569
     *
570
     * @return $this This QueryBuilder instance.
571
     */
572 54
    public function update(string $table) : self
573
    {
574 54
        $this->type = self::UPDATE;
575
576 54
        $this->table = $table;
577
578 54
        $this->state = self::STATE_DIRTY;
579
580 54
        return $this;
581
    }
582
583
    /**
584
     * Turns the query being built into an insert query that inserts into
585
     * a certain table
586
     *
587
     * <code>
588
     *     $qb = $conn->createQueryBuilder()
589
     *         ->insert('users')
590
     *         ->values(
591
     *             array(
592
     *                 'name' => '?',
593
     *                 'password' => '?'
594
     *             )
595
     *         );
596
     * </code>
597
     *
598
     * @param string $table The table into which the rows should be inserted.
599
     *
600
     * @return $this This QueryBuilder instance.
601
     */
602 108
    public function insert(string $table) : self
603
    {
604 108
        $this->type = self::INSERT;
605
606 108
        $this->table = $table;
607
608 108
        $this->state = self::STATE_DIRTY;
609
610 108
        return $this;
611
    }
612
613
    /**
614
     * Creates and adds a query root corresponding to the table identified by the
615
     * given alias, forming a cartesian product with any existing query roots.
616
     *
617
     * <code>
618
     *     $qb = $conn->createQueryBuilder()
619
     *         ->select('u.id')
620
     *         ->from('users', 'u')
621
     * </code>
622
     *
623
     * @param string      $table The table.
624
     * @param string|null $alias The alias of the table.
625
     *
626
     * @return $this This QueryBuilder instance.
627
     */
628 1215
    public function from(string $table, ?string $alias = null) : self
629
    {
630 1215
        $this->from[] = new From($table, $alias);
631
632 1215
        $this->state = self::STATE_DIRTY;
633
634 1215
        return $this;
635
    }
636
637
    /**
638
     * Creates and adds a join to the query.
639
     *
640
     * <code>
641
     *     $qb = $conn->createQueryBuilder()
642
     *         ->select('u.name')
643
     *         ->from('users', 'u')
644
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
645
     * </code>
646
     *
647
     * @param string $fromAlias The alias that points to a from clause.
648
     * @param string $join      The table name to join.
649
     * @param string $alias     The alias of the join table.
650
     * @param string $condition The condition for the join.
651
     *
652
     * @return $this This QueryBuilder instance.
653
     */
654 108
    public function join(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
655
    {
656 108
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
657
    }
658
659
    /**
660
     * Creates and adds a join to the query.
661
     *
662
     * <code>
663
     *     $qb = $conn->createQueryBuilder()
664
     *         ->select('u.name')
665
     *         ->from('users', 'u')
666
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
667
     * </code>
668
     *
669
     * @param string $fromAlias The alias that points to a from clause.
670
     * @param string $join      The table name to join.
671
     * @param string $alias     The alias of the join table.
672
     * @param string $condition The condition for the join.
673
     *
674
     * @return $this This QueryBuilder instance.
675
     */
676 270
    public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
677
    {
678 270
        $this->join[$fromAlias][] = Join::inner($join, $alias, $condition);
679
680 270
        $this->state = self::STATE_DIRTY;
681
682 270
        return $this;
683
    }
684
685
    /**
686
     * Creates and adds a left join to the query.
687
     *
688
     * <code>
689
     *     $qb = $conn->createQueryBuilder()
690
     *         ->select('u.name')
691
     *         ->from('users', 'u')
692
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
693
     * </code>
694
     *
695
     * @param string $fromAlias The alias that points to a from clause.
696
     * @param string $join      The table name to join.
697
     * @param string $alias     The alias of the join table.
698
     * @param string $condition The condition for the join.
699
     *
700
     * @return $this This QueryBuilder instance.
701
     */
702 27
    public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
703
    {
704 27
        $this->join[$fromAlias][] = Join::left($join, $alias, $condition);
705
706 27
        $this->state = self::STATE_DIRTY;
707
708 27
        return $this;
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 27
    public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
729
    {
730 27
        $this->join[$fromAlias][] = Join::right($join, $alias, $condition);
731
732 27
        $this->state = self::STATE_DIRTY;
733
734 27
        return $this;
735
    }
736
737
    /**
738
     * Sets a new value for a column in a bulk update query.
739
     *
740
     * <code>
741
     *     $qb = $conn->createQueryBuilder()
742
     *         ->update('counters', 'c')
743
     *         ->set('c.value', 'c.value + 1')
744
     *         ->where('c.id = ?');
745
     * </code>
746
     *
747
     * @param string $key   The column to set.
748
     * @param string $value The value, expression, placeholder, etc.
749
     *
750
     * @return $this This QueryBuilder instance.
751
     */
752 54
    public function set(string $key, string $value) : self
753
    {
754 54
        $this->set[] = $key . ' = ' . $value;
755
756 54
        $this->state = self::STATE_DIRTY;
757
758 54
        return $this;
759
    }
760
761
    /**
762
     * Specifies one or more restrictions to the query result.
763
     * Replaces any previously specified restrictions, if any.
764
     *
765
     * <code>
766
     *     $qb = $conn->createQueryBuilder()
767
     *         ->select('c.value')
768
     *         ->from('counters', 'c')
769
     *         ->where('c.id = ?');
770
     *
771
     *     // You can optionally programmatically build and/or expressions
772
     *     $qb = $conn->createQueryBuilder();
773
     *
774
     *     $or = $qb->expr()->orx();
775
     *     $or->add($qb->expr()->eq('c.id', 1));
776
     *     $or->add($qb->expr()->eq('c.id', 2));
777
     *
778
     *     $qb->update('counters', 'c')
779
     *         ->set('c.value', 'c.value + 1')
780
     *         ->where($or);
781
     * </code>
782
     *
783
     * @param string|CompositeExpression $predicate     The WHERE clause predicate.
784
     * @param string|CompositeExpression ...$predicates Additional WHERE clause predicates.
785
     *
786
     * @return $this This QueryBuilder instance.
787
     */
788 459
    public function where($predicate, ...$predicates) : self
789
    {
790 459
        if ($predicate instanceof CompositeExpression && ! $predicates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $predicates of type array<integer,Doctrine\D...ositeExpression|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
791 27
            $this->where = $predicate;
792
        } else {
793 432
            $this->where = new CompositeExpression(CompositeExpression::TYPE_AND, array_merge([$predicate], $predicates));
794
        }
795
796 459
        $this->state = self::STATE_DIRTY;
797
798 459
        return $this;
799
    }
800
801
    /**
802
     * Adds one or more restrictions to the query results, forming a logical
803
     * conjunction with any previously specified restrictions.
804
     *
805
     * <code>
806
     *     $qb = $conn->createQueryBuilder()
807
     *         ->select('u')
808
     *         ->from('users', 'u')
809
     *         ->where('u.username LIKE ?')
810
     *         ->andWhere('u.is_active = 1');
811
     * </code>
812
     *
813
     * @see where()
814
     *
815
     * @param string|CompositeExpression $predicate     The predicate to append.
816
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
817
     *
818
     * @return $this This QueryBuilder instance.
819
     */
820 162
    public function andWhere($predicate, ...$predicates) : self
821
    {
822 162
        $allPredicates = array_merge([$predicate], $predicates);
823
824 162
        if ($this->where !== null && $this->where->getType() === CompositeExpression::TYPE_AND) {
825 162
            $this->where->addMultiple($allPredicates);
826
        } else {
827 27
            if ($this->where !== null) {
828 27
                array_unshift($allPredicates, $this->where);
829
            }
830
831 27
            $this->where = new CompositeExpression(CompositeExpression::TYPE_AND, $allPredicates);
832
        }
833
834 162
        $this->state = self::STATE_DIRTY;
835
836 162
        return $this;
837
    }
838
839
    /**
840
     * Adds one or more restrictions to the query results, forming a logical
841
     * disjunction with any previously specified restrictions.
842
     *
843
     * <code>
844
     *     $qb = $em->createQueryBuilder()
845
     *         ->select('u.name')
846
     *         ->from('users', 'u')
847
     *         ->where('u.id = 1')
848
     *         ->orWhere('u.id = 2');
849
     * </code>
850
     *
851
     * @see where()
852
     *
853
     * @param string|CompositeExpression $predicate     The predicate to append.
854
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
855
     *
856
     * @return $this This QueryBuilder instance.
857
     */
858 81
    public function orWhere($predicate, ...$predicates) : self
859
    {
860 81
        $allPredicates = array_merge([$predicate], $predicates);
861
862 81
        if ($this->where !== null && $this->where->getType() === CompositeExpression::TYPE_OR) {
863 27
            $this->where->addMultiple($allPredicates);
864
        } else {
865 81
            if ($this->where !== null) {
866 54
                array_unshift($allPredicates, $this->where);
867
            }
868
869 81
            $this->where = new CompositeExpression(CompositeExpression::TYPE_OR, $allPredicates);
870
        }
871
872 81
        $this->state = self::STATE_DIRTY;
873
874 81
        return $this;
875
    }
876
877
    /**
878
     * Specifies one or more grouping expressions over the results of the query.
879
     * Replaces any previously specified groupings, if any.
880
     *
881
     * <code>
882
     *     $qb = $conn->createQueryBuilder()
883
     *         ->select('u.name')
884
     *         ->from('users', 'u')
885
     *         ->groupBy('u.id');
886
     * </code>
887
     *
888
     * @param string $expression     The grouping expression
889
     * @param string ...$expressions Additional grouping expressions
890
     *
891
     * @return $this This QueryBuilder instance.
892
     */
893 243
    public function groupBy(string $expression, string ...$expressions) : self
894
    {
895 243
        $this->groupBy = array_merge([$expression], $expressions);
896
897 243
        $this->state = self::STATE_DIRTY;
898
899 243
        return $this;
900
    }
901
902
    /**
903
     * Adds one or more grouping expressions to the query.
904
     *
905
     * <code>
906
     *     $qb = $conn->createQueryBuilder()
907
     *         ->select('u.name')
908
     *         ->from('users', 'u')
909
     *         ->groupBy('u.lastLogin')
910
     *         ->addGroupBy('u.createdAt');
911
     * </code>
912
     *
913
     * @param string $expression     The grouping expression
914
     * @param string ...$expressions Additional grouping expressions
915
     *
916
     * @return $this This QueryBuilder instance.
917
     */
918 54
    public function addGroupBy(string $expression, string ...$expressions) : self
919
    {
920 54
        $this->groupBy = array_merge($this->groupBy, [$expression], $expressions);
921
922 54
        $this->state = self::STATE_DIRTY;
923
924 54
        return $this;
925
    }
926
927
    /**
928
     * Sets a value for a column in an insert query.
929
     *
930
     * <code>
931
     *     $qb = $conn->createQueryBuilder()
932
     *         ->insert('users')
933
     *         ->values(
934
     *             array(
935
     *                 'name' => '?'
936
     *             )
937
     *         )
938
     *         ->setValue('password', '?');
939
     * </code>
940
     *
941
     * @param string $column The column into which the value should be inserted.
942
     * @param string $value  The value that should be inserted into the column.
943
     *
944
     * @return $this This QueryBuilder instance.
945
     */
946 54
    public function setValue(string $column, string $value) : self
947
    {
948 54
        $this->values[$column] = $value;
949
950 54
        return $this;
951
    }
952
953
    /**
954
     * Specifies values for an insert query indexed by column names.
955
     * Replaces any previous values, if any.
956
     *
957
     * <code>
958
     *     $qb = $conn->createQueryBuilder()
959
     *         ->insert('users')
960
     *         ->values(
961
     *             array(
962
     *                 'name' => '?',
963
     *                 'password' => '?'
964
     *             )
965
     *         );
966
     * </code>
967
     *
968
     * @param array<string, mixed> $values The values to specify for the insert query indexed by column names.
969
     *
970
     * @return $this This QueryBuilder instance.
971
     */
972 81
    public function values(array $values) : self
973
    {
974 81
        $this->values = $values;
975
976 81
        $this->state = self::STATE_DIRTY;
977
978 81
        return $this;
979
    }
980
981
    /**
982
     * Specifies a restriction over the groups of the query.
983
     * Replaces any previous having restrictions, if any.
984
     *
985
     * @param string|CompositeExpression $predicate     The HAVING clause predicate.
986
     * @param string|CompositeExpression ...$predicates Additional HAVING clause predicates.
987
     *
988
     * @return $this This QueryBuilder instance.
989
     */
990 108
    public function having($predicate, ...$predicates) : self
991
    {
992 108
        if ($predicate instanceof CompositeExpression && ! $predicates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $predicates of type array<integer,Doctrine\D...ositeExpression|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
993
            $this->having = $predicate;
994
        } else {
995 108
            $this->having = new CompositeExpression(CompositeExpression::TYPE_AND, array_merge([$predicate], $predicates));
996
        }
997
998 108
        $this->state = self::STATE_DIRTY;
999
1000 108
        return $this;
1001
    }
1002
1003
    /**
1004
     * Adds a restriction over the groups of the query, forming a logical
1005
     * conjunction with any existing having restrictions.
1006
     *
1007
     * @param string|CompositeExpression $predicate     The predicate to append.
1008
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
1009
     *
1010
     * @return $this This QueryBuilder instance.
1011
     */
1012 81
    public function andHaving($predicate, ...$predicates) : self
1013
    {
1014 81
        $allPredicates = array_merge([$predicate], $predicates);
1015
1016 81
        if ($this->having !== null && $this->having->getType() === CompositeExpression::TYPE_AND) {
1017 27
            $this->having->addMultiple($allPredicates);
1018
        } else {
1019 54
            if ($this->having !== null) {
1020 27
                array_unshift($allPredicates, $this->having);
1021
            }
1022
1023 54
            $this->having = new CompositeExpression(CompositeExpression::TYPE_AND, $allPredicates);
1024
        }
1025
1026 81
        $this->state = self::STATE_DIRTY;
1027
1028 81
        return $this;
1029
    }
1030
1031
    /**
1032
     * Adds a restriction over the groups of the query, forming a logical
1033
     * disjunction with any existing having restrictions.
1034
     *
1035
     * @param string|CompositeExpression $predicate     The predicate to append.
1036
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
1037
     *
1038
     * @return $this This QueryBuilder instance.
1039
     */
1040 81
    public function orHaving($predicate, ...$predicates) : self
1041
    {
1042 81
        $allPredicates = array_merge([$predicate], $predicates);
1043
1044 81
        if ($this->having !== null && $this->having->getType() === CompositeExpression::TYPE_OR) {
1045 27
            $this->having->addMultiple($allPredicates);
1046
        } else {
1047 81
            if ($this->having !== null) {
1048 54
                array_unshift($allPredicates, $this->having);
1049
            }
1050
1051 81
            $this->having = new CompositeExpression(CompositeExpression::TYPE_OR, $allPredicates);
1052
        }
1053
1054 81
        $this->state = self::STATE_DIRTY;
1055
1056 81
        return $this;
1057
    }
1058
1059
    /**
1060
     * Specifies an ordering for the query results.
1061
     * Replaces any previously specified orderings, if any.
1062
     *
1063
     * @param string $sort  The ordering expression.
1064
     * @param string $order The ordering direction.
1065
     *
1066
     * @return $this This QueryBuilder instance.
1067
     */
1068 54
    public function orderBy(string $sort, ?string $order = null) : self
1069
    {
1070 54
        $orderBy = $sort;
1071
1072 54
        if ($order !== null) {
1073
            $orderBy .= ' ' . $order;
1074
        }
1075
1076 54
        $this->orderBy = [$orderBy];
1077
1078 54
        $this->state = self::STATE_DIRTY;
1079
1080 54
        return $this;
1081
    }
1082
1083
    /**
1084
     * Adds an ordering to the query results.
1085
     *
1086
     * @param string $sort  The ordering expression.
1087
     * @param string $order The ordering direction.
1088
     *
1089
     * @return $this This QueryBuilder instance.
1090
     */
1091 54
    public function addOrderBy(string $sort, ?string $order = null) : self
1092
    {
1093 54
        $orderBy = $sort;
1094
1095 54
        if ($order !== null) {
1096 54
            $orderBy .= ' ' . $order;
1097
        }
1098
1099 54
        $this->orderBy[] = $orderBy;
1100
1101 54
        $this->state = self::STATE_DIRTY;
1102
1103 54
        return $this;
1104
    }
1105
1106
    /**
1107
     * @throws QueryException
1108
     */
1109 1215
    private function getSQLForSelect() : string
1110
    {
1111 1215
        if (! $this->select) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->select of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1112 27
            throw new QueryException('No SELECT expressions given. Please use select() or addSelect().');
1113
        }
1114
1115 1188
        $query = 'SELECT';
1116
1117 1188
        if ($this->distinct) {
1118 27
            $query .= ' DISTINCT';
1119
        }
1120
1121 1188
        $query .= ' ' . implode(', ', $this->select);
1122
1123 1188
        if ($this->from) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->from of type Doctrine\DBAL\Query\From[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1124 1161
            $query .= ' FROM ' . implode(', ', $this->getFromClauses());
1125
        }
1126
1127 1134
        if ($this->where) {
1128 351
            $query .= ' WHERE ' . $this->where;
1129
        }
1130
1131 1134
        if ($this->groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->groupBy of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1132 243
            $query .= ' GROUP BY ' . implode(', ', $this->groupBy);
1133
        }
1134
1135 1134
        if ($this->having) {
1136 162
            $query .= ' HAVING ' . $this->having;
1137
        }
1138
1139 1134
        if ($this->orderBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orderBy of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1140 81
            $query .= ' ORDER BY ' . implode(', ', $this->orderBy);
1141
        }
1142
1143 1134
        if ($this->isLimitQuery()) {
1144
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1145
                $query,
1146
                $this->maxResults,
1147
                $this->firstResult
1148
            );
1149
        }
1150
1151 1134
        return $query;
1152
    }
1153
1154
    /**
1155
     * @return array<string, string>
1156
     */
1157 1161
    private function getFromClauses() : array
1158
    {
1159 1161
        $fromClauses  = [];
1160 1161
        $knownAliases = [];
1161
1162 1161
        foreach ($this->from as $from) {
1163 1161
            if ($from->alias === null || $from->alias === $from->table) {
1164 189
                $tableSql       = $from->table;
1165 189
                $tableReference = $from->table;
1166
            } else {
1167 999
                $tableSql       = $from->table . ' ' . $from->alias;
1168 999
                $tableReference = $from->alias;
1169
            }
1170
1171 1161
            $knownAliases[$tableReference] = true;
1172
1173 1161
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1174
        }
1175
1176 1134
        $this->verifyAllAliasesAreKnown($knownAliases);
1177
1178 1107
        return $fromClauses;
1179
    }
1180
1181
    /**
1182
     * @param array<string, true> $knownAliases
1183
     *
1184
     * @throws QueryException
1185
     */
1186 1134
    private function verifyAllAliasesAreKnown(array $knownAliases) : void
1187
    {
1188 1134
        foreach ($this->join as $fromAlias => $joins) {
1189 297
            if (! isset($knownAliases[$fromAlias])) {
1190 27
                throw UnknownAlias::new($fromAlias, array_keys($knownAliases));
1191
            }
1192
        }
1193 1107
    }
1194
1195 1134
    private function isLimitQuery() : bool
1196
    {
1197 1134
        return $this->maxResults !== null || $this->firstResult !== 0;
1198
    }
1199
1200
    /**
1201
     * Converts this instance into an INSERT string in SQL.
1202
     */
1203 108
    private function getSQLForInsert() : string
1204
    {
1205 108
        return 'INSERT INTO ' . $this->table .
1206 108
        ' (' . implode(', ', array_keys($this->values)) . ')' .
1207 108
        ' VALUES(' . implode(', ', $this->values) . ')';
1208
    }
1209
1210
    /**
1211
     * Converts this instance into an UPDATE string in SQL.
1212
     */
1213 54
    private function getSQLForUpdate() : string
1214
    {
1215 54
        $query = 'UPDATE ' . $this->table . ' SET ' . implode(', ', $this->set);
1216
1217 54
        if ($this->where !== null) {
1218 27
            $query .= ' WHERE ' . $this->where;
1219
        }
1220
1221 54
        return $query;
1222
    }
1223
1224
    /**
1225
     * Converts this instance into a DELETE string in SQL.
1226
     */
1227 54
    private function getSQLForDelete() : string
1228
    {
1229 54
        $query = 'DELETE FROM ' . $this->table;
1230
1231 54
        if ($this->where !== null) {
1232 27
            $query .= ' WHERE ' . $this->where;
1233
        }
1234
1235 54
        return $query;
1236
    }
1237
1238
    /**
1239
     * Gets a string representation of this QueryBuilder which corresponds to
1240
     * the final SQL query being constructed.
1241
     *
1242
     * @return string The string representation of this QueryBuilder.
1243
     */
1244 1215
    public function __toString() : string
1245
    {
1246 1215
        return $this->getSQL();
1247
    }
1248
1249
    /**
1250
     * Creates a new named parameter and bind the value $value to it.
1251
     *
1252
     * This method provides a shortcut for PDOStatement::bindValue
1253
     * when using prepared statements.
1254
     *
1255
     * The parameter $value specifies the value that you want to bind. If
1256
     * $placeholder is not provided bindValue() will automatically create a
1257
     * placeholder for you. An automatic placeholder will be of the name
1258
     * ':dcValue1', ':dcValue2' etc.
1259
     *
1260
     * For more information see {@link http://php.net/pdostatement-bindparam}
1261
     *
1262
     * Example:
1263
     * <code>
1264
     * $value = 2;
1265
     * $q->eq( 'id', $q->bindValue( $value ) );
1266
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1267
     * </code>
1268
     *
1269
     * @link http://www.zetacomponents.org
1270
     *
1271
     * @param mixed  $value
1272
     * @param mixed  $type
1273
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1274
     *
1275
     * @return string the placeholder name used.
1276
     */
1277 54
    public function createNamedParameter($value, $type = ParameterType::STRING, ?string $placeHolder = null) : string
1278
    {
1279 54
        if ($placeHolder === null) {
1280 27
            $this->boundCounter++;
1281 27
            $placeHolder = ':dcValue' . $this->boundCounter;
1282
        }
1283 54
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1284
1285 54
        return $placeHolder;
1286
    }
1287
1288
    /**
1289
     * Creates a new positional parameter and bind the given value to it.
1290
     *
1291
     * Attention: If you are using positional parameters with the query builder you have
1292
     * to be very careful to bind all parameters in the order they appear in the SQL
1293
     * statement , otherwise they get bound in the wrong order which can lead to serious
1294
     * bugs in your code.
1295
     *
1296
     * Example:
1297
     * <code>
1298
     *  $qb = $conn->createQueryBuilder();
1299
     *  $qb->select('u.*')
1300
     *     ->from('users', 'u')
1301
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1302
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1303
     * </code>
1304
     *
1305
     * @param mixed $value
1306
     */
1307 27
    public function createPositionalParameter($value, int $type = ParameterType::STRING) : string
1308
    {
1309 27
        $this->boundCounter++;
1310 27
        $this->setParameter($this->boundCounter, $value, $type);
1311
1312 27
        return '?';
1313
    }
1314
1315
    /**
1316
     * @param array<string, true> $knownAliases
1317
     *
1318
     * @throws QueryException
1319
     */
1320 1161
    private function getSQLForJoins(string $fromAlias, array &$knownAliases) : string
1321
    {
1322 1161
        $sql = '';
1323
1324 1161
        if (! isset($this->join[$fromAlias])) {
1325 1134
            return $sql;
1326
        }
1327
1328 324
        foreach ($this->join[$fromAlias] as $join) {
1329 324
            if (array_key_exists($join->alias, $knownAliases)) {
1330 27
                throw NonUniqueAlias::new($join->alias, array_keys($knownAliases));
1331
            }
1332 297
            $sql                       .= ' ' . $join->type
1333 297
                . ' JOIN ' . $join->table . ' ' . $join->alias
1334 297
                . ' ON ' . ((string) $join->condition);
1335 297
            $knownAliases[$join->alias] = true;
1336
        }
1337
1338 297
        foreach ($this->join[$fromAlias] as $join) {
1339 297
            $sql .= $this->getSQLForJoins($join->alias, $knownAliases);
1340
        }
1341
1342 297
        return $sql;
1343
    }
1344
1345
    /**
1346
     * Deep clone of all expression objects in the SQL parts.
1347
     */
1348 27
    public function __clone()
1349
    {
1350 27
        foreach ($this->from as $key => $from) {
1351 27
            $this->from[$key] = clone $from;
1352
        }
1353
1354 27
        foreach ($this->join as $fromAlias => $joins) {
1355
            foreach ($joins as $key => $join) {
1356
                $this->join[$fromAlias][$key] = clone $join;
1357
            }
1358
        }
1359
1360 27
        if ($this->where !== null) {
1361 27
            $this->where = clone $this->where;
1362
        }
1363
1364 27
        if ($this->having !== null) {
1365
            $this->having = clone $this->having;
1366
        }
1367
1368 27
        foreach ($this->params as $name => $param) {
1369 27
            if (! is_object($param)) {
1370
                continue;
1371
            }
1372
1373 27
            $this->params[$name] = clone $param;
1374
        }
1375 27
    }
1376
}
1377