QueryBuilder::getSQLForDelete()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
cc 2
nc 2
nop 0
crap 2
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\ResultStatement;
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 string|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 string|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 1320
    public function __construct(Connection $connection)
195
    {
196 1320
        $this->connection = $connection;
197 1320
    }
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 176
    public function expr() : ExpressionBuilder
214
    {
215 176
        return $this->connection->getExpressionBuilder();
216
    }
217
218
    /**
219
     * Gets the type of the currently built query.
220
     */
221 154
    public function getType() : int
222
    {
223 154
        return $this->type;
224
    }
225
226
    /**
227
     * Gets the associated DBAL Connection for this query builder.
228
     */
229 22
    public function getConnection() : Connection
230
    {
231 22
        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 88
    public function getState() : int
240
    {
241 88
        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 ResultStatement|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
     * @throws QueryException If the object doesn't represent a valid query in its current state.
274
     */
275 1188
    public function getSQL() : string
276
    {
277 1188
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
278 22
            return $this->sql;
279
        }
280
281 1188
        switch ($this->type) {
282 1188
            case self::INSERT:
283 88
                $sql = $this->getSQLForInsert();
284 88
                break;
285
286 1100
            case self::DELETE:
287 44
                $sql = $this->getSQLForDelete();
288 44
                break;
289
290 1056
            case self::UPDATE:
291 44
                $sql = $this->getSQLForUpdate();
292 44
                break;
293
294 1012
            case self::SELECT:
295
            default:
296 1012
                $sql = $this->getSQLForSelect();
297 946
                break;
298
        }
299
300 1122
        $this->state = self::STATE_CLEAN;
301 1122
        $this->sql   = $sql;
302
303 1122
        return $sql;
304
    }
305
306
    /**
307
     * Sets a query parameter for the query being constructed.
308
     *
309
     * <code>
310
     *     $qb = $conn->createQueryBuilder()
311
     *         ->select('u')
312
     *         ->from('users', 'u')
313
     *         ->where('u.id = :user_id')
314
     *         ->setParameter(':user_id', 1);
315
     * </code>
316
     *
317
     * @param string|int      $key   The parameter position or name.
318
     * @param mixed           $value The parameter value.
319
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
320
     *
321
     * @return $this This QueryBuilder instance.
322
     */
323 132
    public function setParameter($key, $value, $type = null) : self
324
    {
325 132
        if ($type !== null) {
326 110
            $this->paramTypes[$key] = $type;
327
        }
328
329 132
        $this->params[$key] = $value;
330
331 132
        return $this;
332
    }
333
334
    /**
335
     * Sets a collection of query parameters for the query being constructed.
336
     *
337
     * <code>
338
     *     $qb = $conn->createQueryBuilder()
339
     *         ->select('u')
340
     *         ->from('users', 'u')
341
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
342
     *         ->setParameters(array(
343
     *             ':user_id1' => 1,
344
     *             ':user_id2' => 2
345
     *         ));
346
     * </code>
347
     *
348
     * @param array<int, mixed>|array<string, mixed> $params The query parameters to set.
349
     * @param array<int, mixed>|array<string, mixed> $types  The query parameters types to set.
350
     *
351
     * @return $this This QueryBuilder instance.
352
     */
353
    public function setParameters(array $params, array $types = []) : self
354
    {
355
        $this->paramTypes = $types;
356
        $this->params     = $params;
357
358
        return $this;
359
    }
360
361
    /**
362
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
363
     *
364
     * @return array<string|int, mixed> The currently defined query parameters indexed by parameter index or name.
365
     */
366 22
    public function getParameters() : array
367
    {
368 22
        return $this->params;
369
    }
370
371
    /**
372
     * Gets a (previously set) query parameter of the query being constructed.
373
     *
374
     * @param string|int $key The key (index or name) of the bound parameter.
375
     *
376
     * @return mixed The value of the bound parameter.
377
     */
378 66
    public function getParameter($key)
379
    {
380 66
        return $this->params[$key] ?? null;
381
    }
382
383
    /**
384
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
385
     *
386
     * @return array<string|int, mixed> The currently defined query parameter types indexed by parameter index or name.
387
     */
388 22
    public function getParameterTypes() : array
389
    {
390 22
        return $this->paramTypes;
391
    }
392
393
    /**
394
     * Gets a (previously set) query parameter type of the query being constructed.
395
     *
396
     * @param string|int $key The key (index or name) of the bound parameter type.
397
     *
398
     * @return mixed The value of the bound parameter type.
399
     */
400 88
    public function getParameterType($key)
401
    {
402 88
        return $this->paramTypes[$key] ?? null;
403
    }
404
405
    /**
406
     * Sets the position of the first result to retrieve (the "offset").
407
     *
408
     * @param int $firstResult The first result to return.
409
     *
410
     * @return $this This QueryBuilder instance.
411
     */
412 22
    public function setFirstResult(int $firstResult) : self
413
    {
414 22
        $this->state       = self::STATE_DIRTY;
415 22
        $this->firstResult = $firstResult;
416
417 22
        return $this;
418
    }
419
420
    /**
421
     * Gets the position of the first result the query object was set to retrieve (the "offset").
422
     *
423
     * @return int The position of the first result.
424
     */
425 22
    public function getFirstResult() : int
426
    {
427 22
        return $this->firstResult;
428
    }
429
430
    /**
431
     * Sets the maximum number of results to retrieve (the "limit").
432
     *
433
     * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results.
434
     *
435
     * @return $this This QueryBuilder instance.
436
     */
437 44
    public function setMaxResults(?int $maxResults) : self
438
    {
439 44
        $this->state      = self::STATE_DIRTY;
440 44
        $this->maxResults = $maxResults;
441
442 44
        return $this;
443
    }
444
445
    /**
446
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
447
     * Returns NULL if all results will be returned.
448
     *
449
     * @return int|null The maximum number of results.
450
     */
451 44
    public function getMaxResults() : ?int
452
    {
453 44
        return $this->maxResults;
454
    }
455
456
    /**
457
     * Specifies an item that is to be returned in the query result.
458
     * Replaces any previously specified selections, if any.
459
     *
460
     * <code>
461
     *     $qb = $conn->createQueryBuilder()
462
     *         ->select('u.id', 'p.id')
463
     *         ->from('users', 'u')
464
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
465
     * </code>
466
     *
467
     * @param string ...$expressions The selection expressions.
468
     *
469
     * @return $this This QueryBuilder instance.
470
     */
471 1056
    public function select(string ...$expressions) : self
472
    {
473 1056
        $this->type = self::SELECT;
474
475 1056
        if (count($expressions) < 1) {
476 22
            return $this;
477
        }
478
479 1034
        $this->select = $expressions;
480
481 1034
        $this->state = self::STATE_DIRTY;
482
483 1034
        return $this;
484
    }
485
486
    /**
487
     * Adds DISTINCT to the query.
488
     *
489
     * <code>
490
     *     $qb = $conn->createQueryBuilder()
491
     *         ->select('u.id')
492
     *         ->distinct()
493
     *         ->from('users', 'u')
494
     * </code>
495
     *
496
     * @return $this This QueryBuilder instance.
497
     */
498 22
    public function distinct() : self
499
    {
500 22
        $this->distinct = true;
501
502 22
        $this->state = self::STATE_DIRTY;
503
504 22
        return $this;
505
    }
506
507
    /**
508
     * Adds an item that is to be returned in the query result.
509
     *
510
     * <code>
511
     *     $qb = $conn->createQueryBuilder()
512
     *         ->select('u.id')
513
     *         ->addSelect('p.id')
514
     *         ->from('users', 'u')
515
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
516
     * </code>
517
     *
518
     * @param string $expression     The selection expression.
519
     * @param string ...$expressions Additional selection expressions.
520
     *
521
     * @return $this This QueryBuilder instance.
522
     */
523 44
    public function addSelect(string $expression, string ...$expressions) : self
524
    {
525 44
        $this->type = self::SELECT;
526
527 44
        $this->select = array_merge($this->select, [$expression], $expressions);
528
529 44
        $this->state = self::STATE_DIRTY;
530
531 44
        return $this;
532
    }
533
534
    /**
535
     * Turns the query being built into a bulk delete query that ranges over
536
     * a certain table.
537
     *
538
     * <code>
539
     *     $qb = $conn->createQueryBuilder()
540
     *         ->delete('users', 'u')
541
     *         ->where('u.id = :user_id')
542
     *         ->setParameter(':user_id', 1);
543
     * </code>
544
     *
545
     * @param string $table The table whose rows are subject to the deletion.
546
     *
547
     * @return $this This QueryBuilder instance.
548
     */
549 44
    public function delete(string $table) : self
550
    {
551 44
        $this->type = self::DELETE;
552
553 44
        $this->table = $table;
554
555 44
        $this->state = self::STATE_DIRTY;
556
557 44
        return $this;
558
    }
559
560
    /**
561
     * Turns the query being built into a bulk update query that ranges over
562
     * a certain table
563
     *
564
     * <code>
565
     *     $qb = $conn->createQueryBuilder()
566
     *         ->update('counters', 'c')
567
     *         ->set('c.value', 'c.value + 1')
568
     *         ->where('c.id = ?');
569
     * </code>
570
     *
571
     * @param string $table The table whose rows are subject to the update.
572
     *
573
     * @return $this This QueryBuilder instance.
574
     */
575 44
    public function update(string $table) : self
576
    {
577 44
        $this->type = self::UPDATE;
578
579 44
        $this->table = $table;
580
581 44
        $this->state = self::STATE_DIRTY;
582
583 44
        return $this;
584
    }
585
586
    /**
587
     * Turns the query being built into an insert query that inserts into
588
     * a certain table
589
     *
590
     * <code>
591
     *     $qb = $conn->createQueryBuilder()
592
     *         ->insert('users')
593
     *         ->values(
594
     *             array(
595
     *                 'name' => '?',
596
     *                 'password' => '?'
597
     *             )
598
     *         );
599
     * </code>
600
     *
601
     * @param string $table The table into which the rows should be inserted.
602
     *
603
     * @return $this This QueryBuilder instance.
604
     */
605 88
    public function insert(string $table) : self
606
    {
607 88
        $this->type = self::INSERT;
608
609 88
        $this->table = $table;
610
611 88
        $this->state = self::STATE_DIRTY;
612
613 88
        return $this;
614
    }
615
616
    /**
617
     * Creates and adds a query root corresponding to the table identified by the
618
     * given alias, forming a cartesian product with any existing query roots.
619
     *
620
     * <code>
621
     *     $qb = $conn->createQueryBuilder()
622
     *         ->select('u.id')
623
     *         ->from('users', 'u')
624
     * </code>
625
     *
626
     * @param string      $table The table.
627
     * @param string|null $alias The alias of the table.
628
     *
629
     * @return $this This QueryBuilder instance.
630
     */
631 1012
    public function from(string $table, ?string $alias = null) : self
632
    {
633 1012
        $this->from[] = new From($table, $alias);
634
635 1012
        $this->state = self::STATE_DIRTY;
636
637 1012
        return $this;
638
    }
639
640
    /**
641
     * Creates and adds a join to the query.
642
     *
643
     * <code>
644
     *     $qb = $conn->createQueryBuilder()
645
     *         ->select('u.name')
646
     *         ->from('users', 'u')
647
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
648
     * </code>
649
     *
650
     * @param string $fromAlias The alias that points to a from clause.
651
     * @param string $join      The table name to join.
652
     * @param string $alias     The alias of the join table.
653
     * @param string $condition The condition for the join.
654
     *
655
     * @return $this This QueryBuilder instance.
656
     */
657 110
    public function join(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
658
    {
659 110
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
660
    }
661
662
    /**
663
     * Creates and adds a join to the query.
664
     *
665
     * <code>
666
     *     $qb = $conn->createQueryBuilder()
667
     *         ->select('u.name')
668
     *         ->from('users', 'u')
669
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
670
     * </code>
671
     *
672
     * @param string $fromAlias The alias that points to a from clause.
673
     * @param string $join      The table name to join.
674
     * @param string $alias     The alias of the join table.
675
     * @param string $condition The condition for the join.
676
     *
677
     * @return $this This QueryBuilder instance.
678
     */
679 242
    public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
680
    {
681 242
        $this->join[$fromAlias][] = Join::inner($join, $alias, $condition);
682
683 242
        $this->state = self::STATE_DIRTY;
684
685 242
        return $this;
686
    }
687
688
    /**
689
     * Creates and adds a left join to the query.
690
     *
691
     * <code>
692
     *     $qb = $conn->createQueryBuilder()
693
     *         ->select('u.name')
694
     *         ->from('users', 'u')
695
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
696
     * </code>
697
     *
698
     * @param string $fromAlias The alias that points to a from clause.
699
     * @param string $join      The table name to join.
700
     * @param string $alias     The alias of the join table.
701
     * @param string $condition The condition for the join.
702
     *
703
     * @return $this This QueryBuilder instance.
704
     */
705 22
    public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
706
    {
707 22
        $this->join[$fromAlias][] = Join::left($join, $alias, $condition);
708
709 22
        $this->state = self::STATE_DIRTY;
710
711 22
        return $this;
712
    }
713
714
    /**
715
     * Creates and adds a right join to the query.
716
     *
717
     * <code>
718
     *     $qb = $conn->createQueryBuilder()
719
     *         ->select('u.name')
720
     *         ->from('users', 'u')
721
     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
722
     * </code>
723
     *
724
     * @param string $fromAlias The alias that points to a from clause.
725
     * @param string $join      The table name to join.
726
     * @param string $alias     The alias of the join table.
727
     * @param string $condition The condition for the join.
728
     *
729
     * @return $this This QueryBuilder instance.
730
     */
731 22
    public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
732
    {
733 22
        $this->join[$fromAlias][] = Join::right($join, $alias, $condition);
734
735 22
        $this->state = self::STATE_DIRTY;
736
737 22
        return $this;
738
    }
739
740
    /**
741
     * Sets a new value for a column in a bulk update query.
742
     *
743
     * <code>
744
     *     $qb = $conn->createQueryBuilder()
745
     *         ->update('counters', 'c')
746
     *         ->set('c.value', 'c.value + 1')
747
     *         ->where('c.id = ?');
748
     * </code>
749
     *
750
     * @param string $key   The column to set.
751
     * @param string $value The value, expression, placeholder, etc.
752
     *
753
     * @return $this This QueryBuilder instance.
754
     */
755 44
    public function set(string $key, string $value) : self
756
    {
757 44
        $this->set[] = $key . ' = ' . $value;
758
759 44
        $this->state = self::STATE_DIRTY;
760
761 44
        return $this;
762
    }
763
764
    /**
765
     * Specifies one or more restrictions to the query result.
766
     * Replaces any previously specified restrictions, if any.
767
     *
768
     * <code>
769
     *     $qb = $conn->createQueryBuilder()
770
     *         ->select('c.value')
771
     *         ->from('counters', 'c')
772
     *         ->where('c.id = ?');
773
     *
774
     *     // You can optionally programmatically build and/or expressions
775
     *     $qb = $conn->createQueryBuilder();
776
     *
777
     *     $or = $qb->expr()->orx();
778
     *     $or->add($qb->expr()->eq('c.id', 1));
779
     *     $or->add($qb->expr()->eq('c.id', 2));
780
     *
781
     *     $qb->update('counters', 'c')
782
     *         ->set('c.value', 'c.value + 1')
783
     *         ->where($or);
784
     * </code>
785
     *
786
     * @param string|CompositeExpression $predicate     The WHERE clause predicate.
787
     * @param string|CompositeExpression ...$predicates Additional WHERE clause predicates.
788
     *
789
     * @return $this This QueryBuilder instance.
790
     */
791 374
    public function where($predicate, ...$predicates) : self
792
    {
793 374
        $this->where = $this->createPredicate($predicate, ...$predicates);
794
795 374
        $this->state = self::STATE_DIRTY;
796
797 374
        return $this;
798
    }
799
800
    /**
801
     * Adds one or more restrictions to the query results, forming a logical
802
     * conjunction with any previously specified restrictions.
803
     *
804
     * <code>
805
     *     $qb = $conn->createQueryBuilder()
806
     *         ->select('u')
807
     *         ->from('users', 'u')
808
     *         ->where('u.username LIKE ?')
809
     *         ->andWhere('u.is_active = 1');
810
     * </code>
811
     *
812
     * @see where()
813
     *
814
     * @param string|CompositeExpression $predicate     The predicate to append.
815
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
816
     *
817
     * @return $this This QueryBuilder instance.
818
     */
819 132
    public function andWhere($predicate, ...$predicates) : self
820
    {
821 132
        $this->where = $this->appendToPredicate($this->where, CompositeExpression::TYPE_AND, $predicate, ...$predicates);
822
823 132
        $this->state = self::STATE_DIRTY;
824
825 132
        return $this;
826
    }
827
828
    /**
829
     * Adds one or more restrictions to the query results, forming a logical
830
     * disjunction with any previously specified restrictions.
831
     *
832
     * <code>
833
     *     $qb = $em->createQueryBuilder()
834
     *         ->select('u.name')
835
     *         ->from('users', 'u')
836
     *         ->where('u.id = 1')
837
     *         ->orWhere('u.id = 2');
838
     * </code>
839
     *
840
     * @see where()
841
     *
842
     * @param string|CompositeExpression $predicate     The predicate to append.
843
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
844
     *
845
     * @return $this This QueryBuilder instance.
846
     */
847 66
    public function orWhere($predicate, ...$predicates) : self
848
    {
849 66
        $this->where = $this->appendToPredicate($this->where, CompositeExpression::TYPE_OR, $predicate, ...$predicates);
850
851 66
        $this->state = self::STATE_DIRTY;
852
853 66
        return $this;
854
    }
855
856
    /**
857
     * Specifies one or more grouping expressions over the results of the query.
858
     * Replaces any previously specified groupings, if any.
859
     *
860
     * <code>
861
     *     $qb = $conn->createQueryBuilder()
862
     *         ->select('u.name')
863
     *         ->from('users', 'u')
864
     *         ->groupBy('u.id');
865
     * </code>
866
     *
867
     * @param string $expression     The grouping expression
868
     * @param string ...$expressions Additional grouping expressions
869
     *
870
     * @return $this This QueryBuilder instance.
871
     */
872 198
    public function groupBy(string $expression, string ...$expressions) : self
873
    {
874 198
        $this->groupBy = array_merge([$expression], $expressions);
875
876 198
        $this->state = self::STATE_DIRTY;
877
878 198
        return $this;
879
    }
880
881
    /**
882
     * Adds one or more grouping expressions to the query.
883
     *
884
     * <code>
885
     *     $qb = $conn->createQueryBuilder()
886
     *         ->select('u.name')
887
     *         ->from('users', 'u')
888
     *         ->groupBy('u.lastLogin')
889
     *         ->addGroupBy('u.createdAt');
890
     * </code>
891
     *
892
     * @param string $expression     The grouping expression
893
     * @param string ...$expressions Additional grouping expressions
894
     *
895
     * @return $this This QueryBuilder instance.
896
     */
897 44
    public function addGroupBy(string $expression, string ...$expressions) : self
898
    {
899 44
        $this->groupBy = array_merge($this->groupBy, [$expression], $expressions);
900
901 44
        $this->state = self::STATE_DIRTY;
902
903 44
        return $this;
904
    }
905
906
    /**
907
     * Sets a value for a column in an insert query.
908
     *
909
     * <code>
910
     *     $qb = $conn->createQueryBuilder()
911
     *         ->insert('users')
912
     *         ->values(
913
     *             array(
914
     *                 'name' => '?'
915
     *             )
916
     *         )
917
     *         ->setValue('password', '?');
918
     * </code>
919
     *
920
     * @param string $column The column into which the value should be inserted.
921
     * @param string $value  The value that should be inserted into the column.
922
     *
923
     * @return $this This QueryBuilder instance.
924
     */
925 44
    public function setValue(string $column, string $value) : self
926
    {
927 44
        $this->values[$column] = $value;
928
929 44
        return $this;
930
    }
931
932
    /**
933
     * Specifies values for an insert query indexed by column names.
934
     * Replaces any previous values, if any.
935
     *
936
     * <code>
937
     *     $qb = $conn->createQueryBuilder()
938
     *         ->insert('users')
939
     *         ->values(
940
     *             array(
941
     *                 'name' => '?',
942
     *                 'password' => '?'
943
     *             )
944
     *         );
945
     * </code>
946
     *
947
     * @param array<string, mixed> $values The values to specify for the insert query indexed by column names.
948
     *
949
     * @return $this This QueryBuilder instance.
950
     */
951 66
    public function values(array $values) : self
952
    {
953 66
        $this->values = $values;
954
955 66
        $this->state = self::STATE_DIRTY;
956
957 66
        return $this;
958
    }
959
960
    /**
961
     * Specifies a restriction over the groups of the query.
962
     * Replaces any previous having restrictions, if any.
963
     *
964
     * @param string|CompositeExpression $predicate     The HAVING clause predicate.
965
     * @param string|CompositeExpression ...$predicates Additional HAVING clause predicates.
966
     *
967
     * @return $this This QueryBuilder instance.
968
     */
969 88
    public function having($predicate, ...$predicates) : self
970
    {
971 88
        $this->having = $this->createPredicate($predicate, ...$predicates);
972
973 88
        $this->state = self::STATE_DIRTY;
974
975 88
        return $this;
976
    }
977
978
    /**
979
     * Adds a restriction over the groups of the query, forming a logical
980
     * conjunction with any existing having restrictions.
981
     *
982
     * @param string|CompositeExpression $predicate     The predicate to append.
983
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
984
     *
985
     * @return $this This QueryBuilder instance.
986
     */
987 66
    public function andHaving($predicate, ...$predicates) : self
988
    {
989 66
        $this->having = $this->appendToPredicate($this->having, CompositeExpression::TYPE_AND, $predicate, ...$predicates);
990
991 66
        $this->state = self::STATE_DIRTY;
992
993 66
        return $this;
994
    }
995
996
    /**
997
     * Adds a restriction over the groups of the query, forming a logical
998
     * disjunction with any existing having restrictions.
999
     *
1000
     * @param string|CompositeExpression $predicate     The predicate to append.
1001
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
1002
     *
1003
     * @return $this This QueryBuilder instance.
1004
     */
1005 66
    public function orHaving($predicate, ...$predicates) : self
1006
    {
1007 66
        $this->having = $this->appendToPredicate($this->having, CompositeExpression::TYPE_OR, $predicate, ...$predicates);
1008
1009 66
        $this->state = self::STATE_DIRTY;
1010
1011 66
        return $this;
1012
    }
1013
1014
    /**
1015
     * Creates a CompositeExpression from one or more predicates combined by the AND logic.
1016
     *
1017
     * @param string|CompositeExpression $predicate
1018
     * @param string|CompositeExpression ...$predicates
1019
     *
1020
     * @return string|CompositeExpression
1021
     */
1022 462
    private function createPredicate($predicate, ...$predicates)
1023
    {
1024 462
        if (count($predicates) === 0) {
1025 462
            return $predicate;
1026
        }
1027
1028
        return new CompositeExpression(CompositeExpression::TYPE_AND, $predicate, ...$predicates);
1029
    }
1030
1031
    /**
1032
     * Appends the given predicates combined by the given type of logic to the current predicate.
1033
     *
1034
     * @param string|CompositeExpression|null $currentPredicate
1035
     * @param string|CompositeExpression      ...$predicates
1036
     *
1037
     * @return string|CompositeExpression
1038
     */
1039 286
    private function appendToPredicate($currentPredicate, string $type, ...$predicates)
1040
    {
1041 286
        if ($currentPredicate instanceof CompositeExpression && $currentPredicate->getType() === $type) {
1042
            return $currentPredicate->with(...$predicates);
1043
        }
1044
1045 286
        if ($currentPredicate !== null) {
1046 264
            array_unshift($predicates, $currentPredicate);
1047 66
        } elseif (count($predicates) === 1) {
1048 66
            return $predicates[0];
1049
        }
1050
1051 264
        return new CompositeExpression($type, ...$predicates);
1052
    }
1053
1054
    /**
1055
     * Specifies an ordering for the query results.
1056
     * Replaces any previously specified orderings, if any.
1057
     *
1058
     * @param string $sort  The ordering expression.
1059
     * @param string $order The ordering direction.
1060
     *
1061
     * @return $this This QueryBuilder instance.
1062
     */
1063 44
    public function orderBy(string $sort, ?string $order = null) : self
1064
    {
1065 44
        $orderBy = $sort;
1066
1067 44
        if ($order !== null) {
1068
            $orderBy .= ' ' . $order;
1069
        }
1070
1071 44
        $this->orderBy = [$orderBy];
1072
1073 44
        $this->state = self::STATE_DIRTY;
1074
1075 44
        return $this;
1076
    }
1077
1078
    /**
1079
     * Adds an ordering to the query results.
1080
     *
1081
     * @param string $sort  The ordering expression.
1082
     * @param string $order The ordering direction.
1083
     *
1084
     * @return $this This QueryBuilder instance.
1085
     */
1086 44
    public function addOrderBy(string $sort, ?string $order = null) : self
1087
    {
1088 44
        $orderBy = $sort;
1089
1090 44
        if ($order !== null) {
1091 44
            $orderBy .= ' ' . $order;
1092
        }
1093
1094 44
        $this->orderBy[] = $orderBy;
1095
1096 44
        $this->state = self::STATE_DIRTY;
1097
1098 44
        return $this;
1099
    }
1100
1101
    /**
1102
     * @throws QueryException
1103
     */
1104 1012
    private function getSQLForSelect() : string
1105
    {
1106 1012
        if (count($this->select) === 0) {
1107 22
            throw new QueryException('No SELECT expressions given. Please use select() or addSelect().');
1108
        }
1109
1110 990
        $query = 'SELECT';
1111
1112 990
        if ($this->distinct) {
1113 22
            $query .= ' DISTINCT';
1114
        }
1115
1116 990
        $query .= ' ' . implode(', ', $this->select);
1117
1118 990
        if (count($this->from) !== 0) {
1119 968
            $query .= ' FROM ' . implode(', ', $this->getFromClauses());
1120
        }
1121
1122 946
        if ($this->where !== null) {
1123 286
            $query .= ' WHERE ' . $this->where;
1124
        }
1125
1126 946
        if (count($this->groupBy) !== 0) {
1127 198
            $query .= ' GROUP BY ' . implode(', ', $this->groupBy);
1128
        }
1129
1130 946
        if ($this->having !== null) {
1131 132
            $query .= ' HAVING ' . $this->having;
1132
        }
1133
1134 946
        if (count($this->orderBy) !== 0) {
1135 66
            $query .= ' ORDER BY ' . implode(', ', $this->orderBy);
1136
        }
1137
1138 946
        if ($this->isLimitQuery()) {
1139
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1140
                $query,
1141
                $this->maxResults,
1142
                $this->firstResult
1143
            );
1144
        }
1145
1146 946
        return $query;
1147
    }
1148
1149
    /**
1150
     * @return array<string, string>
1151
     */
1152 968
    private function getFromClauses() : array
1153
    {
1154 968
        $fromClauses  = [];
1155 968
        $knownAliases = [];
1156
1157 968
        foreach ($this->from as $from) {
1158 968
            if ($from->alias === null || $from->alias === $from->table) {
1159 154
                $tableSql       = $from->table;
1160 154
                $tableReference = $from->table;
1161
            } else {
1162 836
                $tableSql       = $from->table . ' ' . $from->alias;
1163 836
                $tableReference = $from->alias;
1164
            }
1165
1166 968
            $knownAliases[$tableReference] = true;
1167
1168 968
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1169
        }
1170
1171 946
        $this->verifyAllAliasesAreKnown($knownAliases);
1172
1173 924
        return $fromClauses;
1174
    }
1175
1176
    /**
1177
     * @param array<string, true> $knownAliases
1178
     *
1179
     * @throws QueryException
1180
     */
1181 946
    private function verifyAllAliasesAreKnown(array $knownAliases) : void
1182
    {
1183 946
        foreach ($this->join as $fromAlias => $joins) {
1184 264
            if (! isset($knownAliases[$fromAlias])) {
1185 22
                throw UnknownAlias::new($fromAlias, array_keys($knownAliases));
1186
            }
1187
        }
1188 924
    }
1189
1190 946
    private function isLimitQuery() : bool
1191
    {
1192 946
        return $this->maxResults !== null || $this->firstResult !== 0;
1193
    }
1194
1195
    /**
1196
     * Converts this instance into an INSERT string in SQL.
1197
     */
1198 88
    private function getSQLForInsert() : string
1199
    {
1200 88
        return 'INSERT INTO ' . $this->table .
1201 88
        ' (' . implode(', ', array_keys($this->values)) . ')' .
1202 88
        ' VALUES(' . implode(', ', $this->values) . ')';
1203
    }
1204
1205
    /**
1206
     * Converts this instance into an UPDATE string in SQL.
1207
     */
1208 44
    private function getSQLForUpdate() : string
1209
    {
1210 44
        $query = 'UPDATE ' . $this->table . ' SET ' . implode(', ', $this->set);
1211
1212 44
        if ($this->where !== null) {
1213 22
            $query .= ' WHERE ' . $this->where;
1214
        }
1215
1216 44
        return $query;
1217
    }
1218
1219
    /**
1220
     * Converts this instance into a DELETE string in SQL.
1221
     */
1222 44
    private function getSQLForDelete() : string
1223
    {
1224 44
        $query = 'DELETE FROM ' . $this->table;
1225
1226 44
        if ($this->where !== null) {
1227 22
            $query .= ' WHERE ' . $this->where;
1228
        }
1229
1230 44
        return $query;
1231
    }
1232
1233
    /**
1234
     * Gets a string representation of this QueryBuilder which corresponds to
1235
     * the final SQL query being constructed.
1236
     *
1237
     * @return string The string representation of this QueryBuilder.
1238
     */
1239 1012
    public function __toString() : string
1240
    {
1241 1012
        return $this->getSQL();
1242
    }
1243
1244
    /**
1245
     * Creates a new named parameter and bind the value $value to it.
1246
     *
1247
     * This method provides a shortcut for PDOStatement::bindValue
1248
     * when using prepared statements.
1249
     *
1250
     * The parameter $value specifies the value that you want to bind. If
1251
     * $placeholder is not provided bindValue() will automatically create a
1252
     * placeholder for you. An automatic placeholder will be of the name
1253
     * ':dcValue1', ':dcValue2' etc.
1254
     *
1255
     * For more information see {@link http://php.net/pdostatement-bindparam}
1256
     *
1257
     * Example:
1258
     * <code>
1259
     * $value = 2;
1260
     * $q->eq( 'id', $q->bindValue( $value ) );
1261
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1262
     * </code>
1263
     *
1264
     * @link http://www.zetacomponents.org
1265
     *
1266
     * @param mixed  $value
1267
     * @param mixed  $type
1268
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1269
     *
1270
     * @return string the placeholder name used.
1271
     */
1272 44
    public function createNamedParameter($value, $type = ParameterType::STRING, ?string $placeHolder = null) : string
1273
    {
1274 44
        if ($placeHolder === null) {
1275 22
            $this->boundCounter++;
1276 22
            $placeHolder = ':dcValue' . $this->boundCounter;
1277
        }
1278
1279 44
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1280
1281 44
        return $placeHolder;
1282
    }
1283
1284
    /**
1285
     * Creates a new positional parameter and bind the given value to it.
1286
     *
1287
     * Attention: If you are using positional parameters with the query builder you have
1288
     * to be very careful to bind all parameters in the order they appear in the SQL
1289
     * statement , otherwise they get bound in the wrong order which can lead to serious
1290
     * bugs in your code.
1291
     *
1292
     * Example:
1293
     * <code>
1294
     *  $qb = $conn->createQueryBuilder();
1295
     *  $qb->select('u.*')
1296
     *     ->from('users', 'u')
1297
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1298
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1299
     * </code>
1300
     *
1301
     * @param mixed $value
1302
     */
1303 22
    public function createPositionalParameter($value, int $type = ParameterType::STRING) : string
1304
    {
1305 22
        $this->boundCounter++;
1306 22
        $this->setParameter($this->boundCounter, $value, $type);
1307
1308 22
        return '?';
1309
    }
1310
1311
    /**
1312
     * @param array<string, true> $knownAliases
1313
     *
1314
     * @throws QueryException
1315
     */
1316 968
    private function getSQLForJoins(string $fromAlias, array &$knownAliases) : string
1317
    {
1318 968
        $sql = '';
1319
1320 968
        if (! isset($this->join[$fromAlias])) {
1321 946
            return $sql;
1322
        }
1323
1324 286
        foreach ($this->join[$fromAlias] as $join) {
1325 286
            if (array_key_exists($join->alias, $knownAliases)) {
1326 22
                throw NonUniqueAlias::new($join->alias, array_keys($knownAliases));
1327
            }
1328
1329 264
            $sql .= ' ' . $join->type . ' JOIN ' . $join->table . ' ' . $join->alias;
1330
1331 264
            if ($join->condition !== null) {
1332 242
                $sql .= ' ON ' . $join->condition;
1333
            }
1334
1335 264
            $knownAliases[$join->alias] = true;
1336
        }
1337
1338 264
        foreach ($this->join[$fromAlias] as $join) {
1339 264
            $sql .= $this->getSQLForJoins($join->alias, $knownAliases);
1340
        }
1341
1342 264
        return $sql;
1343
    }
1344
1345
    /**
1346
     * Deep clone of all expression objects in the SQL parts.
1347
     */
1348 22
    public function __clone()
1349
    {
1350 22
        foreach ($this->from as $key => $from) {
1351 22
            $this->from[$key] = clone $from;
1352
        }
1353
1354 22
        foreach ($this->join as $fromAlias => $joins) {
1355
            foreach ($joins as $key => $join) {
1356
                $this->join[$fromAlias][$key] = clone $join;
1357
            }
1358
        }
1359
1360 22
        if (is_object($this->where)) {
1361
            $this->where = clone $this->where;
1362
        }
1363
1364 22
        if (is_object($this->having)) {
1365
            $this->having = clone $this->having;
1366
        }
1367
1368 22
        foreach ($this->params as $name => $param) {
1369 22
            if (! is_object($param)) {
1370
                continue;
1371
            }
1372
1373 22
            $this->params[$name] = clone $param;
1374
        }
1375 22
    }
1376
}
1377