Completed
Pull Request — master (#3829)
by Benjamin
65:10
created

QueryBuilder::__clone()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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