Completed
Pull Request — master (#3340)
by Michael
63:48
created

QueryBuilder   F

Complexity

Total Complexity 134

Size/Duplication

Total Lines 1343
Duplicated Lines 0 %

Test Coverage

Coverage 95.35%

Importance

Changes 0
Metric Value
wmc 134
eloc 281
dl 0
loc 1343
rs 2
c 0
b 0
f 0
ccs 287
cts 301
cp 0.9535

59 Methods

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

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
namespace Doctrine\DBAL\Query;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Driver\Statement;
7
use Doctrine\DBAL\ParameterType;
8
use Doctrine\DBAL\Query\Expression\CompositeExpression;
9
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
10
use function array_key_exists;
11
use function array_keys;
12
use function array_unshift;
13
use function func_get_args;
14
use function func_num_args;
15
use function implode;
16
use function is_array;
17
use function is_object;
18
use function key;
19
use function strtoupper;
20
use function substr;
21
22
/**
23
 * QueryBuilder class is responsible to dynamically create SQL queries.
24
 *
25
 * Important: Verify that every feature you use will work with your database vendor.
26
 * SQL Query Builder does not attempt to validate the generated SQL at all.
27
 *
28
 * The query builder does no validation whatsoever if certain features even work with the
29
 * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements
30
 * even if some vendors such as MySQL support it.
31
 */
32
class QueryBuilder
33
{
34
    /*
35
     * The query types.
36
     */
37
    public const SELECT = 0;
38
    public const DELETE = 1;
39
    public const UPDATE = 2;
40
    public const INSERT = 3;
41
42
    /*
43
     * The builder states.
44
     */
45
    public const STATE_DIRTY = 0;
46
    public const STATE_CLEAN = 1;
47
48
    /**
49
     * The DBAL Connection.
50
     *
51
     * @var Connection
52
     */
53
    private $connection;
54
55
    /**
56
     * The array of SQL parts collected.
57
     *
58
     * @var mixed[]
59
     */
60
    private $sqlParts = [
61
        'select'  => [],
62
        'from'    => [],
63
        'join'    => [],
64
        'set'     => [],
65
        'where'   => null,
66
        'groupBy' => [],
67
        'having'  => null,
68
        'orderBy' => [],
69
        'values'  => [],
70
    ];
71
72
    /**
73
     * The complete SQL string for this query.
74
     *
75
     * @var string
76
     */
77
    private $sql;
78
79
    /**
80
     * The query parameters.
81
     *
82
     * @var mixed[]
83
     */
84
    private $params = [];
85
86
    /**
87
     * The parameter type map of this query.
88
     *
89
     * @var int[]|string[]
90
     */
91
    private $paramTypes = [];
92
93
    /**
94
     * The type of query this is. Can be select, update or delete.
95
     *
96
     * @var int
97
     */
98
    private $type = self::SELECT;
99
100
    /**
101
     * The state of the query object. Can be dirty or clean.
102
     *
103
     * @var int
104
     */
105
    private $state = self::STATE_CLEAN;
106
107
    /**
108
     * The index of the first result to retrieve.
109
     *
110
     * @var int
111
     */
112
    private $firstResult = null;
113
114
    /**
115
     * The maximum number of results to retrieve.
116
     *
117
     * @var int
118
     */
119
    private $maxResults = null;
120
121
    /**
122
     * The counter of bound parameters used with {@see bindValue).
123
     *
124
     * @var int
125
     */
126
    private $boundCounter = 0;
127
128
    /**
129
     * Whether or not to include a distinct flag in the SELECT clause
130
     *
131
     * @var bool
132
     */
133 1254
    private $isDistinct = false;
134
135 1254
    /**
136 1254
     * Initializes a new <tt>QueryBuilder</tt>.
137
     *
138
     * @param Connection $connection The DBAL Connection.
139
     */
140
    public function __construct(Connection $connection)
141
    {
142
        $this->connection = $connection;
143
    }
144
145
    /**
146
     * Gets an ExpressionBuilder used for object-oriented construction of query expressions.
147
     * This producer method is intended for convenient inline usage. Example:
148
     *
149
     * <code>
150
     *     $qb = $conn->createQueryBuilder()
151
     *         ->select('u')
152
     *         ->from('users', 'u')
153
     *         ->where($qb->expr()->eq('u.id', 1));
154 532
     * </code>
155
     *
156 532
     * For more complex expression construction, consider storing the expression
157
     * builder object in a local variable.
158
     *
159
     * @return ExpressionBuilder
160
     */
161
    public function expr()
162
    {
163
        return $this->connection->getExpressionBuilder();
164 228
    }
165
166 228
    /**
167
     * Gets the type of the currently built query.
168
     *
169
     * @return int
170
     */
171
    public function getType()
172
    {
173
        return $this->type;
174 19
    }
175
176 19
    /**
177
     * Gets the associated DBAL Connection for this query builder.
178
     *
179
     * @return Connection
180
     */
181
    public function getConnection()
182
    {
183
        return $this->connection;
184 57
    }
185
186 57
    /**
187
     * Gets the state of this query builder instance.
188
     *
189
     * @return int Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN.
190
     */
191
    public function getState()
192
    {
193
        return $this->state;
194
    }
195
196
    /**
197
     * Executes this query using the bound parameters and their types.
198
     *
199
     * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate}
200
     * for insert, update and delete statements.
201
     *
202
     * @return Statement|int
203
     */
204
    public function execute()
205
    {
206
        if ($this->type === self::SELECT) {
207
            return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes);
208
        }
209
210
        return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes);
211
    }
212
213
    /**
214
     * Gets the complete SQL string formed by the current specifications of this QueryBuilder.
215
     *
216
     * <code>
217
     *     $qb = $em->createQueryBuilder()
218 1064
     *         ->select('u')
219
     *         ->from('User', 'u')
220 1064
     *     echo $qb->getSQL(); // SELECT u FROM User u
221 19
     * </code>
222
     *
223
     * @return string The SQL query string.
224 1064
     */
225 1064
    public function getSQL()
226 76
    {
227 76
        if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
228 988
            return $this->sql;
229 57
        }
230 57
231
        switch ($this->type) {
232 931
            case self::INSERT:
233 57
                $sql = $this->getSQLForInsert();
234 57
                break;
235
            case self::DELETE:
236 874
                $sql = $this->getSQLForDelete();
237
                break;
238 874
239 836
            case self::UPDATE:
240
                $sql = $this->getSQLForUpdate();
241
                break;
242 1026
243 1026
            case self::SELECT:
244
            default:
245 1026
                $sql = $this->getSQLForSelect();
246
                break;
247
        }
248
249
        $this->state = self::STATE_CLEAN;
250
        $this->sql   = $sql;
251
252
        return $sql;
253
    }
254
255
    /**
256
     * Sets a query parameter for the query being constructed.
257
     *
258
     * <code>
259
     *     $qb = $conn->createQueryBuilder()
260
     *         ->select('u')
261
     *         ->from('users', 'u')
262
     *         ->where('u.id = :user_id')
263
     *         ->setParameter(':user_id', 1);
264
     * </code>
265 114
     *
266
     * @param string|int      $key   The parameter position or name.
267 114
     * @param mixed           $value The parameter value.
268 95
     * @param string|int|null $type  One of the {@link \Doctrine\DBAL\ParameterType} constants.
269
     *
270
     * @return $this This QueryBuilder instance.
271 114
     */
272
    public function setParameter($key, $value, $type = null)
273 114
    {
274
        if ($type !== null) {
275
            $this->paramTypes[$key] = $type;
276
        }
277
278
        $this->params[$key] = $value;
279
280
        return $this;
281
    }
282
283
    /**
284
     * Sets a collection of query parameters for the query being constructed.
285
     *
286
     * <code>
287
     *     $qb = $conn->createQueryBuilder()
288
     *         ->select('u')
289
     *         ->from('users', 'u')
290
     *         ->where('u.id = :user_id1 OR u.id = :user_id2')
291
     *         ->setParameters(array(
292
     *             ':user_id1' => 1,
293
     *             ':user_id2' => 2
294
     *         ));
295
     * </code>
296
     *
297
     * @param mixed[]        $params The query parameters to set.
298
     * @param int[]|string[] $types  The query parameters types to set.
299
     *
300
     * @return $this This QueryBuilder instance.
301
     */
302
    public function setParameters(array $params, array $types = [])
303
    {
304
        $this->paramTypes = $types;
305
        $this->params     = $params;
306
307
        return $this;
308 19
    }
309
310 19
    /**
311
     * Gets all defined query parameters for the query being constructed indexed by parameter index or name.
312
     *
313
     * @return mixed[] The currently defined query parameters indexed by parameter index or name.
314
     */
315
    public function getParameters()
316
    {
317
        return $this->params;
318
    }
319
320 57
    /**
321
     * Gets a (previously set) query parameter of the query being constructed.
322 57
     *
323
     * @param mixed $key The key (index or name) of the bound parameter.
324
     *
325
     * @return mixed The value of the bound parameter.
326
     */
327
    public function getParameter($key)
328
    {
329
        return $this->params[$key] ?? null;
330 19
    }
331
332 19
    /**
333
     * Gets all defined query parameter types for the query being constructed indexed by parameter index or name.
334
     *
335
     * @return int[]|string[] The currently defined query parameter types indexed by parameter index or name.
336
     */
337
    public function getParameterTypes()
338
    {
339
        return $this->paramTypes;
340
    }
341
342 76
    /**
343
     * Gets a (previously set) query parameter type of the query being constructed.
344 76
     *
345
     * @param mixed $key The key (index or name) of the bound parameter type.
346
     *
347
     * @return mixed The value of the bound parameter type.
348
     */
349
    public function getParameterType($key)
350
    {
351
        return $this->paramTypes[$key] ?? null;
352
    }
353
354 19
    /**
355
     * Sets the position of the first result to retrieve (the "offset").
356 19
     *
357 19
     * @param int $firstResult The first result to return.
358
     *
359 19
     * @return $this This QueryBuilder instance.
360
     */
361
    public function setFirstResult($firstResult)
362
    {
363
        $this->state       = self::STATE_DIRTY;
364
        $this->firstResult = $firstResult;
365
366
        return $this;
367
    }
368 19
369
    /**
370 19
     * Gets the position of the first result the query object was set to retrieve (the "offset").
371
     * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder.
372
     *
373
     * @return int The position of the first result.
374
     */
375
    public function getFirstResult()
376
    {
377
        return $this->firstResult;
378
    }
379
380 19
    /**
381
     * Sets the maximum number of results to retrieve (the "limit").
382 19
     *
383 19
     * @param int $maxResults The maximum number of results to retrieve.
384
     *
385 19
     * @return $this This QueryBuilder instance.
386
     */
387
    public function setMaxResults($maxResults)
388
    {
389
        $this->state      = self::STATE_DIRTY;
390
        $this->maxResults = $maxResults;
391
392
        return $this;
393
    }
394 19
395
    /**
396 19
     * Gets the maximum number of results the query object was set to retrieve (the "limit").
397
     * Returns NULL if {@link setMaxResults} was not applied to this query builder.
398
     *
399
     * @return int The maximum number of results.
400
     */
401
    public function getMaxResults()
402
    {
403
        return $this->maxResults;
404
    }
405
406
    /**
407
     * Sets the flag to only retrieve distinct results
408
     *
409
     * @return $this This QueryBuilder instance.
410
     */
411 1102
    public function distinct()
412
    {
413 1102
        $this->state      = self::STATE_DIRTY;
414 1102
        $this->isDistinct = true;
415
416 1102
        return $this;
417 133
    }
418
419
    /**
420 1102
     * Returns whether or not the query object is set to return only distinct results
421
     *
422 1102
     * @return bool
423 950
     */
424 171
    public function getDistinct()
425 171
    {
426
        return $this->isDistinct;
427 893
    }
428 228
429 228
    /**
430 893
     * Either appends to or replaces a single, generic query part.
431 893
     *
432
     * The available parts are: 'select', 'from', 'set', 'where',
433 152
     * 'groupBy', 'having' and 'orderBy'.
434
     *
435
     * @param string $sqlPartName
436 950
     * @param string $sqlPart
437
     * @param bool   $append
438
     *
439 1102
     * @return $this This QueryBuilder instance.
440
     */
441 1102
    public function add($sqlPartName, $sqlPart, $append = false)
442
    {
443
        $isArray    = is_array($sqlPart);
444
        $isMultiple = is_array($this->sqlParts[$sqlPartName]);
445
446
        if ($isMultiple && ! $isArray) {
447
            $sqlPart = [$sqlPart];
448
        }
449
450
        $this->state = self::STATE_DIRTY;
451
452
        if ($append) {
453
            if ($sqlPartName === 'orderBy' || $sqlPartName === 'groupBy' || $sqlPartName === 'select' || $sqlPartName === 'set') {
454
                foreach ($sqlPart as $part) {
455
                    $this->sqlParts[$sqlPartName][] = $part;
456
                }
457
            } elseif ($isArray && is_array($sqlPart[key($sqlPart)])) {
0 ignored issues
show
Bug introduced by
It seems like $sqlPart can also be of type string; however, parameter $array of key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

457
            } elseif ($isArray && is_array($sqlPart[key(/** @scrutinizer ignore-type */ $sqlPart)])) {
Loading history...
458
                $key                                  = key($sqlPart);
459 931
                $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key];
460
            } elseif ($isMultiple) {
461 931
                $this->sqlParts[$sqlPartName][] = $sqlPart;
462
            } else {
463 931
                $this->sqlParts[$sqlPartName] = $sqlPart;
464 19
            }
465
466
            return $this;
467 912
        }
468
469 912
        $this->sqlParts[$sqlPartName] = $sqlPart;
470
471
        return $this;
472
    }
473
474
    /**
475
     * Specifies an item that is to be returned in the query result.
476
     * Replaces any previously specified selections, if any.
477
     *
478
     * <code>
479
     *     $qb = $conn->createQueryBuilder()
480
     *         ->select('u.id', 'p.id')
481
     *         ->from('users', 'u')
482
     *         ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
483
     * </code>
484
     *
485
     * @param mixed $select The selection expressions.
486
     *
487 57
     * @return $this This QueryBuilder instance.
488
     */
489 57
    public function select($select = null)
490
    {
491 57
        $this->type = self::SELECT;
492 19
493
        if (empty($select)) {
494
            return $this;
495 38
        }
496
497 38
        $selects = is_array($select) ? $select : func_get_args();
498
499
        return $this->add('select', $selects);
0 ignored issues
show
Bug introduced by
$selects of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

499
        return $this->add('select', /** @scrutinizer ignore-type */ $selects);
Loading history...
500
    }
501
502
    /**
503
     * Adds an item that is to be returned in the query result.
504
     *
505
     * <code>
506
     *     $qb = $conn->createQueryBuilder()
507
     *         ->select('u.id')
508
     *         ->addSelect('p.id')
509
     *         ->from('users', 'u')
510
     *         ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id');
511
     * </code>
512
     *
513
     * @param mixed $select The selection expression.
514
     *
515
     * @return $this This QueryBuilder instance.
516 76
     */
517
    public function addSelect($select = null)
518 76
    {
519
        $this->type = self::SELECT;
520 76
521 19
        if (empty($select)) {
522
            return $this;
523
        }
524 57
525 57
        $selects = is_array($select) ? $select : func_get_args();
526 57
527
        return $this->add('select', $selects, true);
0 ignored issues
show
Bug introduced by
$selects of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

527
        return $this->add('select', /** @scrutinizer ignore-type */ $selects, true);
Loading history...
528
    }
529
530
    /**
531
     * Turns the query being built into a bulk delete query that ranges over
532
     * a certain table.
533
     *
534
     * <code>
535
     *     $qb = $conn->createQueryBuilder()
536
     *         ->delete('users', 'u')
537
     *         ->where('u.id = :user_id');
538
     *         ->setParameter(':user_id', 1);
539
     * </code>
540
     *
541
     * @param string $delete The table whose rows are subject to the deletion.
542
     * @param string $alias  The table alias used in the constructed query.
543
     *
544
     * @return $this This QueryBuilder instance.
545
     */
546 76
    public function delete($delete = null, $alias = null)
547
    {
548 76
        $this->type = self::DELETE;
549
550 76
        if (! $delete) {
551 19
            return $this;
552
        }
553
554 57
        return $this->add('from', [
0 ignored issues
show
Bug introduced by
array('table' => $delete, 'alias' => $alias) of type array<string,null|string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

554
        return $this->add('from', /** @scrutinizer ignore-type */ [
Loading history...
555 57
            'table' => $delete,
556 57
            'alias' => $alias,
557
        ]);
558
    }
559
560
    /**
561
     * Turns the query being built into a bulk update query that ranges over
562
     * a certain table
563
     *
564
     * <code>
565
     *     $qb = $conn->createQueryBuilder()
566
     *         ->update('counters', 'c')
567
     *         ->set('c.value', 'c.value + 1')
568
     *         ->where('c.id = ?');
569
     * </code>
570
     *
571
     * @param string $update The table whose rows are subject to the update.
572
     * @param string $alias  The table alias used in the constructed query.
573
     *
574
     * @return $this This QueryBuilder instance.
575
     */
576
    public function update($update = null, $alias = null)
577
    {
578
        $this->type = self::UPDATE;
579 95
580
        if (! $update) {
581 95
            return $this;
582
        }
583 95
584 19
        return $this->add('from', [
0 ignored issues
show
Bug introduced by
array('table' => $update, 'alias' => $alias) of type array<string,null|string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

584
        return $this->add('from', /** @scrutinizer ignore-type */ [
Loading history...
585
            'table' => $update,
586
            'alias' => $alias,
587 76
        ]);
588
    }
589
590
    /**
591
     * Turns the query being built into an insert query that inserts into
592
     * a certain table
593
     *
594
     * <code>
595
     *     $qb = $conn->createQueryBuilder()
596
     *         ->insert('users')
597
     *         ->values(
598
     *             array(
599
     *                 'name' => '?',
600
     *                 'password' => '?'
601
     *             )
602
     *         );
603
     * </code>
604
     *
605 893
     * @param string $insert The table into which the rows should be inserted.
606
     *
607 893
     * @return $this This QueryBuilder instance.
608 893
     */
609 893
    public function insert($insert = null)
610 893
    {
611
        $this->type = self::INSERT;
612
613
        if (! $insert) {
614
            return $this;
615
        }
616
617
        return $this->add('from', ['table' => $insert]);
0 ignored issues
show
Bug introduced by
array('table' => $insert) of type array<string,string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

617
        return $this->add('from', /** @scrutinizer ignore-type */ ['table' => $insert]);
Loading history...
618
    }
619
620
    /**
621
     * Creates and adds a query root corresponding to the table identified by the
622
     * given alias, forming a cartesian product with any existing query roots.
623
     *
624
     * <code>
625
     *     $qb = $conn->createQueryBuilder()
626
     *         ->select('u.id')
627
     *         ->from('users', 'u')
628
     * </code>
629
     *
630 76
     * @param string      $from  The table.
631
     * @param string|null $alias The alias of the table.
632 76
     *
633
     * @return $this This QueryBuilder instance.
634
     */
635
    public function from($from, $alias = null)
636
    {
637
        return $this->add('from', [
0 ignored issues
show
Bug introduced by
array('table' => $from, 'alias' => $alias) of type array<string,null|string> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

637
        return $this->add('from', /** @scrutinizer ignore-type */ [
Loading history...
638
            'table' => $from,
639
            'alias' => $alias,
640
        ], true);
641
    }
642
643
    /**
644
     * Creates and adds a join to the query.
645
     *
646
     * <code>
647
     *     $qb = $conn->createQueryBuilder()
648
     *         ->select('u.name')
649
     *         ->from('users', 'u')
650
     *         ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1');
651
     * </code>
652 190
     *
653
     * @param string $fromAlias The alias that points to a from clause.
654 190
     * @param string $join      The table name to join.
655
     * @param string $alias     The alias of the join table.
656 190
     * @param string $condition The condition for the join.
657 190
     *
658 190
     * @return $this This QueryBuilder instance.
659 190
     */
660
    public function join($fromAlias, $join, $alias, $condition = null)
661 190
    {
662
        return $this->innerJoin($fromAlias, $join, $alias, $condition);
663
    }
664
665
    /**
666
     * Creates and adds a join to the query.
667
     *
668
     * <code>
669
     *     $qb = $conn->createQueryBuilder()
670
     *         ->select('u.name')
671
     *         ->from('users', 'u')
672
     *         ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
673
     * </code>
674
     *
675
     * @param string $fromAlias The alias that points to a from clause.
676
     * @param string $join      The table name to join.
677
     * @param string $alias     The alias of the join table.
678
     * @param string $condition The condition for the join.
679
     *
680
     * @return $this This QueryBuilder instance.
681 19
     */
682
    public function innerJoin($fromAlias, $join, $alias, $condition = null)
683 19
    {
684
        return $this->add('join', [
0 ignored issues
show
Bug introduced by
array($fromAlias => arra...dition' => $condition)) of type array<string,array<string,null|string>> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

684
        return $this->add('join', /** @scrutinizer ignore-type */ [
Loading history...
685 19
            $fromAlias => [
686 19
                'joinType'      => 'inner',
687 19
                'joinTable'     => $join,
688 19
                'joinAlias'     => $alias,
689
                'joinCondition' => $condition,
690 19
            ],
691
        ], true);
692
    }
693
694
    /**
695
     * Creates and adds a left join to the query.
696
     *
697
     * <code>
698
     *     $qb = $conn->createQueryBuilder()
699
     *         ->select('u.name')
700
     *         ->from('users', 'u')
701
     *         ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
702
     * </code>
703
     *
704
     * @param string $fromAlias The alias that points to a from clause.
705
     * @param string $join      The table name to join.
706
     * @param string $alias     The alias of the join table.
707
     * @param string $condition The condition for the join.
708
     *
709
     * @return $this This QueryBuilder instance.
710 19
     */
711
    public function leftJoin($fromAlias, $join, $alias, $condition = null)
712 19
    {
713
        return $this->add('join', [
0 ignored issues
show
Bug introduced by
array($fromAlias => arra...dition' => $condition)) of type array<string,array<string,null|string>> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

713
        return $this->add('join', /** @scrutinizer ignore-type */ [
Loading history...
714 19
            $fromAlias => [
715 19
                'joinType'      => 'left',
716 19
                'joinTable'     => $join,
717 19
                'joinAlias'     => $alias,
718
                'joinCondition' => $condition,
719 19
            ],
720
        ], true);
721
    }
722
723
    /**
724
     * Creates and adds a right join to the query.
725
     *
726
     * <code>
727
     *     $qb = $conn->createQueryBuilder()
728
     *         ->select('u.name')
729
     *         ->from('users', 'u')
730
     *         ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1');
731
     * </code>
732
     *
733
     * @param string $fromAlias The alias that points to a from clause.
734
     * @param string $join      The table name to join.
735
     * @param string $alias     The alias of the join table.
736
     * @param string $condition The condition for the join.
737 57
     *
738
     * @return $this This QueryBuilder instance.
739 57
     */
740
    public function rightJoin($fromAlias, $join, $alias, $condition = null)
741
    {
742
        return $this->add('join', [
0 ignored issues
show
Bug introduced by
array($fromAlias => arra...dition' => $condition)) of type array<string,array<string,null|string>> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

742
        return $this->add('join', /** @scrutinizer ignore-type */ [
Loading history...
743
            $fromAlias => [
744
                'joinType'      => 'right',
745
                'joinTable'     => $join,
746
                'joinAlias'     => $alias,
747
                'joinCondition' => $condition,
748
            ],
749
        ], true);
750
    }
751
752
    /**
753
     * Sets a new value for a column in a bulk update query.
754
     *
755
     * <code>
756
     *     $qb = $conn->createQueryBuilder()
757
     *         ->update('counters', 'c')
758
     *         ->set('c.value', 'c.value + 1')
759
     *         ->where('c.id = ?');
760
     * </code>
761
     *
762
     * @param string $key   The column to set.
763
     * @param string $value The value, expression, placeholder, etc.
764
     *
765
     * @return $this This QueryBuilder instance.
766
     */
767
    public function set($key, $value)
768 361
    {
769
        return $this->add('set', $key . ' = ' . $value, true);
770 361
    }
771 342
772
    /**
773
     * Specifies one or more restrictions to the query result.
774 361
     * Replaces any previously specified restrictions, if any.
775
     *
776
     * <code>
777
     *     $qb = $conn->createQueryBuilder()
778
     *         ->select('c.value')
779
     *         ->from('counters', 'c')
780
     *         ->where('c.id = ?');
781
     *
782
     *     // You can optionally programatically build and/or expressions
783
     *     $qb = $conn->createQueryBuilder();
784
     *
785
     *     $or = $qb->expr()->orx();
786
     *     $or->add($qb->expr()->eq('c.id', 1));
787
     *     $or->add($qb->expr()->eq('c.id', 2));
788
     *
789
     *     $qb->update('counters', 'c')
790
     *         ->set('c.value', 'c.value + 1')
791
     *         ->where($or);
792
     * </code>
793
     *
794
     * @param mixed $predicates The restriction predicates.
795 114
     *
796
     * @return $this This QueryBuilder instance.
797 114
     */
798 114
    public function where($predicates)
799
    {
800 114
        if (! (func_num_args() === 1 && $predicates instanceof CompositeExpression)) {
801 114
            $predicates = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
802
        }
803 19
804 19
        return $this->add('where', $predicates);
805
    }
806
807 114
    /**
808
     * Adds one or more restrictions to the query results, forming a logical
809
     * conjunction with any previously specified restrictions.
810
     *
811
     * <code>
812
     *     $qb = $conn->createQueryBuilder()
813
     *         ->select('u')
814
     *         ->from('users', 'u')
815
     *         ->where('u.username LIKE ?')
816
     *         ->andWhere('u.is_active = 1');
817
     * </code>
818
     *
819
     * @see where()
820
     *
821
     * @param mixed $where The query restrictions.
822
     *
823
     * @return $this This QueryBuilder instance.
824
     */
825
    public function andWhere($where)
826
    {
827
        $args  = func_get_args();
828 57
        $where = $this->getQueryPart('where');
829
830 57
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) {
831 57
            $where->addMultiple($args);
832
        } else {
833 57
            array_unshift($args, $where);
834 19
            $where = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
835
        }
836 57
837 57
        return $this->add('where', $where, true);
838
    }
839
840 57
    /**
841
     * Adds one or more restrictions to the query results, forming a logical
842
     * disjunction with any previously specified restrictions.
843
     *
844
     * <code>
845
     *     $qb = $em->createQueryBuilder()
846
     *         ->select('u.name')
847
     *         ->from('users', 'u')
848
     *         ->where('u.id = 1')
849
     *         ->orWhere('u.id = 2');
850
     * </code>
851
     *
852
     * @see where()
853
     *
854
     * @param mixed $where The WHERE statement.
855
     *
856
     * @return $this This QueryBuilder instance.
857
     */
858 190
    public function orWhere($where)
859
    {
860 190
        $args  = func_get_args();
861 19
        $where = $this->getQueryPart('where');
862
863
        if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) {
864 171
            $where->addMultiple($args);
865
        } else {
866 171
            array_unshift($args, $where);
867
            $where = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
868
        }
869
870
        return $this->add('where', $where, true);
871
    }
872
873
    /**
874
     * Specifies a grouping over the results of the query.
875
     * Replaces any previously specified groupings, if any.
876
     *
877
     * <code>
878
     *     $qb = $conn->createQueryBuilder()
879
     *         ->select('u.name')
880
     *         ->from('users', 'u')
881
     *         ->groupBy('u.id');
882
     * </code>
883
     *
884
     * @param mixed $groupBy The grouping expression.
885 57
     *
886
     * @return $this This QueryBuilder instance.
887 57
     */
888 19
    public function groupBy($groupBy)
889
    {
890
        if (empty($groupBy)) {
891 38
            return $this;
892
        }
893 38
894
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
895
896
        return $this->add('groupBy', $groupBy, false);
0 ignored issues
show
Bug introduced by
$groupBy of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

896
        return $this->add('groupBy', /** @scrutinizer ignore-type */ $groupBy, false);
Loading history...
897
    }
898
899
900
    /**
901
     * Adds a grouping expression to the query.
902
     *
903
     * <code>
904
     *     $qb = $conn->createQueryBuilder()
905
     *         ->select('u.name')
906
     *         ->from('users', 'u')
907
     *         ->groupBy('u.lastLogin');
908
     *         ->addGroupBy('u.createdAt')
909
     * </code>
910
     *
911
     * @param mixed $groupBy The grouping expression.
912
     *
913
     * @return $this This QueryBuilder instance.
914
     */
915 38
    public function addGroupBy($groupBy)
916
    {
917 38
        if (empty($groupBy)) {
918
            return $this;
919 38
        }
920
921
        $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
922
923
        return $this->add('groupBy', $groupBy, true);
0 ignored issues
show
Bug introduced by
$groupBy of type array is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

923
        return $this->add('groupBy', /** @scrutinizer ignore-type */ $groupBy, true);
Loading history...
924
    }
925
926
    /**
927
     * Sets a value for a column in an insert query.
928
     *
929
     * <code>
930
     *     $qb = $conn->createQueryBuilder()
931
     *         ->insert('users')
932
     *         ->values(
933
     *             array(
934
     *                 'name' => '?'
935
     *             )
936
     *         )
937
     *         ->setValue('password', '?');
938
     * </code>
939
     *
940
     * @param string $column The column into which the value should be inserted.
941 57
     * @param string $value  The value that should be inserted into the column.
942
     *
943 57
     * @return $this This QueryBuilder instance.
944
     */
945
    public function setValue($column, $value)
946
    {
947
        $this->sqlParts['values'][$column] = $value;
948
949
        return $this;
950
    }
951
952
    /**
953
     * Specifies values for an insert query indexed by column names.
954 76
     * Replaces any previous values, if any.
955
     *
956 76
     * <code>
957 76
     *     $qb = $conn->createQueryBuilder()
958
     *         ->insert('users')
959
     *         ->values(
960 76
     *             array(
961
     *                 'name' => '?',
962
     *                 'password' => '?'
963
     *             )
964
     *         );
965
     * </code>
966
     *
967
     * @param mixed[] $values The values to specify for the insert query indexed by column names.
968
     *
969
     * @return $this This QueryBuilder instance.
970
     */
971 57
    public function values(array $values)
972
    {
973 57
        return $this->add('values', $values);
0 ignored issues
show
Bug introduced by
$values of type array<mixed,mixed> is incompatible with the type string expected by parameter $sqlPart of Doctrine\DBAL\Query\QueryBuilder::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

973
        return $this->add('values', /** @scrutinizer ignore-type */ $values);
Loading history...
974 57
    }
975
976 57
    /**
977 19
     * Specifies a restriction over the groups of the query.
978
     * Replaces any previous having restrictions, if any.
979 38
     *
980 38
     * @param mixed $having The restriction over the groups.
981
     *
982
     * @return $this This QueryBuilder instance.
983 57
     */
984
    public function having($having)
985
    {
986
        if (! (func_num_args() === 1 && $having instanceof CompositeExpression)) {
987
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args());
988
        }
989
990
        return $this->add('having', $having);
991
    }
992
993
    /**
994 57
     * Adds a restriction over the groups of the query, forming a logical
995
     * conjunction with any existing having restrictions.
996 57
     *
997 57
     * @param mixed $having The restriction to append.
998
     *
999 57
     * @return $this This QueryBuilder instance.
1000 19
     */
1001
    public function andHaving($having)
1002 57
    {
1003 57
        $args   = func_get_args();
1004
        $having = $this->getQueryPart('having');
1005
1006 57
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) {
1007
            $having->addMultiple($args);
1008
        } else {
1009
            array_unshift($args, $having);
1010
            $having = new CompositeExpression(CompositeExpression::TYPE_AND, $args);
1011
        }
1012
1013
        return $this->add('having', $having);
1014
    }
1015
1016
    /**
1017
     * Adds a restriction over the groups of the query, forming a logical
1018 57
     * disjunction with any existing having restrictions.
1019
     *
1020 57
     * @param mixed $having The restriction to add.
1021
     *
1022
     * @return $this This QueryBuilder instance.
1023
     */
1024
    public function orHaving($having)
1025
    {
1026
        $args   = func_get_args();
1027
        $having = $this->getQueryPart('having');
1028
1029
        if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) {
1030
            $having->addMultiple($args);
1031 38
        } else {
1032
            array_unshift($args, $having);
1033 38
            $having = new CompositeExpression(CompositeExpression::TYPE_OR, $args);
1034
        }
1035
1036
        return $this->add('having', $having);
1037
    }
1038
1039
    /**
1040
     * Specifies an ordering for the query results.
1041
     * Replaces any previously specified orderings, if any.
1042
     *
1043 247
     * @param string $sort  The ordering expression.
1044
     * @param string $order The ordering direction.
1045 247
     *
1046
     * @return $this This QueryBuilder instance.
1047
     */
1048
    public function orderBy($sort, $order = null)
1049
    {
1050
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false);
1051
    }
1052
1053 19
    /**
1054
     * Adds an ordering to the query results.
1055 19
     *
1056
     * @param string $sort  The ordering expression.
1057
     * @param string $order The ordering direction.
1058
     *
1059
     * @return $this This QueryBuilder instance.
1060
     */
1061
    public function addOrderBy($sort, $order = null)
1062
    {
1063
        return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true);
1064
    }
1065 19
1066
    /**
1067 19
     * Gets a query part by its name.
1068
     *
1069
     * @param string $queryPartName
1070
     *
1071 19
     * @return mixed
1072 19
     */
1073
    public function getQueryPart($queryPartName)
1074
    {
1075 19
        return $this->sqlParts[$queryPartName];
1076
    }
1077
1078
    /**
1079
     * Gets all query parts.
1080
     *
1081
     * @return mixed[]
1082
     */
1083
    public function getQueryParts()
1084
    {
1085 38
        return $this->sqlParts;
1086
    }
1087 38
1088 38
    /**
1089
     * Resets SQL parts.
1090 38
     *
1091
     * @param string[]|null $queryPartNames
1092 38
     *
1093
     * @return $this This QueryBuilder instance.
1094
     */
1095
    public function resetQueryParts($queryPartNames = null)
1096
    {
1097
        if ($queryPartNames === null) {
1098
            $queryPartNames = array_keys($this->sqlParts);
1099
        }
1100 874
1101
        foreach ($queryPartNames as $queryPartName) {
1102 874
            $this->resetQueryPart($queryPartName);
1103
        }
1104 874
1105 836
        return $this;
1106 836
    }
1107 836
1108 836
    /**
1109
     * Resets a single SQL part.
1110 836
     *
1111
     * @param string $queryPartName
1112
     *
1113
     * @return $this This QueryBuilder instance.
1114
     */
1115
    public function resetQueryPart($queryPartName)
1116
    {
1117
        $this->sqlParts[$queryPartName] = is_array($this->sqlParts[$queryPartName])
1118 836
            ? [] : null;
1119
1120
        $this->state = self::STATE_DIRTY;
1121
1122
        return $this;
1123
    }
1124 855
1125
    /**
1126 855
     * @return string
1127 855
     *
1128
     * @throws QueryException
1129
     */
1130 855
    private function getSQLForSelect()
1131 855
    {
1132 114
        $query = 'SELECT' . ($this->isDistinct ? ' DISTINCT ' : ' ') . implode(', ', $this->sqlParts['select']);
1133 114
1134
        $query .= ($this->sqlParts['from'] ? ' FROM ' . implode(', ', $this->getFromClauses()) : '')
1135 760
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '')
1136 760
            . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '')
1137
            . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '')
1138
            . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : '');
1139 855
1140
        if ($this->isLimitQuery()) {
1141 855
            return $this->connection->getDatabasePlatform()->modifyLimitQuery(
1142
                $query,
1143
                $this->maxResults,
1144 836
                $this->firstResult
1145
            );
1146 817
        }
1147
1148
        return $query;
1149
    }
1150
1151
    /**
1152
     * @return string[]
1153
     */
1154 836
    private function getFromClauses()
1155
    {
1156 836
        $fromClauses  = [];
1157 209
        $knownAliases = [];
1158 209
1159
        // Loop through all FROM clauses
1160
        foreach ($this->sqlParts['from'] as $from) {
1161 817
            if ($from['alias'] === null) {
1162
                $tableSql       = $from['table'];
1163
                $tableReference = $from['table'];
1164
            } else {
1165
                $tableSql       = $from['table'] . ' ' . $from['alias'];
1166 836
                $tableReference = $from['alias'];
1167
            }
1168 836
1169
            $knownAliases[$tableReference] = true;
1170
1171
            $fromClauses[$tableReference] = $tableSql . $this->getSQLForJoins($tableReference, $knownAliases);
1172
        }
1173
1174
        $this->verifyAllAliasesAreKnown($knownAliases);
1175
1176 76
        return $fromClauses;
1177
    }
1178 76
1179 76
    /**
1180 76
     * @param string[] $knownAliases
1181
     *
1182
     * @throws QueryException
1183
     */
1184
    private function verifyAllAliasesAreKnown(array $knownAliases)
1185
    {
1186
        foreach ($this->sqlParts['join'] as $fromAlias => $joins) {
1187
            if (! isset($knownAliases[$fromAlias])) {
1188 57
                throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases));
1189
            }
1190 57
        }
1191 57
    }
1192 57
1193 57
    /**
1194
     * @return bool
1195
     */
1196
    private function isLimitQuery()
1197
    {
1198
        return $this->maxResults !== null || $this->firstResult !== null;
1199
    }
1200
1201 57
    /**
1202
     * Converts this instance into an INSERT string in SQL.
1203 57
     *
1204 57
     * @return string
1205
     */
1206
    private function getSQLForInsert()
1207
    {
1208
        return 'INSERT INTO ' . $this->sqlParts['from']['table'] .
1209
        ' (' . implode(', ', array_keys($this->sqlParts['values'])) . ')' .
1210
        ' VALUES(' . implode(', ', $this->sqlParts['values']) . ')';
1211
    }
1212
1213 931
    /**
1214
     * Converts this instance into an UPDATE string in SQL.
1215 931
     *
1216
     * @return string
1217
     */
1218
    private function getSQLForUpdate()
1219
    {
1220
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1221
        return 'UPDATE ' . $table
1222
            . ' SET ' . implode(', ', $this->sqlParts['set'])
1223
            . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1224
    }
1225
1226
    /**
1227
     * Converts this instance into a DELETE string in SQL.
1228
     *
1229
     * @return string
1230
     */
1231
    private function getSQLForDelete()
1232
    {
1233
        $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : '');
1234
        return 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '');
1235
    }
1236
1237
    /**
1238
     * Gets a string representation of this QueryBuilder which corresponds to
1239
     * the final SQL query being constructed.
1240
     *
1241
     * @return string The string representation of this QueryBuilder.
1242
     */
1243
    public function __toString()
1244
    {
1245
        return $this->getSQL();
1246 38
    }
1247
1248 38
    /**
1249 19
     * Creates a new named parameter and bind the value $value to it.
1250 19
     *
1251
     * This method provides a shortcut for PDOStatement::bindValue
1252 38
     * when using prepared statements.
1253
     *
1254 38
     * The parameter $value specifies the value that you want to bind. If
1255
     * $placeholder is not provided bindValue() will automatically create a
1256
     * placeholder for you. An automatic placeholder will be of the name
1257
     * ':dcValue1', ':dcValue2' etc.
1258
     *
1259
     * For more information see {@link http://php.net/pdostatement-bindparam}
1260
     *
1261
     * Example:
1262
     * <code>
1263
     * $value = 2;
1264
     * $q->eq( 'id', $q->bindValue( $value ) );
1265
     * $stmt = $q->executeQuery(); // executed with 'id = 2'
1266
     * </code>
1267
     *
1268
     * @link http://www.zetacomponents.org
1269
     *
1270
     * @param mixed  $value
1271
     * @param mixed  $type
1272
     * @param string $placeHolder The name to bind with. The string must start with a colon ':'.
1273
     *
1274
     * @return string the placeholder name used.
1275
     */
1276
    public function createNamedParameter($value, $type = ParameterType::STRING, $placeHolder = null)
1277
    {
1278
        if ($placeHolder === null) {
1279 19
            $this->boundCounter++;
1280
            $placeHolder = ':dcValue' . $this->boundCounter;
1281 19
        }
1282 19
        $this->setParameter(substr($placeHolder, 1), $value, $type);
1283
1284 19
        return $placeHolder;
1285
    }
1286
1287
    /**
1288
     * Creates a new positional parameter and bind the given value to it.
1289
     *
1290
     * Attention: If you are using positional parameters with the query builder you have
1291
     * to be very careful to bind all parameters in the order they appear in the SQL
1292
     * statement , otherwise they get bound in the wrong order which can lead to serious
1293
     * bugs in your code.
1294
     *
1295 855
     * Example:
1296
     * <code>
1297 855
     *  $qb = $conn->createQueryBuilder();
1298
     *  $qb->select('u.*')
1299 855
     *     ->from('users', 'u')
1300 228
     *     ->where('u.username = ' . $qb->createPositionalParameter('Foo', ParameterType::STRING))
1301 228
     *     ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', ParameterType::STRING))
1302 19
     * </code>
1303
     *
1304 209
     * @param mixed $value
1305 209
     * @param int   $type
1306 209
     *
1307 209
     * @return string
1308
     */
1309
    public function createPositionalParameter($value, $type = ParameterType::STRING)
1310 209
    {
1311 209
        $this->boundCounter++;
1312
        $this->setParameter($this->boundCounter, $value, $type);
1313
1314
        return '?';
1315 836
    }
1316
1317
    /**
1318
     * @param string   $fromAlias
1319
     * @param string[] $knownAliases
1320
     *
1321
     * @return string
1322
     *
1323 19
     * @throws QueryException
1324
     */
1325 19
    private function getSQLForJoins($fromAlias, array &$knownAliases)
1326 19
    {
1327 19
        $sql = '';
1328 19
1329 19
        if (isset($this->sqlParts['join'][$fromAlias])) {
1330
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1331
                if (array_key_exists($join['joinAlias'], $knownAliases)) {
1332 19
                    throw QueryException::nonUniqueAlias($join['joinAlias'], array_keys($knownAliases));
1333
                }
1334 19
                $sql                             .= ' ' . strtoupper($join['joinType'])
1335 19
                    . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias']
1336
                    . ' ON ' . ((string) $join['joinCondition']);
1337
                $knownAliases[$join['joinAlias']] = true;
1338
            }
1339 19
1340 19
            foreach ($this->sqlParts['join'][$fromAlias] as $join) {
1341
                $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases);
1342
            }
1343
        }
1344 19
1345
        return $sql;
1346 19
    }
1347
1348
    /**
1349
     * Deep clone of all expression objects in the SQL parts.
1350
     *
1351
     * @return void
1352
     */
1353
    public function __clone()
1354
    {
1355
        foreach ($this->sqlParts as $part => $elements) {
1356
            if (is_array($this->sqlParts[$part])) {
1357
                foreach ($this->sqlParts[$part] as $idx => $element) {
1358
                    if (! is_object($element)) {
1359
                        continue;
1360
                    }
1361
1362
                    $this->sqlParts[$part][$idx] = clone $element;
1363
                }
1364
            } elseif (is_object($elements)) {
1365
                $this->sqlParts[$part] = clone $elements;
1366
            }
1367
        }
1368
1369
        foreach ($this->params as $name => $param) {
1370
            if (! is_object($param)) {
1371
                continue;
1372
            }
1373
1374
            $this->params[$name] = clone $param;
1375
        }
1376
    }
1377
}
1378