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

QueryBuilder   F

Complexity

Total Complexity 107

Size/Duplication

Total Lines 1338
Duplicated Lines 0 %

Test Coverage

Coverage 92.97%

Importance

Changes 8
Bugs 1 Features 0
Metric Value
wmc 107
eloc 267
c 8
b 1
f 0
dl 0
loc 1338
rs 2
ccs 238
cts 256
cp 0.9297

53 Methods

Rating   Name   Duplication   Size   Complexity  
B getSQL() 0 28 7
A getMaxResults() 0 3 1
A values() 0 7 1
A isLimitQuery() 0 3 2
A join() 0 3 1
A getParameterTypes() 0 3 1
A andWhere() 0 17 4
A setValue() 0 5 1
A addGroupBy() 0 7 1
A getSQLForUpdate() 0 9 2
A __construct() 0 3 1
A getState() 0 3 1
A getConnection() 0 3 1
A select() 0 13 2
A setParameter() 0 9 2
A getFromClauses() 0 23 4
A where() 0 11 3
B __clone() 0 26 8
A getFirstResult() 0 3 1
A verifyAllAliasesAreKnown() 0 5 3
A groupBy() 0 7 1
A getSQLForInsert() 0 5 1
A createPositionalParameter() 0 6 1
A setFirstResult() 0 6 1
A getSQLForJoins() 0 23 5
A leftJoin() 0 7 1
A setMaxResults() 0 6 1
A getParameterType() 0 3 1
A setParameters() 0 6 1
A update() 0 9 1
A innerJoin() 0 7 1
A orderBy() 0 13 2
A execute() 0 7 2
A addOrderBy() 0 13 2
A getParameters() 0 3 1
A delete() 0 9 1
A expr() 0 3 1
A from() 0 7 1
A getSQLForDelete() 0 9 2
A orWhere() 0 17 4
A rightJoin() 0 7 1
A andHaving() 0 17 4
A addSelect() 0 9 1
A __toString() 0 3 1
A createNamedParameter() 0 9 2
A getType() 0 3 1
A distinct() 0 7 1
A set() 0 7 1
A insert() 0 9 1
A having() 0 11 3
B getSQLForSelect() 0 39 8
A getParameter() 0 3 1
A orHaving() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like QueryBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Query;
6
7
use Doctrine\DBAL\Connection;
8
use Doctrine\DBAL\Driver\Statement;
9
use Doctrine\DBAL\ParameterType;
10
use Doctrine\DBAL\Query\Exception\NonUniqueAlias;
11
use Doctrine\DBAL\Query\Exception\UnknownAlias;
12
use Doctrine\DBAL\Query\Expression\CompositeExpression;
13
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
14
use function array_key_exists;
15
use function array_keys;
16
use function array_merge;
17
use function array_unshift;
18
use function count;
19
use function implode;
20
use function is_object;
21
use function substr;
22
23
/**
24
 * QueryBuilder class is responsible to dynamically create SQL queries.
25
 *
26
 * Important: Verify that every feature you use will work with your database vendor.
27
 * SQL Query Builder does not attempt to validate the generated SQL at all.
28
 *
29
 * The query builder does no validation whatsoever if certain features even work with the
30
 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
31
 * even if some vendors such as MySQL support it.
32
 */
33
class QueryBuilder
34
{
35
    /*
36
     * The query types.
37
     */
38
    public const SELECT = 0;
39
    public const DELETE = 1;
40
    public const UPDATE = 2;
41
    public const INSERT = 3;
42
43
    /*
44
     * The builder states.
45
     */
46
    public const STATE_DIRTY = 0;
47
    public const STATE_CLEAN = 1;
48
49
    /**
50
     * The DBAL Connection.
51
     *
52
     * @var Connection
53
     */
54
    private $connection;
55
56
    /**
57
     * The complete SQL string for this query.
58
     *
59
     * @var string
60
     */
61
    private $sql;
62
63
    /**
64
     * The query parameters.
65
     *
66
     * @var array<int, mixed>|array<string, mixed>
67
     */
68
    private $params = [];
69
70
    /**
71
     * The parameter type map of this query.
72
     *
73
     * @var array<int, mixed>|array<string, mixed>
74
     */
75
    private $paramTypes = [];
76
77
    /**
78
     * The type of query this is. Can be select, update or delete.
79
     *
80
     * @var int
81
     */
82
    private $type = self::SELECT;
83
84
    /**
85
     * The state of the query object. Can be dirty or clean.
86
     *
87
     * @var int
88
     */
89
    private $state = self::STATE_CLEAN;
90
91
    /**
92
     * The index of the first result to retrieve.
93
     *
94
     * @var int
95
     */
96
    private $firstResult = 0;
97
98
    /**
99
     * The maximum number of results to retrieve or NULL to retrieve all results.
100
     *
101
     * @var int|null
102
     */
103
    private $maxResults;
104
105
    /**
106
     * The counter of bound parameters used with {@see bindValue).
107
     *
108
     * @var int
109
     */
110
    private $boundCounter = 0;
111
112
    /**
113
     * The SELECT parts of the query.
114
     *
115
     * @var string[]
116
     */
117
    private $select = [];
118
119
    /**
120
     * Whether this is a SELECT DISTINCT query.
121
     *
122
     * @var bool
123
     */
124
    private $distinct = false;
125
126
    /**
127
     * The FROM parts of a SELECT query.
128
     *
129
     * @var From[]
130
     */
131
    private $from = [];
132
133
    /**
134
     * The table name for an INSERT, UPDATE or DELETE query.
135
     *
136
     * @var string|null
137
     */
138
    private $table;
139
140
    /**
141
     * The list of joins, indexed by from alias.
142
     *
143 1620
     * @var array<string, Join[]>
144
     */
145 1620
    private $join = [];
146 1620
147
    /**
148
     * The SET parts of an UPDATE query.
149
     *
150
     * @var string[]
151
     */
152
    private $set = [];
153
154
    /**
155
     * The WHERE part of a SELECT, UPDATE or DELETE query.
156
     *
157
     * @var CompositeExpression|null
158
     */
159
    private $where;
160
161
    /**
162 216
     * The GROUP BY part of a SELECT query.
163
     *
164 216
     * @var string[]
165
     */
166
    private $groupBy = [];
167
168
    /**
169
     * The HAVING part of a SELECT query.
170 189
     *
171
     * @var CompositeExpression|null
172 189
     */
173
    private $having;
174
175
    /**
176
     * The ORDER BY parts of a SELECT query.
177
     *
178 27
     * @var string[]
179
     */
180 27
    private $orderBy = [];
181
182
    /**
183
     * The values of an INSERT query.
184
     *
185
     * @var array<string, mixed>
186
     */
187
    private $values = [];
188 81
189
    /**
190 81
     * Initializes a new <tt>QueryBuilder</tt>.
191
     *
192
     * @param Connection $connection The DBAL Connection.
193
     */
194
    public function __construct(Connection $connection)
195
    {
196
        $this->connection = $connection;
197
    }
198
199
    /**
200
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
201
     * This producer method is intended for convenient inline usage. Example:
202
     *
203
     * <code>
204
     *     $qb = $conn->createQueryBuilder()
205
     *         ->select('u')
206
     *         ->from('users', 'u')
207
     *         ->where($qb->expr()->eq('u.id', 1));
208
     * </code>
209
     *
210
     * For more complex expression construction, consider storing the expression
211
     * builder object in a local variable.
212
     */
213
    public function expr() : ExpressionBuilder
214
    {
215
        return $this->connection->getExpressionBuilder();
216
    }
217
218
    /**
219
     * Gets the type of the currently built query.
220
     */
221
    public function getType() : int
222 1458
    {
223
        return $this->type;
224 1458
    }
225 27
226
    /**
227
     * Gets the associated DBAL Connection for this query builder.
228 1458
     */
229 1458
    public function getConnection() : Connection
230 108
    {
231 108
        return $this->connection;
232 1350
    }
233 54
234 54
    /**
235
     * Gets the state of this query builder instance.
236 1296
     *
237 54
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
238 54
     */
239
    public function getState() : int
240 1242
    {
241
        return $this->state;
242 1242
    }
243 1188
244
    /**
245
     * Executes this query using the bound parameters and their types.
246 1404
     *
247 1404
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
248
     * for insert, update and delete statements.
249 1404
     *
250
     * @return Statement|int
251
     */
252
    public function execute()
253
    {
254
        if ($this->type === self::SELECT) {
255
            return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
256
        }
257
258
        return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes);
259
    }
260
261
    /**
262
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
263
     *
264
     * <code>
265
     *     $qb = $em->createQueryBuilder()
266
     *         ->select('u')
267
     *         ->from('User', 'u')
268
     *     echo $qb->getSQL(); // SELECT u FROM User u
269 162
     * </code>
270
     *
271 162
     * @return string The SQL query string.
272 135
     */
273
    public function getSQL() : string
274
    {
275 162
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
276
            return $this->sql;
277 162
        }
278
279
        switch ($this->type) {
280
            case self::INSERT:
281
                $sql = $this->getSQLForInsert();
282
                break;
283
            case self::DELETE:
284
                $sql = $this->getSQLForDelete();
285
                break;
286
287
            case self::UPDATE:
288
                $sql = $this->getSQLForUpdate();
289
                break;
290
291
            case self::SELECT:
292
            default:
293
                $sql = $this->getSQLForSelect();
294
                break;
295
        }
296
297
        $this->state = self::STATE_CLEAN;
298
        $this->sql   = $sql;
299
300
        return $sql;
301
    }
302
303
    /**
304
     * Sets a query parameter for the query being constructed.
305
     *
306
     * <code>
307
     *     $qb = $conn->createQueryBuilder()
308
     *         ->select('u')
309
     *         ->from('users', 'u')
310
     *         ->where('u.id = :user_id')
311
     *         ->setParameter(':user_id', 1);
312 27
     * </code>
313
     *
314 27
     * @param string|int      $key   The parameter position or name.
315
     * @param mixed           $value The parameter value.
316
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
317
     *
318
     * @return $this This QueryBuilder instance.
319
     */
320
    public function setParameter($key, $value, $type = null) : self
321
    {
322
        if ($type !== null) {
323
            $this->paramTypes[$key] = $type;
324 81
        }
325
326 81
        $this->params[$key] = $value;
327
328
        return $this;
329
    }
330
331
    /**
332
     * Sets a collection of query parameters for the query being constructed.
333
     *
334 27
     * <code>
335
     *     $qb = $conn->createQueryBuilder()
336 27
     *         ->select('u')
337
     *         ->from('users', 'u')
338
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
339
     *         ->setParameters(array(
340
     *             ':user_id1' => 1,
341
     *             ':user_id2' => 2
342
     *         ));
343
     * </code>
344
     *
345
     * @param array<int, mixed>|array<string, mixed> $params The query parameters to set.
346 108
     * @param array<int, mixed>|array<string, mixed> $types  The query parameters types to set.
347
     *
348 108
     * @return $this This QueryBuilder instance.
349
     */
350
    public function setParameters(array $params, array $types = []) : self
351
    {
352
        $this->paramTypes = $types;
353
        $this->params     = $params;
354
355
        return $this;
356
    }
357
358 27
    /**
359
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
360 27
     *
361 27
     * @return array<string|int, mixed> The currently defined query parameters indexed by parameter index or name.
362
     */
363 27
    public function getParameters() : array
364
    {
365
        return $this->params;
366
    }
367
368
    /**
369
     * Gets a (previously set) query parameter of the query being constructed.
370
     *
371
     * @param string|int $key The key (index or name) of the bound parameter.
372 27
     *
373
     * @return mixed The value of the bound parameter.
374 27
     */
375
    public function getParameter($key)
376
    {
377
        return $this->params[$key] ?? null;
378
    }
379
380
    /**
381
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
382
     *
383
     * @return array<string|int, mixed> The currently defined query parameter types indexed by parameter index or name.
384 27
     */
385
    public function getParameterTypes() : array
386 27
    {
387 27
        return $this->paramTypes;
388
    }
389 27
390
    /**
391
     * Gets a (previously set) query parameter type of the query being constructed.
392
     *
393
     * @param string|int $key The key (index or name) of the bound parameter type.
394
     *
395
     * @return mixed The value of the bound parameter type.
396
     */
397
    public function getParameterType($key)
398 27
    {
399
        return $this->paramTypes[$key] ?? null;
400 27
    }
401
402
    /**
403
     * Sets the position of the first result to retrieve (the "offset").
404
     *
405
     * @param int $firstResult The first result to return.
406
     *
407
     * @return $this This QueryBuilder instance.
408
     */
409
    public function setFirstResult(int $firstResult) : self
410
    {
411
        $this->state       = self::STATE_DIRTY;
412
        $this->firstResult = $firstResult;
413 1512
414
        return $this;
415 1512
    }
416 1512
417
    /**
418 1512
     * Gets the position of the first result the query object was set to retrieve (the "offset").
419 1323
     *
420
     * @return int The position of the first result.
421
     */
422 1512
    public function getFirstResult() : int
423
    {
424 1512
        return $this->firstResult;
425 1323
    }
426 1323
427 1323
    /**
428
     * Sets the maximum number of results to retrieve (the "limit").
429 594
     *
430 324
     * @param int|null $maxResults The maximum number of results to retrieve or NULL to retrieve all results.
431 324
     *
432 351
     * @return $this This QueryBuilder instance.
433
     */
434
    public function setMaxResults(?int $maxResults) : self
435 351
    {
436
        $this->state      = self::STATE_DIRTY;
437
        $this->maxResults = $maxResults;
438 1323
439
        return $this;
440
    }
441 1512
442
    /**
443 1512
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
444
     * Returns NULL if all results will be returned.
445
     *
446
     * @return int|null The maximum number of results.
447
     */
448
    public function getMaxResults() : ?int
449
    {
450
        return $this->maxResults;
451
    }
452
453
    /**
454
     * Specifies an item that is to be returned in the query result.
455
     * Replaces any previously specified selections, if any.
456
     *
457
     * <code>
458
     *     $qb = $conn->createQueryBuilder()
459
     *         ->select('u.id', 'p.id')
460
     *         ->from('users', 'u')
461 1323
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
462
     * </code>
463 1323
     *
464
     * @param string ...$expressions The selection expressions.
465 1323
     *
466 27
     * @return $this This QueryBuilder instance.
467
     */
468
    public function select(string ...$expressions) : self
469 1296
    {
470
        $this->type = self::SELECT;
471
472
        if (count($expressions) < 1) {
473
            return $this;
474
        }
475
476
        $this->select = $expressions;
477
478
        $this->state = self::STATE_DIRTY;
479
480
        return $this;
481
    }
482
483
    /**
484 27
     * Adds DISTINCT to the query.
485
     *
486 27
     * <code>
487
     *     $qb = $conn->createQueryBuilder()
488 27
     *         ->select('u.id')
489
     *         ->distinct()
490
     *         ->from('users', 'u')
491
     * </code>
492
     *
493
     * @return $this This QueryBuilder instance.
494
     */
495
    public function distinct() : self
496
    {
497
        $this->distinct = true;
498
499
        $this->state = self::STATE_DIRTY;
500
501
        return $this;
502
    }
503
504
    /**
505
     * Adds an item that is to be returned in the query result.
506
     *
507 54
     * <code>
508
     *     $qb = $conn->createQueryBuilder()
509 54
     *         ->select('u.id')
510
     *         ->addSelect('p.id')
511 54
     *         ->from('users', 'u')
512
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
513
     * </code>
514
     *
515
     * @param string $expression     The selection expression.
516
     * @param string ...$expressions Additional selection expressions.
517
     *
518
     * @return $this This QueryBuilder instance.
519
     */
520
    public function addSelect(string $expression, string ...$expressions) : self
521
    {
522
        $this->type = self::SELECT;
523
524
        $this->select = array_merge($this->select, [$expression], $expressions);
525
526
        $this->state = self::STATE_DIRTY;
527
528
        return $this;
529 54
    }
530
531 54
    /**
532
     * Turns the query being built into a bulk delete query that ranges over
533 54
     * a certain table.
534
     *
535
     * <code>
536
     *     $qb = $conn->createQueryBuilder()
537
     *         ->delete('users', 'u')
538
     *         ->where('u.id = :user_id')
539
     *         ->setParameter(':user_id', 1);
540
     * </code>
541
     *
542
     * @param string $table The table whose rows are subject to the deletion.
543
     *
544
     * @return $this This QueryBuilder instance.
545
     */
546
    public function delete(string $table) : self
547
    {
548
        $this->type = self::DELETE;
549
550
        $this->table = $table;
551 54
552
        $this->state = self::STATE_DIRTY;
553 54
554
        return $this;
555 54
    }
556
557
    /**
558
     * Turns the query being built into a bulk update query that ranges over
559
     * a certain table
560
     *
561
     * <code>
562
     *     $qb = $conn->createQueryBuilder()
563
     *         ->update('counters', 'c')
564
     *         ->set('c.value', 'c.value + 1')
565
     *         ->where('c.id = ?');
566
     * </code>
567
     *
568
     * @param string $table The table whose rows are subject to the update.
569
     *
570
     * @return $this This QueryBuilder instance.
571
     */
572
    public function update(string $table) : self
573
    {
574
        $this->type = self::UPDATE;
575
576
        $this->table = $table;
577 108
578
        $this->state = self::STATE_DIRTY;
579 108
580
        return $this;
581 108
    }
582
583
    /**
584
     * Turns the query being built into an insert query that inserts into
585
     * a certain table
586
     *
587
     * <code>
588
     *     $qb = $conn->createQueryBuilder()
589
     *         ->insert('users')
590
     *         ->values(
591
     *             array(
592
     *                 'name' => '?',
593
     *                 'password' => '?'
594
     *             )
595
     *         );
596
     * </code>
597
     *
598
     * @param string $table The table into which the rows should be inserted.
599 1269
     *
600
     * @return $this This QueryBuilder instance.
601 1269
     */
602
    public function insert(string $table) : self
603
    {
604
        $this->type = self::INSERT;
605
606
        $this->table = $table;
607
608
        $this->state = self::STATE_DIRTY;
609
610
        return $this;
611
    }
612
613
    /**
614
     * Creates and adds a query root corresponding to the table identified by the
615
     * given alias, forming a cartesian product with any existing query roots.
616
     *
617
     * <code>
618
     *     $qb = $conn->createQueryBuilder()
619
     *         ->select('u.id')
620
     *         ->from('users', 'u')
621 108
     * </code>
622
     *
623 108
     * @param string      $table The table.
624
     * @param string|null $alias The alias of the table.
625
     *
626
     * @return $this This QueryBuilder instance.
627
     */
628
    public function from(string $table, ?string $alias = null) : self
629
    {
630
        $this->from[] = new From($table, $alias);
631
632
        $this->state = self::STATE_DIRTY;
633
634
        return $this;
635
    }
636
637
    /**
638
     * Creates and adds a join to the query.
639
     *
640
     * <code>
641
     *     $qb = $conn->createQueryBuilder()
642
     *         ->select('u.name')
643 270
     *         ->from('users', 'u')
644
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
645 270
     * </code>
646 270
     *
647 270
     * @param string $fromAlias The alias that points to a from clause.
648
     * @param string $join      The table name to join.
649
     * @param string $alias     The alias of the join table.
650
     * @param string $condition The condition for the join.
651
     *
652
     * @return $this This QueryBuilder instance.
653
     */
654
    public function join(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
655
    {
656
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
657
    }
658
659
    /**
660
     * Creates and adds a join to the query.
661
     *
662
     * <code>
663
     *     $qb = $conn->createQueryBuilder()
664
     *         ->select('u.name')
665
     *         ->from('users', 'u')
666
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
667 27
     * </code>
668
     *
669 27
     * @param string $fromAlias The alias that points to a from clause.
670 27
     * @param string $join      The table name to join.
671 27
     * @param string $alias     The alias of the join table.
672
     * @param string $condition The condition for the join.
673
     *
674
     * @return $this This QueryBuilder instance.
675
     */
676
    public function innerJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
677
    {
678
        $this->join[$fromAlias][] = Join::inner($join, $alias, $condition);
679
680
        $this->state = self::STATE_DIRTY;
681
682
        return $this;
683
    }
684
685
    /**
686
     * Creates and adds a left join to the query.
687
     *
688
     * <code>
689
     *     $qb = $conn->createQueryBuilder()
690
     *         ->select('u.name')
691 27
     *         ->from('users', 'u')
692
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
693 27
     * </code>
694 27
     *
695 27
     * @param string $fromAlias The alias that points to a from clause.
696
     * @param string $join      The table name to join.
697
     * @param string $alias     The alias of the join table.
698
     * @param string $condition The condition for the join.
699
     *
700
     * @return $this This QueryBuilder instance.
701
     */
702
    public function leftJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
703
    {
704
        $this->join[$fromAlias][] = Join::left($join, $alias, $condition);
705
706
        $this->state = self::STATE_DIRTY;
707
708
        return $this;
709
    }
710
711
    /**
712
     * Creates and adds a right join to the query.
713 54
     *
714
     * <code>
715 54
     *     $qb = $conn->createQueryBuilder()
716
     *         ->select('u.name')
717
     *         ->from('users', 'u')
718
     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
719
     * </code>
720
     *
721
     * @param string $fromAlias The alias that points to a from clause.
722
     * @param string $join      The table name to join.
723
     * @param string $alias     The alias of the join table.
724
     * @param string $condition The condition for the join.
725
     *
726
     * @return $this This QueryBuilder instance.
727
     */
728
    public function rightJoin(string $fromAlias, string $join, string $alias, ?string $condition = null) : self
729
    {
730
        $this->join[$fromAlias][] = Join::right($join, $alias, $condition);
731
732
        $this->state = self::STATE_DIRTY;
733
734
        return $this;
735
    }
736
737
    /**
738
     * Sets a new value for a column in a bulk update query.
739
     *
740
     * <code>
741
     *     $qb = $conn->createQueryBuilder()
742
     *         ->update('counters', 'c')
743
     *         ->set('c.value', 'c.value + 1')
744
     *         ->where('c.id = ?');
745 513
     * </code>
746
     *
747 513
     * @param string $key   The column to set.
748
     * @param string $value The value, expression, placeholder, etc.
749
     *
750
     * @return $this This QueryBuilder instance.
751
     */
752
    public function set(string $key, string $value) : self
753
    {
754
        $this->set[] = $key . ' = ' . $value;
755
756
        $this->state = self::STATE_DIRTY;
757
758
        return $this;
759
    }
760
761
    /**
762
     * Specifies one or more restrictions to the query result.
763
     * Replaces any previously specified restrictions, if any.
764
     *
765
     * <code>
766
     *     $qb = $conn->createQueryBuilder()
767
     *         ->select('c.value')
768
     *         ->from('counters', 'c')
769 162
     *         ->where('c.id = ?');
770
     *
771 162
     *     // You can optionally programmatically build and/or expressions
772
     *     $qb = $conn->createQueryBuilder();
773
     *
774
     *     $or = $qb->expr()->orx();
775
     *     $or->add($qb->expr()->eq('c.id', 1));
776
     *     $or->add($qb->expr()->eq('c.id', 2));
777
     *
778
     *     $qb->update('counters', 'c')
779
     *         ->set('c.value', 'c.value + 1')
780
     *         ->where($or);
781
     * </code>
782
     *
783
     * @param string|CompositeExpression $predicate     The WHERE clause predicate.
784
     * @param string|CompositeExpression ...$predicates Additional WHERE clause predicates.
785
     *
786
     * @return $this This QueryBuilder instance.
787
     */
788
    public function where($predicate, ...$predicates) : self
789
    {
790
        if ($predicate instanceof CompositeExpression && ! $predicates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $predicates of type array<integer,Doctrine\D...ositeExpression|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
791
            $this->where = $predicate;
792
        } else {
793 81
            $this->where = new CompositeExpression(CompositeExpression::TYPE_AND, array_merge([$predicate], $predicates));
794
        }
795 81
796
        $this->state = self::STATE_DIRTY;
797
798
        return $this;
799
    }
800
801
    /**
802
     * Adds one or more restrictions to the query results, forming a logical
803
     * conjunction with any previously specified restrictions.
804
     *
805
     * <code>
806
     *     $qb = $conn->createQueryBuilder()
807
     *         ->select('u')
808
     *         ->from('users', 'u')
809
     *         ->where('u.username LIKE ?')
810
     *         ->andWhere('u.is_active = 1');
811
     * </code>
812
     *
813
     * @see where()
814 243
     *
815
     * @param string|CompositeExpression $predicate     The predicate to append.
816 243
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
817
     *
818
     * @return $this This QueryBuilder instance.
819
     */
820
    public function andWhere($predicate, ...$predicates) : self
821
    {
822
        $allPredicates = array_merge([$predicate], $predicates);
823
824
        if ($this->where !== null && $this->where->getType() === CompositeExpression::TYPE_AND) {
825
            $this->where->addMultiple($allPredicates);
826
        } else {
827
            if ($this->where !== null) {
828
                array_unshift($allPredicates, $this->where);
829
            }
830
831
            $this->where = new CompositeExpression(CompositeExpression::TYPE_AND, $allPredicates);
832
        }
833
834
        $this->state = self::STATE_DIRTY;
835 54
836
        return $this;
837 54
    }
838
839
    /**
840
     * Adds one or more restrictions to the query results, forming a logical
841
     * disjunction with any previously specified restrictions.
842
     *
843
     * <code>
844
     *     $qb = $em->createQueryBuilder()
845
     *         ->select('u.name')
846
     *         ->from('users', 'u')
847
     *         ->where('u.id = 1')
848
     *         ->orWhere('u.id = 2');
849
     * </code>
850
     *
851
     * @see where()
852
     *
853
     * @param string|CompositeExpression $predicate     The predicate to append.
854
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
855
     *
856
     * @return $this This QueryBuilder instance.
857
     */
858
    public function orWhere($predicate, ...$predicates) : self
859 54
    {
860
        $allPredicates = array_merge([$predicate], $predicates);
861 54
862
        if ($this->where !== null && $this->where->getType() === CompositeExpression::TYPE_OR) {
863 54
            $this->where->addMultiple($allPredicates);
864
        } else {
865
            if ($this->where !== null) {
866
                array_unshift($allPredicates, $this->where);
867
            }
868
869
            $this->where = new CompositeExpression(CompositeExpression::TYPE_OR, $allPredicates);
870
        }
871
872
        $this->state = self::STATE_DIRTY;
873
874
        return $this;
875
    }
876
877
    /**
878
     * Specifies one or more grouping expressions over the results of the query.
879
     * Replaces any previously specified groupings, if any.
880
     *
881
     * <code>
882
     *     $qb = $conn->createQueryBuilder()
883
     *         ->select('u.name')
884
     *         ->from('users', 'u')
885 81
     *         ->groupBy('u.id');
886
     * </code>
887 81
     *
888
     * @param string $expression     The grouping expression
889
     * @param string ...$expressions Additional grouping expressions
890
     *
891
     * @return $this This QueryBuilder instance.
892
     */
893
    public function groupBy(string $expression, string ...$expressions) : self
894
    {
895
        $this->groupBy = array_merge([$expression], $expressions);
896
897
        $this->state = self::STATE_DIRTY;
898
899 108
        return $this;
900
    }
901 108
902
    /**
903
     * Adds one or more grouping expressions to the query.
904
     *
905
     * <code>
906
     *     $qb = $conn->createQueryBuilder()
907
     *         ->select('u.name')
908
     *         ->from('users', 'u')
909
     *         ->groupBy('u.lastLogin')
910
     *         ->addGroupBy('u.createdAt');
911
     * </code>
912
     *
913 81
     * @param string $expression     The grouping expression
914
     * @param string ...$expressions Additional grouping expressions
915 81
     *
916
     * @return $this This QueryBuilder instance.
917
     */
918
    public function addGroupBy(string $expression, string ...$expressions) : self
919
    {
920
        $this->groupBy = array_merge($this->groupBy, [$expression], $expressions);
921
922
        $this->state = self::STATE_DIRTY;
923
924
        return $this;
925
    }
926
927 81
    /**
928
     * Sets a value for a column in an insert query.
929 81
     *
930
     * <code>
931
     *     $qb = $conn->createQueryBuilder()
932
     *         ->insert('users')
933
     *         ->values(
934
     *             array(
935
     *                 'name' => '?'
936
     *             )
937
     *         )
938
     *         ->setValue('password', '?');
939
     * </code>
940 621
     *
941
     * @param string $column The column into which the value should be inserted.
942 621
     * @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 621
        $this->values[$column] = $value;
949
950
        return $this;
951 621
    }
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 351
     *             array(
962
     *                 'name' => '?',
963 351
     *                 'password' => '?'
964
     *             )
965 351
     *         );
966 54
     * </code>
967
     *
968 351
     * @param array<string, mixed> $values The values to specify for the insert query indexed by column names.
969 78
     *
970 351
     * @return $this This QueryBuilder instance.
971
     */
972
    public function values(array $values) : self
973
    {
974 351
        $this->values = $values;
975
976
        $this->state = self::STATE_DIRTY;
977
978
        return $this;
979
    }
980
981
    /**
982
     * Specifies a restriction over the groups of the query.
983
     * Replaces any previous having restrictions, if any.
984
     *
985
     * @param string|CompositeExpression $predicate     The HAVING clause predicate.
986 81
     * @param string|CompositeExpression ...$predicates Additional HAVING clause predicates.
987
     *
988 81
     * @return $this This QueryBuilder instance.
989
     */
990
    public function having($predicate, ...$predicates) : self
991
    {
992
        if ($predicate instanceof CompositeExpression && ! $predicates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $predicates of type array<integer,Doctrine\D...ositeExpression|string> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
993
            $this->having = $predicate;
994
        } else {
995
            $this->having = new CompositeExpression(CompositeExpression::TYPE_AND, array_merge([$predicate], $predicates));
996
        }
997
998
        $this->state = self::STATE_DIRTY;
999 54
1000
        return $this;
1001 54
    }
1002
1003
    /**
1004
     * Adds a restriction over the groups of the query, forming a logical
1005
     * conjunction with any existing having restrictions.
1006
     *
1007
     * @param string|CompositeExpression $predicate     The predicate to append.
1008
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
1009 351
     *
1010
     * @return $this This QueryBuilder instance.
1011 351
     */
1012
    public function andHaving($predicate, ...$predicates) : self
1013
    {
1014
        $allPredicates = array_merge([$predicate], $predicates);
1015
1016
        if ($this->having !== null && $this->having->getType() === CompositeExpression::TYPE_AND) {
1017
            $this->having->addMultiple($allPredicates);
1018
        } else {
1019 27
            if ($this->having !== null) {
1020
                array_unshift($allPredicates, $this->having);
1021 27
            }
1022
1023
            $this->having = new CompositeExpression(CompositeExpression::TYPE_AND, $allPredicates);
1024
        }
1025
1026
        $this->state = self::STATE_DIRTY;
1027
1028
        return $this;
1029
    }
1030
1031 27
    /**
1032
     * Adds a restriction over the groups of the query, forming a logical
1033 27
     * disjunction with any existing having restrictions.
1034
     *
1035
     * @param string|CompositeExpression $predicate     The predicate to append.
1036
     * @param string|CompositeExpression ...$predicates Additional predicates to append.
1037 27
     *
1038 27
     * @return $this This QueryBuilder instance.
1039
     */
1040
    public function orHaving($predicate, ...$predicates) : self
1041 27
    {
1042
        $allPredicates = array_merge([$predicate], $predicates);
1043
1044
        if ($this->having !== null && $this->having->getType() === CompositeExpression::TYPE_OR) {
1045
            $this->having->addMultiple($allPredicates);
1046
        } else {
1047
            if ($this->having !== null) {
1048
                array_unshift($allPredicates, $this->having);
1049 54
            }
1050
1051 54
            $this->having = new CompositeExpression(CompositeExpression::TYPE_OR, $allPredicates);
1052
        }
1053 54
1054
        $this->state = self::STATE_DIRTY;
1055 54
1056
        return $this;
1057
    }
1058
1059
    /**
1060
     * Specifies an ordering for the query results.
1061 1242
     * Replaces any previously specified orderings, if any.
1062
     *
1063 1242
     * @param string $sort  The ordering expression.
1064 1242
     * @param string $order The ordering direction.
1065
     *
1066 1242
     * @return $this This QueryBuilder instance.
1067 1188
     */
1068 1188
    public function orderBy(string $sort, ?string $order = null) : self
1069 1188
    {
1070 1188
        $orderBy = $sort;
1071
1072 1188
        if ($order !== null) {
1073
            $orderBy .= ' ' . $order;
1074
        }
1075
1076
        $this->orderBy = [$orderBy];
1077
1078
        $this->state = self::STATE_DIRTY;
1079
1080 1188
        return $this;
1081
    }
1082
1083
    /**
1084
     * Adds an ordering to the query results.
1085
     *
1086 1215
     * @param string $sort  The ordering expression.
1087
     * @param string $order The ordering direction.
1088 1215
     *
1089 1215
     * @return $this This QueryBuilder instance.
1090
     */
1091
    public function addOrderBy(string $sort, ?string $order = null) : self
1092
    {
1093 1215
        $orderBy = $sort;
1094 1215
1095 189
        if ($order !== null) {
1096 189
            $orderBy .= ' ' . $order;
1097
        }
1098 1053
1099 1053
        $this->orderBy[] = $orderBy;
1100
1101
        $this->state = self::STATE_DIRTY;
1102 1215
1103
        return $this;
1104 1215
    }
1105
1106
    /**
1107 1188
     * @throws QueryException
1108
     */
1109 1161
    private function getSQLForSelect() : string
1110
    {
1111
        $query = 'SELECT ';
1112
1113
        if ($this->distinct) {
1114
            $query .= 'DISTINCT ';
1115
        }
1116
1117 1188
        $query .= implode(', ', $this->select);
1118
1119 1188
        if ($this->from) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->from of type Doctrine\DBAL\Query\From[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1120 297
            $query .= ' FROM ' . implode(', ', $this->getFromClauses());
1121 27
        }
1122
1123
        if ($this->where) {
1124 1161
            $query .= ' WHERE ' . $this->where;
1125
        }
1126 1188
1127
        if ($this->groupBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->groupBy of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1128 1188
            $query .= ' GROUP BY ' . implode(', ', $this->groupBy);
1129
        }
1130
1131
        if ($this->having) {
1132
            $query .= ' HAVING ' . $this->having;
1133
        }
1134 108
1135
        if ($this->orderBy) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orderBy of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1136 108
            $query .= ' ORDER BY ' . implode(', ', $this->orderBy);
1137 108
        }
1138 108
1139
        if ($this->isLimitQuery()) {
1140
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1141
                $query,
1142
                $this->maxResults,
1143
                $this->firstResult
1144 54
            );
1145
        }
1146 54
1147 54
        return $query;
1148 54
    }
1149
1150
    /**
1151
     * @return array<string, string>
1152
     */
1153
    private function getFromClauses() : array
1154 54
    {
1155
        $fromClauses  = [];
1156 54
        $knownAliases = [];
1157
1158
        // Loop through all FROM clauses
1159
        foreach ($this->from as $from) {
1160
            if ($from->alias === null || $from->alias === $from->table) {
1161
                $tableSql       = $from->table;
1162
                $tableReference = $from->table;
1163
            } else {
1164
                $tableSql       = $from->table . ' ' . $from->alias;
1165 1269
                $tableReference = $from->alias;
1166
            }
1167 1269
1168
            $knownAliases[$tableReference] = true;
1169
1170
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1171
        }
1172
1173
        $this->verifyAllAliasesAreKnown($knownAliases);
1174
1175
        return $fromClauses;
1176
    }
1177
1178
    /**
1179
     * @param array<string, true> $knownAliases
1180
     *
1181
     * @throws QueryException
1182
     */
1183
    private function verifyAllAliasesAreKnown(array $knownAliases) : void
1184
    {
1185
        foreach ($this->join as $fromAlias => $joins) {
1186
            if (! isset($knownAliases[$fromAlias])) {
1187
                throw UnknownAlias::new($fromAlias, array_keys($knownAliases));
1188
            }
1189
        }
1190
    }
1191
1192
    private function isLimitQuery() : bool
1193
    {
1194
        return $this->maxResults !== null || $this->firstResult !== 0;
1195
    }
1196
1197
    /**
1198 54
     * Converts this instance into an INSERT string in SQL.
1199
     */
1200 54
    private function getSQLForInsert() : string
1201 27
    {
1202 27
        return 'INSERT INTO ' . $this->table .
1203
        ' (' . implode(', ', array_keys($this->values)) . ')' .
1204 54
        ' VALUES(' . implode(', ', $this->values) . ')';
1205
    }
1206 54
1207
    /**
1208
     * Converts this instance into an UPDATE string in SQL.
1209
     */
1210
    private function getSQLForUpdate() : string
1211
    {
1212
        $query = 'UPDATE ' . $this->table . ' SET ' . implode(', ', $this->set);
1213
1214
        if ($this->where !== null) {
1215
            $query .= ' WHERE ' . $this->where;
1216
        }
1217
1218
        return $query;
1219
    }
1220
1221
    /**
1222
     * Converts this instance into a DELETE string in SQL.
1223
     */
1224
    private function getSQLForDelete() : string
1225
    {
1226
        $query = 'DELETE FROM ' . $this->table;
1227
1228 27
        if ($this->where !== null) {
1229
            $query .= ' WHERE ' . $this->where;
1230 27
        }
1231 27
1232
        return $query;
1233 27
    }
1234
1235
    /**
1236
     * Gets a string representation of this QueryBuilder which corresponds to
1237
     * the final SQL query being constructed.
1238
     *
1239
     * @return string The string representation of this QueryBuilder.
1240
     */
1241 1215
    public function __toString() : string
1242
    {
1243 1215
        return $this->getSQL();
1244
    }
1245 1215
1246
    /**
1247 324
     * Creates a new named parameter and bind the value $value to it.
1248 324
     *
1249 27
     * This method provides a shortcut for PDOStatement::bindValue
1250
     * when using prepared statements.
1251 297
     *
1252 297
     * The parameter $value specifies the value that you want to bind. If
1253 297
     * $placeholder is not provided bindValue() will automatically create a
1254 297
     * placeholder for you. An automatic placeholder will be of the name
1255
     * ':dcValue1', ':dcValue2' etc.
1256
     *
1257 297
     * For more information see {@link http://php.net/pdostatement-bindparam}
1258
     *
1259 297
     * Example:
1260
     * <code>
1261
     * $value = 2;
1262
     * $q->eq( 'id', $q->bindValue( $value ) );
1263 1188
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1264
     * </code>
1265
     *
1266
     * @link http://www.zetacomponents.org
1267
     *
1268
     * @param mixed  $value
1269 27
     * @param mixed  $type
1270
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1271 27
     *
1272 27
     * @return string the placeholder name used.
1273 27
     */
1274 27
    public function createNamedParameter($value, $type = ParameterType::STRING, ?string $placeHolder = null) : string
1275 27
    {
1276
        if ($placeHolder === null) {
1277
            $this->boundCounter++;
1278 27
            $placeHolder = ':dcValue' . $this->boundCounter;
1279
        }
1280 27
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1281
1282
        return $placeHolder;
1283
    }
1284
1285 27
    /**
1286 27
     * Creates a new positional parameter and bind the given value to it.
1287
     *
1288
     * Attention: If you are using positional parameters with the query builder you have
1289
     * to be very careful to bind all parameters in the order they appear in the SQL
1290 27
     * statement , otherwise they get bound in the wrong order which can lead to serious
1291
     * bugs in your code.
1292 27
     *
1293
     * Example:
1294
     * <code>
1295
     *  $qb = $conn->createQueryBuilder();
1296
     *  $qb->select('u.*')
1297
     *     ->from('users', 'u')
1298
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1299
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1300
     * </code>
1301
     *
1302
     * @param mixed $value
1303
     */
1304
    public function createPositionalParameter($value, int $type = ParameterType::STRING) : string
1305
    {
1306
        $this->boundCounter++;
1307
        $this->setParameter($this->boundCounter, $value, $type);
1308
1309
        return '?';
1310
    }
1311
1312
    /**
1313
     * @param array<string, true> $knownAliases
1314
     *
1315
     * @throws QueryException
1316
     */
1317
    private function getSQLForJoins(string $fromAlias, array &$knownAliases) : string
1318
    {
1319
        $sql = '';
1320
1321
        if (! isset($this->join[$fromAlias])) {
1322
            return $sql;
1323
        }
1324
1325
        foreach ($this->join[$fromAlias] as $join) {
1326
            if (array_key_exists($join->alias, $knownAliases)) {
1327
                throw NonUniqueAlias::new($join->alias, array_keys($knownAliases));
1328
            }
1329
            $sql                       .= ' ' . $join->type
1330
                . ' JOIN ' . $join->table . ' ' . $join->alias
1331
                . ' ON ' . ((string) $join->condition);
1332
            $knownAliases[$join->alias] = true;
1333
        }
1334
1335
        foreach ($this->join[$fromAlias] as $join) {
1336
            $sql .= $this->getSQLForJoins($join->alias, $knownAliases);
1337
        }
1338
1339
        return $sql;
1340
    }
1341
1342
    /**
1343
     * Deep clone of all expression objects in the SQL parts.
1344
     */
1345
    public function __clone()
1346
    {
1347
        foreach ($this->from as $key => $from) {
1348
            $this->from[$key] = clone $from;
1349
        }
1350
1351
        foreach ($this->join as $fromAlias => $joins) {
1352
            foreach ($joins as $key => $join) {
1353
                $this->join[$fromAlias][$key] = clone $join;
1354
            }
1355
        }
1356
1357
        if ($this->where !== null) {
1358
            $this->where = clone $this->where;
1359
        }
1360
1361
        if ($this->having !== null) {
1362
            $this->having = clone $this->having;
1363
        }
1364
1365
        foreach ($this->params as $name => $param) {
1366
            if (! is_object($param)) {
1367
                continue;
1368
            }
1369
1370
            $this->params[$name] = clone $param;
1371
        }
1372
    }
1373
}
1374