Completed
Pull Request — master (#3836)
by Benjamin
63:09
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 1620
     * @var array<string, Join[]>
144
     */
145 1620
    private $join = [];
146 1620
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 string|CompositeExpression|null
158
     */
159
    private $where;
160
161
    /**
162 216
     * The GROUP BY part of a SELECT query.
163
     *
164 216
     * @var string[]
165
     */
166
    private $groupBy = [];
167
168
    /**
169
     * The HAVING part of a SELECT query.
170 189
     *
171
     * @var string|CompositeExpression|null
172 189
     */
173
    private $having;
174
175
    /**
176
     * The ORDER BY parts of a SELECT query.
177
     *
178 27
     * @var string[]
179
     */
180 27
    private $orderBy = [];
181
182
    /**
183
     * The values of an INSERT query.
184
     *
185
     * @var array<string, mixed>
186
     */
187
    private $values = [];
188 81
189
    /**
190 81
     * Initializes a new <tt>QueryBuilder</tt>.
191
     *
192
     * @param Connection $connection The DBAL Connection.
193
     */
194
    public function __construct(Connection $connection)
195
    {
196
        $this->connection = $connection;
197
    }
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
    public function expr() : ExpressionBuilder
214
    {
215
        return $this->connection->getExpressionBuilder();
216
    }
217
218
    /**
219
     * Gets the type of the currently built query.
220
     */
221
    public function getType() : int
222 1458
    {
223
        return $this->type;
224 1458
    }
225 27
226
    /**
227
     * Gets the associated DBAL Connection for this query builder.
228 1458
     */
229 1458
    public function getConnection() : Connection
230 108
    {
231 108
        return $this->connection;
232 1350
    }
233 54
234 54
    /**
235
     * Gets the state of this query builder instance.
236 1296
     *
237 54
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
238 54
     */
239
    public function getState() : int
240 1242
    {
241
        return $this->state;
242 1242
    }
243 1188
244
    /**
245
     * Executes this query using the bound parameters and their types.
246 1404
     *
247 1404
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
248
     * for insert, update and delete statements.
249 1404
     *
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 162
     * </code>
270
     *
271 162
     * @return string The SQL query string.
272 135
     */
273
    public function getSQL() : string
274
    {
275 162
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
276
            return $this->sql;
277 162
        }
278
279
        switch ($this->type) {
280
            case self::INSERT:
281
                $sql = $this->getSQLForInsert();
282
                break;
283
            case self::DELETE:
284
                $sql = $this->getSQLForDelete();
285
                break;
286
287
            case self::UPDATE:
288
                $sql = $this->getSQLForUpdate();
289
                break;
290
291
            case self::SELECT:
292
            default:
293
                $sql = $this->getSQLForSelect();
294
                break;
295
        }
296
297
        $this->state = self::STATE_CLEAN;
298
        $this->sql   = $sql;
299
300
        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 27
     * </code>
313
     *
314 27
     * @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
    public function setParameter($key, $value, $type = null) : self
321
    {
322
        if ($type !== null) {
323
            $this->paramTypes[$key] = $type;
324 81
        }
325
326 81
        $this->params[$key] = $value;
327
328
        return $this;
329
    }
330
331
    /**
332
     * Sets a collection of query parameters for the query being constructed.
333
     *
334 27
     * <code>
335
     *     $qb = $conn->createQueryBuilder()
336 27
     *         ->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 108
     * @param array<int, mixed>|array<string, mixed> $types  The query parameters types to set.
347
     *
348 108
     * @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 27
    /**
359
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
360 27
     *
361 27
     * @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
        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 27
     *
373
     * @return mixed The value of the bound parameter.
374 27
     */
375
    public function getParameter($key)
376
    {
377
        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 27
     */
385
    public function getParameterTypes() : array
386 27
    {
387 27
        return $this->paramTypes;
388
    }
389 27
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
    public function getParameterType($key)
398 27
    {
399
        return $this->paramTypes[$key] ?? null;
400 27
    }
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
    public function setFirstResult(int $firstResult) : self
410
    {
411
        $this->state       = self::STATE_DIRTY;
412
        $this->firstResult = $firstResult;
413 1512
414
        return $this;
415 1512
    }
416 1512
417
    /**
418 1512
     * Gets the position of the first result the query object was set to retrieve (the "offset").
419 1323
     *
420
     * @return int The position of the first result.
421
     */
422 1512
    public function getFirstResult() : int
423
    {
424 1512
        return $this->firstResult;
425 1323
    }
426 1323
427 1323
    /**
428
     * Sets the maximum number of results to retrieve (the "limit").
429 594
     *
430 324
     * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results.
431 324
     *
432 351
     * @return $this This QueryBuilder instance.
433
     */
434
    public function setMaxResults(?int $maxResults) : self
435 351
    {
436
        $this->state      = self::STATE_DIRTY;
437
        $this->maxResults = $maxResults;
438 1323
439
        return $this;
440
    }
441 1512
442
    /**
443 1512
     * 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
    public function getMaxResults() : ?int
449
    {
450
        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 1323
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
462
     * </code>
463 1323
     *
464
     * @param string ...$expressions The selection expressions.
465 1323
     *
466 27
     * @return $this This QueryBuilder instance.
467
     */
468
    public function select(string ...$expressions) : self
469 1296
    {
470
        $this->type = self::SELECT;
471
472
        if (count($expressions) < 1) {
473
            return $this;
474
        }
475
476
        $this->select = $expressions;
477
478
        $this->state = self::STATE_DIRTY;
479
480
        return $this;
481
    }
482
483
    /**
484 27
     * Adds DISTINCT to the query.
485
     *
486 27
     * <code>
487
     *     $qb = $conn->createQueryBuilder()
488 27
     *         ->select('u.id')
489
     *         ->distinct()
490
     *         ->from('users', 'u')
491
     * </code>
492
     *
493
     * @return $this This QueryBuilder instance.
494
     */
495
    public function distinct() : self
496
    {
497
        $this->distinct = true;
498
499
        $this->state = self::STATE_DIRTY;
500
501
        return $this;
502
    }
503
504
    /**
505
     * Adds an item that is to be returned in the query result.
506
     *
507 54
     * <code>
508
     *     $qb = $conn->createQueryBuilder()
509 54
     *         ->select('u.id')
510
     *         ->addSelect('p.id')
511 54
     *         ->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
    public function addSelect(string $expression, string ...$expressions) : self
521
    {
522
        $this->type = self::SELECT;
523
524
        $this->select = array_merge($this->select, [$expression], $expressions);
525
526
        $this->state = self::STATE_DIRTY;
527
528
        return $this;
529 54
    }
530
531 54
    /**
532
     * Turns the query being built into a bulk delete query that ranges over
533 54
     * 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
    public function delete(string $table) : self
547
    {
548
        $this->type = self::DELETE;
549
550
        $this->table = $table;
551 54
552
        $this->state = self::STATE_DIRTY;
553 54
554
        return $this;
555 54
    }
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
    public function update(string $table) : self
573
    {
574
        $this->type = self::UPDATE;
575
576
        $this->table = $table;
577 108
578
        $this->state = self::STATE_DIRTY;
579 108
580
        return $this;
581 108
    }
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 1269
     *
600
     * @return $this This QueryBuilder instance.
601 1269
     */
602
    public function insert(string $table) : self
603
    {
604
        $this->type = self::INSERT;
605
606
        $this->table = $table;
607
608
        $this->state = self::STATE_DIRTY;
609
610
        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 108
     * </code>
622
     *
623 108
     * @param string      $table The table.
624
     * @param string|null $alias The alias of the table.
625
     *
626
     * @return $this This QueryBuilder instance.
627
     */
628
    public function from(string $table, ?string $alias = null) : self
629
    {
630
        $this->from[] = new From($table, $alias);
631
632
        $this->state = self::STATE_DIRTY;
633
634
        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 270
     *         ->from('users', 'u')
644
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
645 270
     * </code>
646 270
     *
647 270
     * @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
    public function join(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
655
    {
656
        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 27
     * </code>
668
     *
669 27
     * @param string $fromAlias The alias that points to a from clause.
670 27
     * @param string $join      The table name to join.
671 27
     * @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
    public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
677
    {
678
        $this->join[$fromAlias][] = Join::inner($join, $alias, $condition);
679
680
        $this->state = self::STATE_DIRTY;
681
682
        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 27
     *         ->from('users', 'u')
692
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
693 27
     * </code>
694 27
     *
695 27
     * @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
    public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
703
    {
704
        $this->join[$fromAlias][] = Join::left($join, $alias, $condition);
705
706
        $this->state = self::STATE_DIRTY;
707
708
        return $this;
709
    }
710
711
    /**
712
     * Creates and adds a right join to the query.
713 54
     *
714
     * <code>
715 54
     *     $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) : self
729
    {
730
        $this->join[$fromAlias][] = Join::right($join, $alias, $condition);
731
732
        $this->state = self::STATE_DIRTY;
733
734
        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 513
     * </code>
746
     *
747 513
     * @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
    public function set(string $key, string $value) : self
753
    {
754
        $this->set[] = $key . ' = ' . $value;
755
756
        $this->state = self::STATE_DIRTY;
757
758
        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 162
     *         ->where('c.id = ?');
770
     *
771 162
     *     // 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
    public function where($predicate, ...$predicates) : self
789
    {
790
        $this->where = $this->createPredicate($predicate, ...$predicates);
791
792
        $this->state = self::STATE_DIRTY;
793 81
794
        return $this;
795 81
    }
796
797
    /**
798
     * Adds one or more restrictions to the query results, forming a logical
799
     * conjunction with any previously specified restrictions.
800
     *
801
     * <code>
802
     *     $qb = $conn->createQueryBuilder()
803
     *         ->select('u')
804
     *         ->from('users', 'u')
805
     *         ->where('u.username LIKE ?')
806
     *         ->andWhere('u.is_active = 1');
807
     * </code>
808
     *
809
     * @see where()
810
     *
811
     * @param string|CompositeExpression $predicate     The predicate to append.
812
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
813
     *
814 243
     * @return $this This QueryBuilder instance.
815
     */
816 243
    public function andWhere($predicate, ...$predicates) : self
817
    {
818
        $this->where = $this->appendToPredicate($this->where, CompositeExpression::TYPE_AND, $predicate, ...$predicates);
819
820
        $this->state = self::STATE_DIRTY;
821
822
        return $this;
823
    }
824
825
    /**
826
     * Adds one or more restrictions to the query results, forming a logical
827
     * disjunction with any previously specified restrictions.
828
     *
829
     * <code>
830
     *     $qb = $em->createQueryBuilder()
831
     *         ->select('u.name')
832
     *         ->from('users', 'u')
833
     *         ->where('u.id = 1')
834
     *         ->orWhere('u.id = 2');
835 54
     * </code>
836
     *
837 54
     * @see where()
838
     *
839
     * @param string|CompositeExpression $predicate     The predicate to append.
840
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
841
     *
842
     * @return $this This QueryBuilder instance.
843
     */
844
    public function orWhere($predicate, ...$predicates) : self
845
    {
846
        $this->where = $this->appendToPredicate($this->where, CompositeExpression::TYPE_OR, $predicate, ...$predicates);
847
848
        $this->state = self::STATE_DIRTY;
849
850
        return $this;
851
    }
852
853
    /**
854
     * Specifies one or more grouping expressions over the results of the query.
855
     * Replaces any previously specified groupings, if any.
856
     *
857
     * <code>
858
     *     $qb = $conn->createQueryBuilder()
859 54
     *         ->select('u.name')
860
     *         ->from('users', 'u')
861 54
     *         ->groupBy('u.id');
862
     * </code>
863 54
     *
864
     * @param string $expression     The grouping expression
865
     * @param string ...$expressions Additional grouping expressions
866
     *
867
     * @return $this This QueryBuilder instance.
868
     */
869
    public function groupBy(string $expression, string ...$expressions) : self
870
    {
871
        $this->groupBy = array_merge([$expression], $expressions);
872
873
        $this->state = self::STATE_DIRTY;
874
875
        return $this;
876
    }
877
878
    /**
879
     * Adds one or more grouping expressions to the query.
880
     *
881
     * <code>
882
     *     $qb = $conn->createQueryBuilder()
883
     *         ->select('u.name')
884
     *         ->from('users', 'u')
885 81
     *         ->groupBy('u.lastLogin')
886
     *         ->addGroupBy('u.createdAt');
887 81
     * </code>
888
     *
889
     * @param string $expression     The grouping expression
890
     * @param string ...$expressions Additional grouping expressions
891
     *
892
     * @return $this This QueryBuilder instance.
893
     */
894
    public function addGroupBy(string $expression, string ...$expressions) : self
895
    {
896
        $this->groupBy = array_merge($this->groupBy, [$expression], $expressions);
897
898
        $this->state = self::STATE_DIRTY;
899 108
900
        return $this;
901 108
    }
902
903
    /**
904
     * Sets a value for a column in an insert query.
905
     *
906
     * <code>
907
     *     $qb = $conn->createQueryBuilder()
908
     *         ->insert('users')
909
     *         ->values(
910
     *             array(
911
     *                 'name' => '?'
912
     *             )
913 81
     *         )
914
     *         ->setValue('password', '?');
915 81
     * </code>
916
     *
917
     * @param string $column The column into which the value should be inserted.
918
     * @param string $value  The value that should be inserted into the column.
919
     *
920
     * @return $this This QueryBuilder instance.
921
     */
922
    public function setValue(string $column, string $value) : self
923
    {
924
        $this->values[$column] = $value;
925
926
        return $this;
927 81
    }
928
929 81
    /**
930
     * Specifies values for an insert query indexed by column names.
931
     * Replaces any previous values, if any.
932
     *
933
     * <code>
934
     *     $qb = $conn->createQueryBuilder()
935
     *         ->insert('users')
936
     *         ->values(
937
     *             array(
938
     *                 'name' => '?',
939
     *                 'password' => '?'
940 621
     *             )
941
     *         );
942 621
     * </code>
943
     *
944
     * @param array<string, mixed> $values The values to specify for the insert query indexed by column names.
945
     *
946
     * @return $this This QueryBuilder instance.
947
     */
948 621
    public function values(array $values) : self
949
    {
950
        $this->values = $values;
951 621
952
        $this->state = self::STATE_DIRTY;
953
954
        return $this;
955
    }
956
957
    /**
958
     * Specifies a restriction over the groups of the query.
959
     * Replaces any previous having restrictions, if any.
960
     *
961 351
     * @param string|CompositeExpression $predicate     The HAVING clause predicate.
962
     * @param string|CompositeExpression ...$predicates Additional HAVING clause predicates.
963 351
     *
964
     * @return $this This QueryBuilder instance.
965 351
     */
966 54
    public function having($predicate, ...$predicates) : self
967
    {
968 351
        $this->having = $this->createPredicate($predicate, ...$predicates);
969 78
970 351
        $this->state = self::STATE_DIRTY;
971
972
        return $this;
973
    }
974 351
975
    /**
976
     * Adds a restriction over the groups of the query, forming a logical
977
     * conjunction with any existing having restrictions.
978
     *
979
     * @param string|CompositeExpression $predicate     The predicate to append.
980
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
981
     *
982
     * @return $this This QueryBuilder instance.
983
     */
984
    public function andHaving($predicate, ...$predicates) : self
985
    {
986 81
        $this->having = $this->appendToPredicate($this->having, CompositeExpression::TYPE_AND, $predicate, ...$predicates);
987
988 81
        $this->state = self::STATE_DIRTY;
989
990
        return $this;
991
    }
992
993
    /**
994
     * Adds a restriction over the groups of the query, forming a logical
995
     * disjunction with any existing having restrictions.
996
     *
997
     * @param string|CompositeExpression $predicate     The predicate to append.
998
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
999 54
     *
1000
     * @return $this This QueryBuilder instance.
1001 54
     */
1002
    public function orHaving($predicate, ...$predicates) : self
1003
    {
1004
        $this->having = $this->appendToPredicate($this->having, CompositeExpression::TYPE_OR, $predicate, ...$predicates);
1005
1006
        $this->state = self::STATE_DIRTY;
1007
1008
        return $this;
1009 351
    }
1010
1011 351
    /**
1012
     * Creates a CompositeExpression from one or more predicates combined by the AND logic.
1013
     *
1014
     * @param string|CompositeExpression $predicate
1015
     * @param string|CompositeExpression ...$predicates
1016
     *
1017
     * @return string|CompositeExpression
1018
     */
1019 27
    private function createPredicate($predicate, ...$predicates)
1020
    {
1021 27
        if (! $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...
1022
            return $predicate;
1023
        }
1024
1025
        return new CompositeExpression(CompositeExpression::TYPE_AND, array_merge([$predicate], $predicates));
1026
    }
1027
1028
    /**
1029
     * Appends the given predicates combined by the given type of logic to the current predicate.
1030
     *
1031 27
     * @param string|CompositeExpression|null $currentPredicate
1032
     * @param string|CompositeExpression      ...$predicates
1033 27
     *
1034
     * @return string|CompositeExpression
1035
     */
1036
    private function appendToPredicate($currentPredicate, string $type, ...$predicates)
1037 27
    {
1038 27
        if ($currentPredicate === null && count($predicates) === 1) {
1039
            return $predicates[0];
1040
        }
1041 27
1042
        if ($currentPredicate instanceof CompositeExpression && $currentPredicate->getType() === $type) {
1043
            return $currentPredicate->addMultiple($predicates);
1044
        }
1045
1046
        if ($currentPredicate !== null) {
1047
            array_unshift($predicates, $currentPredicate);
1048
        }
1049 54
1050
        return new CompositeExpression($type, $predicates);
1051 54
    }
1052
1053 54
    /**
1054
     * Specifies an ordering for the query results.
1055 54
     * Replaces any previously specified orderings, if any.
1056
     *
1057
     * @param string $sort  The ordering expression.
1058
     * @param string $order The ordering direction.
1059
     *
1060
     * @return $this This QueryBuilder instance.
1061 1242
     */
1062
    public function orderBy(string $sort, ?string $order = null) : self
1063 1242
    {
1064 1242
        $orderBy = $sort;
1065
1066 1242
        if ($order !== null) {
1067 1188
            $orderBy .= ' ' . $order;
1068 1188
        }
1069 1188
1070 1188
        $this->orderBy = [$orderBy];
1071
1072 1188
        $this->state = self::STATE_DIRTY;
1073
1074
        return $this;
1075
    }
1076
1077
    /**
1078
     * Adds an ordering to the query results.
1079
     *
1080 1188
     * @param string $sort  The ordering expression.
1081
     * @param string $order The ordering direction.
1082
     *
1083
     * @return $this This QueryBuilder instance.
1084
     */
1085
    public function addOrderBy(string $sort, ?string $order = null) : self
1086 1215
    {
1087
        $orderBy = $sort;
1088 1215
1089 1215
        if ($order !== null) {
1090
            $orderBy .= ' ' . $order;
1091
        }
1092
1093 1215
        $this->orderBy[] = $orderBy;
1094 1215
1095 189
        $this->state = self::STATE_DIRTY;
1096 189
1097
        return $this;
1098 1053
    }
1099 1053
1100
    /**
1101
     * @throws QueryException
1102 1215
     */
1103
    private function getSQLForSelect() : string
1104 1215
    {
1105
        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...
1106
            throw new QueryException('No SELECT expressions given. Please use select() or addSelect().');
1107 1188
        }
1108
1109 1161
        $query = 'SELECT';
1110
1111
        if ($this->distinct) {
1112
            $query .= ' DISTINCT';
1113
        }
1114
1115
        $query .= ' ' . implode(', ', $this->select);
1116
1117 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...
1118
            $query .= ' FROM ' . implode(', ', $this->getFromClauses());
1119 1188
        }
1120 297
1121 27
        if ($this->where) {
1122
            $query .= ' WHERE ' . $this->where;
1123
        }
1124 1161
1125
        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...
1126 1188
            $query .= ' GROUP BY ' . implode(', ', $this->groupBy);
1127
        }
1128 1188
1129
        if ($this->having) {
1130
            $query .= ' HAVING ' . $this->having;
1131
        }
1132
1133
        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...
1134 108
            $query .= ' ORDER BY ' . implode(', ', $this->orderBy);
1135
        }
1136 108
1137 108
        if ($this->isLimitQuery()) {
1138 108
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1139
                $query,
1140
                $this->maxResults,
1141
                $this->firstResult
1142
            );
1143
        }
1144 54
1145
        return $query;
1146 54
    }
1147 54
1148 54
    /**
1149
     * @return array<string, string>
1150
     */
1151
    private function getFromClauses() : array
1152
    {
1153
        $fromClauses  = [];
1154 54
        $knownAliases = [];
1155
1156 54
        foreach ($this->from as $from) {
1157
            if ($from->alias === null || $from->alias === $from->table) {
1158
                $tableSql       = $from->table;
1159
                $tableReference = $from->table;
1160
            } else {
1161
                $tableSql       = $from->table . ' ' . $from->alias;
1162
                $tableReference = $from->alias;
1163
            }
1164
1165 1269
            $knownAliases[$tableReference] = true;
1166
1167 1269
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1168
        }
1169
1170
        $this->verifyAllAliasesAreKnown($knownAliases);
1171
1172
        return $fromClauses;
1173
    }
1174
1175
    /**
1176
     * @param array<string, true> $knownAliases
1177
     *
1178
     * @throws QueryException
1179
     */
1180
    private function verifyAllAliasesAreKnown(array $knownAliases) : void
1181
    {
1182
        foreach ($this->join as $fromAlias => $joins) {
1183
            if (! isset($knownAliases[$fromAlias])) {
1184
                throw UnknownAlias::new($fromAlias, array_keys($knownAliases));
1185
            }
1186
        }
1187
    }
1188
1189
    private function isLimitQuery() : bool
1190
    {
1191
        return $this->maxResults !== null || $this->firstResult !== 0;
1192
    }
1193
1194
    /**
1195
     * Converts this instance into an INSERT string in SQL.
1196
     */
1197
    private function getSQLForInsert() : string
1198 54
    {
1199
        return 'INSERT INTO ' . $this->table .
1200 54
        ' (' . implode(', ', array_keys($this->values)) . ')' .
1201 27
        ' VALUES(' . implode(', ', $this->values) . ')';
1202 27
    }
1203
1204 54
    /**
1205
     * Converts this instance into an UPDATE string in SQL.
1206 54
     */
1207
    private function getSQLForUpdate() : string
1208
    {
1209
        $query = 'UPDATE ' . $this->table . ' SET ' . implode(', ', $this->set);
1210
1211
        if ($this->where !== null) {
1212
            $query .= ' WHERE ' . $this->where;
1213
        }
1214
1215
        return $query;
1216
    }
1217
1218
    /**
1219
     * Converts this instance into a DELETE string in SQL.
1220
     */
1221
    private function getSQLForDelete() : string
1222
    {
1223
        $query = 'DELETE FROM ' . $this->table;
1224
1225
        if ($this->where !== null) {
1226
            $query .= ' WHERE ' . $this->where;
1227
        }
1228 27
1229
        return $query;
1230 27
    }
1231 27
1232
    /**
1233 27
     * Gets a string representation of this QueryBuilder which corresponds to
1234
     * the final SQL query being constructed.
1235
     *
1236
     * @return string The string representation of this QueryBuilder.
1237
     */
1238
    public function __toString() : string
1239
    {
1240
        return $this->getSQL();
1241 1215
    }
1242
1243 1215
    /**
1244
     * Creates a new named parameter and bind the value $value to it.
1245 1215
     *
1246
     * This method provides a shortcut for PDOStatement::bindValue
1247 324
     * when using prepared statements.
1248 324
     *
1249 27
     * The parameter $value specifies the value that you want to bind. If
1250
     * $placeholder is not provided bindValue() will automatically create a
1251 297
     * placeholder for you. An automatic placeholder will be of the name
1252 297
     * ':dcValue1', ':dcValue2' etc.
1253 297
     *
1254 297
     * For more information see {@link http://php.net/pdostatement-bindparam}
1255
     *
1256
     * Example:
1257 297
     * <code>
1258
     * $value = 2;
1259 297
     * $q->eq( 'id', $q->bindValue( $value ) );
1260
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1261
     * </code>
1262
     *
1263 1188
     * @link http://www.zetacomponents.org
1264
     *
1265
     * @param mixed  $value
1266
     * @param mixed  $type
1267
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1268
     *
1269 27
     * @return string the placeholder name used.
1270
     */
1271 27
    public function createNamedParameter($value, $type = ParameterType::STRING, ?string $placeHolder = null) : string
1272 27
    {
1273 27
        if ($placeHolder === null) {
1274 27
            $this->boundCounter++;
1275 27
            $placeHolder = ':dcValue' . $this->boundCounter;
1276
        }
1277
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1278 27
1279
        return $placeHolder;
1280 27
    }
1281
1282
    /**
1283
     * Creates a new positional parameter and bind the given value to it.
1284
     *
1285 27
     * Attention: If you are using positional parameters with the query builder you have
1286 27
     * to be very careful to bind all parameters in the order they appear in the SQL
1287
     * statement , otherwise they get bound in the wrong order which can lead to serious
1288
     * bugs in your code.
1289
     *
1290 27
     * Example:
1291
     * <code>
1292 27
     *  $qb = $conn->createQueryBuilder();
1293
     *  $qb->select('u.*')
1294
     *     ->from('users', 'u')
1295
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1296
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1297
     * </code>
1298
     *
1299
     * @param mixed $value
1300
     */
1301
    public function createPositionalParameter($value, int $type = ParameterType::STRING) : string
1302
    {
1303
        $this->boundCounter++;
1304
        $this->setParameter($this->boundCounter, $value, $type);
1305
1306
        return '?';
1307
    }
1308
1309
    /**
1310
     * @param array<string, true> $knownAliases
1311
     *
1312
     * @throws QueryException
1313
     */
1314
    private function getSQLForJoins(string $fromAlias, array &$knownAliases) : string
1315
    {
1316
        $sql = '';
1317
1318
        if (! isset($this->join[$fromAlias])) {
1319
            return $sql;
1320
        }
1321
1322
        foreach ($this->join[$fromAlias] as $join) {
1323
            if (array_key_exists($join->alias, $knownAliases)) {
1324
                throw NonUniqueAlias::new($join->alias, array_keys($knownAliases));
1325
            }
1326
            $sql                       .= ' ' . $join->type
1327
                . ' JOIN ' . $join->table . ' ' . $join->alias
1328
                . ' ON ' . ((string) $join->condition);
1329
            $knownAliases[$join->alias] = true;
1330
        }
1331
1332
        foreach ($this->join[$fromAlias] as $join) {
1333
            $sql .= $this->getSQLForJoins($join->alias, $knownAliases);
1334
        }
1335
1336
        return $sql;
1337
    }
1338
1339
    /**
1340
     * Deep clone of all expression objects in the SQL parts.
1341
     */
1342
    public function __clone()
1343
    {
1344
        foreach ($this->from as $key => $from) {
1345
            $this->from[$key] = clone $from;
1346
        }
1347
1348
        foreach ($this->join as $fromAlias => $joins) {
1349
            foreach ($joins as $key => $join) {
1350
                $this->join[$fromAlias][$key] = clone $join;
1351
            }
1352
        }
1353
1354
        if (is_object($this->where)) {
1355
            $this->where = clone $this->where;
1356
        }
1357
1358
        if (is_object($this->having)) {
1359
            $this->having = clone $this->having;
1360
        }
1361
1362
        foreach ($this->params as $name => $param) {
1363
            if (! is_object($param)) {
1364
                continue;
1365
            }
1366
1367
            $this->params[$name] = clone $param;
1368
        }
1369
    }
1370
}
1371