Completed
Push — master ( 9728d9...4d9a08 )
by Sergei
27s queued 16s
created

QueryBuilder::getFromClauses()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

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