Completed
Pull Request — master (#3836)
by Benjamin
64:22
created

QueryBuilder::andHaving()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

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