Completed
Push — 2.1 ( 21da0e...a39d12 )
by
unknown
10:45
created

QueryBuilder::addPrimaryKey()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 4
nop 3
crap 3
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use yii\base\InvalidArgumentException;
11
use yii\base\NotSupportedException;
12
use yii\helpers\ArrayHelper;
13
use yii\helpers\StringHelper;
14
15
/**
16
 * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object.
17
 *
18
 * SQL statements are created from [[Query]] objects using the [[build()]]-method.
19
 *
20
 * QueryBuilder is also used by [[Command]] to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE.
21
 *
22
 * For more details and usage information on QueryBuilder, see the [guide article on query builders](guide:db-query-builder).
23
 *
24
 * @author Qiang Xue <[email protected]>
25
 * @since 2.0
26
 */
27
class QueryBuilder extends \yii\base\BaseObject
28
{
29
    /**
30
     * The prefix for automatically generated query binding parameters.
31
     */
32
    const PARAM_PREFIX = ':qp';
33
34
    /**
35
     * @var Connection the database connection.
36
     */
37
    public $db;
38
    /**
39
     * @var string the separator between different fragments of a SQL statement.
40
     * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
41
     */
42
    public $separator = ' ';
43
    /**
44
     * @var array the abstract column types mapped to physical column types.
45
     * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
46
     * Child classes should override this property to declare supported type mappings.
47
     */
48
    public $typeMap = [];
49
50
    /**
51
     * @var array map of query condition to builder methods.
52
     * These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
53
     */
54
    protected $conditionBuilders = [
55
        'NOT' => 'buildNotCondition',
56
        'AND' => 'buildAndCondition',
57
        'OR' => 'buildAndCondition',
58
        'BETWEEN' => 'buildBetweenCondition',
59
        'NOT BETWEEN' => 'buildBetweenCondition',
60
        'IN' => 'buildInCondition',
61
        'NOT IN' => 'buildInCondition',
62
        'LIKE' => 'buildLikeCondition',
63
        'NOT LIKE' => 'buildLikeCondition',
64
        'OR LIKE' => 'buildLikeCondition',
65
        'OR NOT LIKE' => 'buildLikeCondition',
66
        'EXISTS' => 'buildExistsCondition',
67
        'NOT EXISTS' => 'buildExistsCondition',
68
    ];
69
    /**
70
     * @var array map of chars to their replacements in LIKE conditions.
71
     * By default it's configured to escape `%`, `_` and `\` with `\`.
72
     * @since 2.0.12.
73
     */
74
    protected $likeEscapingReplacements = [
75
        '%' => '\%',
76
        '_' => '\_',
77
        '\\' => '\\\\',
78
    ];
79
    /**
80
     * @var string|null character used to escape special characters in LIKE conditions.
81
     * By default it's assumed to be `\`.
82
     * @since 2.0.12
83
     */
84
    protected $likeEscapeCharacter;
85
86
87
    /**
88
     * Constructor.
89
     * @param Connection $connection the database connection.
90
     * @param array $config name-value pairs that will be used to initialize the object properties
91
     */
92 1239
    public function __construct($connection, $config = [])
93
    {
94 1239
        $this->db = $connection;
95 1239
        parent::__construct($config);
96 1239
    }
97
98
    /**
99
     * Generates a SELECT SQL statement from a [[Query]] object.
100
     * @param Query $query the [[Query]] object from which the SQL statement will be generated.
101
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
102
     * be included in the result with the additional parameters generated during the query building process.
103
     * @return array the generated SQL statement (the first array element) and the corresponding
104
     * parameters to be bound to the SQL statement (the second array element). The parameters returned
105
     * include those provided in `$params`.
106
     */
107 692
    public function build($query, $params = [])
108
    {
109 692
        $query = $query->prepare($this);
110
111 692
        $params = empty($params) ? $query->params : array_merge($params, $query->params);
112
113
        $clauses = [
114 692
            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
115 692
            $this->buildFrom($query->from, $params),
116 692
            $this->buildJoin($query->join, $params),
117 692
            $this->buildWhere($query->where, $params),
118 692
            $this->buildGroupBy($query->groupBy, $params),
119 692
            $this->buildHaving($query->having, $params),
120
        ];
121
122 692
        $sql = implode($this->separator, array_filter($clauses));
123 692
        $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset, $params);
124
125 692
        $union = $this->buildUnion($query->union, $params);
126 692
        if ($union !== '') {
127 8
            $sql = "($sql){$this->separator}$union";
128
        }
129
130 692
        return [$sql, $params];
131
    }
132
133
    /**
134
     * Creates an INSERT SQL statement.
135
     *
136
     * For example,
137
     *
138
     * ```php
139
     * $sql = $queryBuilder->insert('user', [
140
     *     'name' => 'Sam',
141
     *     'age' => 30,
142
     * ], $params);
143
     * ```
144
     *
145
     * The method will properly escape the table and column names.
146
     *
147
     * @param string $table the table that new rows will be inserted into.
148
     * @param array|\yii\db\Query $columns the column data (name => value) to be inserted into the table or instance
149
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
150
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
151
     * @param array $params the binding parameters that will be generated by this method.
152
     * They should be bound to the DB command later.
153
     * @return string the INSERT SQL
154
     */
155 300
    public function insert($table, $columns, &$params)
156
    {
157 300
        $schema = $this->db->getSchema();
158 300
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
159 296
            $columnSchemas = $tableSchema->columns;
160
        } else {
161 4
            $columnSchemas = [];
162
        }
163 300
        $names = [];
164 300
        $placeholders = [];
165 300
        $values = ' DEFAULT VALUES';
166 300
        if ($columns instanceof \yii\db\Query) {
167 12
            [$names, $values, $params] = $this->prepareInsertSelectSubQuery($columns, $schema, $params);
168
        } else {
169 292
            foreach ($columns as $name => $value) {
170 290
                $names[] = $schema->quoteColumnName($name);
171 290
                if ($value instanceof Expression) {
172 9
                    $placeholders[] = $value->expression;
173 9
                    foreach ($value->params as $n => $v) {
174 9
                        $params[$n] = $v;
175
                    }
176 287
                } elseif ($value instanceof \yii\db\Query) {
177 2
                    [$sql, $params] = $this->build($value, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
178 2
                    $placeholders[] = "($sql)";
179
                } else {
180 287
                    $phName = self::PARAM_PREFIX . count($params);
181 287
                    $placeholders[] = $phName;
182 290
                    $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
183
                }
184
            }
185
        }
186
187 294
        return 'INSERT INTO ' . $schema->quoteTableName($table)
188 294
            . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
189 294
            . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
190
    }
191
192
    /**
193
     * Prepare select-subquery and field names for INSERT INTO ... SELECT SQL statement.
194
     *
195
     * @param \yii\db\Query $columns Object, which represents select query.
196
     * @param \yii\db\Schema $schema Schema object to quote column name.
197
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
198
     * be included in the result with the additional parameters generated during the query building process.
199
     * @return array
200
     * @throws InvalidArgumentException if query's select does not contain named parameters only.
201
     * @since 2.0.11
202
     */
203 18
    protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
204
    {
205 18
        if (!is_array($columns->select) || empty($columns->select) || in_array('*', $columns->select)) {
206 9
            throw new InvalidArgumentException('Expected select query object with enumerated (named) parameters');
207
        }
208
209 9
        [$values, $params] = $this->build($columns, $params);
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
210 9
        $names = [];
211 9
        $values = ' ' . $values;
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
212 9
        foreach ($columns->select as $title => $field) {
213 9
            if (is_string($title)) {
214 3
                $names[] = $schema->quoteColumnName($title);
215 6
            } elseif (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $field, $matches)) {
216 3
                $names[] = $schema->quoteColumnName($matches[2]);
217
            } else {
218 9
                $names[] = $schema->quoteColumnName($field);
219
            }
220
        }
221
222 9
        return [$names, $values, $params];
223
    }
224
225
    /**
226
     * Generates a batch INSERT SQL statement.
227
     *
228
     * For example,
229
     *
230
     * ```php
231
     * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [
232
     *     ['Tom', 30],
233
     *     ['Jane', 20],
234
     *     ['Linda', 25],
235
     * ]);
236
     * ```
237
     *
238
     * Note that the values in each row must match the corresponding column names.
239
     *
240
     * The method will properly escape the column names, and quote the values to be inserted.
241
     *
242
     * @param string $table the table that new rows will be inserted into.
243
     * @param array $columns the column names
244
     * @param array|\Generator $rows the rows to be batch inserted into the table
245
     * @return string the batch INSERT SQL statement
246
     */
247 26
    public function batchInsert($table, $columns, $rows)
248
    {
249 26
        if (empty($rows)) {
250 2
            return '';
251
        }
252
253 25
        $schema = $this->db->getSchema();
254 25
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
255 19
            $columnSchemas = $tableSchema->columns;
256
        } else {
257 6
            $columnSchemas = [];
258
        }
259
260 25
        $values = [];
261 25
        foreach ($rows as $row) {
262 23
            $vs = [];
263 23
            foreach ($row as $i => $value) {
264 23
                if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
265 14
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
266
                }
267 23
                if (is_string($value)) {
268 17
                    $value = $schema->quoteValue($value);
269 13
                } elseif (is_float($value)) {
270
                    // ensure type cast always has . as decimal separator in all locales
271 2
                    $value = StringHelper::floatToString($value);
272 13
                } elseif ($value === false) {
273 4
                    $value = 0;
274 13
                } elseif ($value === null) {
275 8
                    $value = 'NULL';
276
                }
277 23
                $vs[] = $value;
278
            }
279 23
            $values[] = '(' . implode(', ', $vs) . ')';
280
        }
281 25
        if (empty($values)) {
282 2
            return '';
283
        }
284
285 23
        foreach ($columns as $i => $name) {
286 21
            $columns[$i] = $schema->quoteColumnName($name);
287
        }
288
289 23
        return 'INSERT INTO ' . $schema->quoteTableName($table)
290 23
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
291
    }
292
293
    /**
294
     * Creates an UPDATE SQL statement.
295
     *
296
     * For example,
297
     *
298
     * ```php
299
     * $params = [];
300
     * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params);
301
     * ```
302
     *
303
     * The method will properly escape the table and column names.
304
     *
305
     * @param string $table the table to be updated.
306
     * @param array $columns the column data (name => value) to be updated.
307
     * @param array|string $condition the condition that will be put in the WHERE part. Please
308
     * refer to [[Query::where()]] on how to specify condition.
309
     * @param array $params the binding parameters that will be modified by this method
310
     * so that they can be bound to the DB command later.
311
     * @return string the UPDATE SQL
312
     */
313 114
    public function update($table, $columns, $condition, &$params)
314
    {
315 114
        if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
316 113
            $columnSchemas = $tableSchema->columns;
317
        } else {
318 1
            $columnSchemas = [];
319
        }
320
321 114
        $lines = [];
322 114
        foreach ($columns as $name => $value) {
323 114
            if ($value instanceof Expression) {
324 9
                $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
325 9
                foreach ($value->params as $n => $v) {
326 9
                    $params[$n] = $v;
327
                }
328
            } else {
329 108
                $phName = self::PARAM_PREFIX . count($params);
330 108
                $lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
331 114
                $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
332
            }
333
        }
334
335 114
        $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
336 114
        $where = $this->buildWhere($condition, $params);
337
338 114
        return $where === '' ? $sql : $sql . ' ' . $where;
339
    }
340
341
    /**
342
     * Creates a DELETE SQL statement.
343
     *
344
     * For example,
345
     *
346
     * ```php
347
     * $sql = $queryBuilder->delete('user', 'status = 0');
348
     * ```
349
     *
350
     * The method will properly escape the table and column names.
351
     *
352
     * @param string $table the table where the data will be deleted from.
353
     * @param array|string $condition the condition that will be put in the WHERE part. Please
354
     * refer to [[Query::where()]] on how to specify condition.
355
     * @param array $params the binding parameters that will be modified by this method
356
     * so that they can be bound to the DB command later.
357
     * @return string the DELETE SQL
358
     */
359 339
    public function delete($table, $condition, &$params)
360
    {
361 339
        $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
362 339
        $where = $this->buildWhere($condition, $params);
363
364 339
        return $where === '' ? $sql : $sql . ' ' . $where;
365
    }
366
367
    /**
368
     * Builds a SQL statement for creating a new DB table.
369
     *
370
     * The columns in the new  table should be specified as name-definition pairs (e.g. 'name' => 'string'),
371
     * where name stands for a column name which will be properly quoted by the method, and definition
372
     * stands for the column type which can contain an abstract DB type.
373
     * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
374
     *
375
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
376
     * inserted into the generated SQL.
377
     *
378
     * For example,
379
     *
380
     * ```php
381
     * $sql = $queryBuilder->createTable('user', [
382
     *  'id' => 'pk',
383
     *  'name' => 'string',
384
     *  'age' => 'integer',
385
     * ]);
386
     * ```
387
     *
388
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
389
     * @param array $columns the columns (name => definition) in the new table.
390
     * @param string $options additional SQL fragment that will be appended to the generated SQL.
391
     * @return string the SQL statement for creating a new DB table.
392
     */
393 128
    public function createTable($table, $columns, $options = null)
394
    {
395 128
        $cols = [];
396 128
        foreach ($columns as $name => $type) {
397 128
            if (is_string($name)) {
398 128
                $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
399
            } else {
400 128
                $cols[] = "\t" . $type;
401
            }
402
        }
403 128
        $sql = 'CREATE TABLE ' . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
404
405 128
        return $options === null ? $sql : $sql . ' ' . $options;
406
    }
407
408
    /**
409
     * Builds a SQL statement for renaming a DB table.
410
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
411
     * @param string $newName the new table name. The name will be properly quoted by the method.
412
     * @return string the SQL statement for renaming a DB table.
413
     */
414 1
    public function renameTable($oldName, $newName)
415
    {
416 1
        return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
417
    }
418
419
    /**
420
     * Builds a SQL statement for dropping a DB table.
421
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
422
     * @return string the SQL statement for dropping a DB table.
423
     */
424 33
    public function dropTable($table)
425
    {
426 33
        return 'DROP TABLE ' . $this->db->quoteTableName($table);
427
    }
428
429
    /**
430
     * Builds a SQL statement for adding a primary key constraint to an existing table.
431
     * @param string $name the name of the primary key constraint.
432
     * @param string $table the table that the primary key constraint will be added to.
433
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
434
     * @return string the SQL statement for adding a primary key constraint to an existing table.
435
     */
436 6
    public function addPrimaryKey($name, $table, $columns)
437
    {
438 6
        if (is_string($columns)) {
439 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
440
        }
441
442 6
        foreach ($columns as $i => $col) {
443 6
            $columns[$i] = $this->db->quoteColumnName($col);
444
        }
445
446 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
447 6
            . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
448 6
            . implode(', ', $columns) . ')';
449
    }
450
451
    /**
452
     * Builds a SQL statement for removing a primary key constraint to an existing table.
453
     * @param string $name the name of the primary key constraint to be removed.
454
     * @param string $table the table that the primary key constraint will be removed from.
455
     * @return string the SQL statement for removing a primary key constraint from an existing table.
456
     */
457 2
    public function dropPrimaryKey($name, $table)
458
    {
459 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
460 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
461
    }
462
463
    /**
464
     * Builds a SQL statement for truncating a DB table.
465
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
466
     * @return string the SQL statement for truncating a DB table.
467
     */
468 11
    public function truncateTable($table)
469
    {
470 11
        return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table);
471
    }
472
473
    /**
474
     * Builds a SQL statement for adding a new DB column.
475
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
476
     * @param string $column the name of the new column. The name will be properly quoted by the method.
477
     * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
478
     * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
479
     * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
480
     * @return string the SQL statement for adding a new column.
481
     */
482 4
    public function addColumn($table, $column, $type)
483
    {
484 4
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
485 4
            . ' ADD ' . $this->db->quoteColumnName($column) . ' '
486 4
            . $this->getColumnType($type);
487
    }
488
489
    /**
490
     * Builds a SQL statement for dropping a DB column.
491
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
492
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
493
     * @return string the SQL statement for dropping a DB column.
494
     */
495
    public function dropColumn($table, $column)
496
    {
497
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
498
            . ' DROP COLUMN ' . $this->db->quoteColumnName($column);
499
    }
500
501
    /**
502
     * Builds a SQL statement for renaming a column.
503
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
504
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
505
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
506
     * @return string the SQL statement for renaming a DB column.
507
     */
508
    public function renameColumn($table, $oldName, $newName)
509
    {
510
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
511
            . ' RENAME COLUMN ' . $this->db->quoteColumnName($oldName)
512
            . ' TO ' . $this->db->quoteColumnName($newName);
513
    }
514
515
    /**
516
     * Builds a SQL statement for changing the definition of a column.
517
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
518
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
519
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
520
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
521
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
522
     * will become 'varchar(255) not null'.
523
     * @return string the SQL statement for changing the definition of a column.
524
     */
525 1
    public function alterColumn($table, $column, $type)
526
    {
527 1
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
528 1
            . $this->db->quoteColumnName($column) . ' '
529 1
            . $this->db->quoteColumnName($column) . ' '
530 1
            . $this->getColumnType($type);
531
    }
532
533
    /**
534
     * Builds a SQL statement for adding a foreign key constraint to an existing table.
535
     * The method will properly quote the table and column names.
536
     * @param string $name the name of the foreign key constraint.
537
     * @param string $table the table that the foreign key constraint will be added to.
538
     * @param string|array $columns the name of the column to that the constraint will be added on.
539
     * If there are multiple columns, separate them with commas or use an array to represent them.
540
     * @param string $refTable the table that the foreign key references to.
541
     * @param string|array $refColumns the name of the column that the foreign key references to.
542
     * If there are multiple columns, separate them with commas or use an array to represent them.
543
     * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
544
     * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
545
     * @return string the SQL statement for adding a foreign key constraint to an existing table.
546
     */
547 8
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
548
    {
549 8
        $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
550 8
            . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
551 8
            . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
552 8
            . ' REFERENCES ' . $this->db->quoteTableName($refTable)
553 8
            . ' (' . $this->buildColumns($refColumns) . ')';
554 8
        if ($delete !== null) {
555 4
            $sql .= ' ON DELETE ' . $delete;
556
        }
557 8
        if ($update !== null) {
558 4
            $sql .= ' ON UPDATE ' . $update;
559
        }
560
561 8
        return $sql;
562
    }
563
564
    /**
565
     * Builds a SQL statement for dropping a foreign key constraint.
566
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
567
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
568
     * @return string the SQL statement for dropping a foreign key constraint.
569
     */
570 3
    public function dropForeignKey($name, $table)
571
    {
572 3
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
573 3
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
574
    }
575
576
    /**
577
     * Builds a SQL statement for creating a new index.
578
     * @param string $name the name of the index. The name will be properly quoted by the method.
579
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
580
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
581
     * separate them with commas or use an array to represent them. Each column name will be properly quoted
582
     * by the method, unless a parenthesis is found in the name.
583
     * @param bool $unique whether to add UNIQUE constraint on the created index.
584
     * @return string the SQL statement for creating a new index.
585
     */
586 6
    public function createIndex($name, $table, $columns, $unique = false)
587
    {
588 6
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
589 6
            . $this->db->quoteTableName($name) . ' ON '
590 6
            . $this->db->quoteTableName($table)
591 6
            . ' (' . $this->buildColumns($columns) . ')';
592
    }
593
594
    /**
595
     * Builds a SQL statement for dropping an index.
596
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
597
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
598
     * @return string the SQL statement for dropping an index.
599
     */
600 4
    public function dropIndex($name, $table)
601
    {
602 4
        return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
603
    }
604
605
    /**
606
     * Creates a SQL command for adding an unique constraint to an existing table.
607
     * @param string $name the name of the unique constraint.
608
     * The name will be properly quoted by the method.
609
     * @param string $table the table that the unique constraint will be added to.
610
     * The name will be properly quoted by the method.
611
     * @param string|array $columns the name of the column to that the constraint will be added on.
612
     * If there are multiple columns, separate them with commas.
613
     * The name will be properly quoted by the method.
614
     * @return string the SQL statement for adding an unique constraint to an existing table.
615
     * @since 2.0.13
616
     */
617 6
    public function addUnique($name, $table, $columns)
618
    {
619 6
        if (is_string($columns)) {
620 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
621
        }
622 6
        foreach ($columns as $i => $col) {
623 6
            $columns[$i] = $this->db->quoteColumnName($col);
624
        }
625
626 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
627 6
            . $this->db->quoteColumnName($name) . ' UNIQUE ('
628 6
            . implode(', ', $columns) . ')';
629
    }
630
631
    /**
632
     * Creates a SQL command for dropping an unique constraint.
633
     * @param string $name the name of the unique constraint to be dropped.
634
     * The name will be properly quoted by the method.
635
     * @param string $table the table whose unique constraint is to be dropped.
636
     * The name will be properly quoted by the method.
637
     * @return string the SQL statement for dropping an unique constraint.
638
     * @since 2.0.13
639
     */
640 2
    public function dropUnique($name, $table)
641
    {
642 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
643 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
644
    }
645
646
    /**
647
     * Creates a SQL command for adding a check constraint to an existing table.
648
     * @param string $name the name of the check constraint.
649
     * The name will be properly quoted by the method.
650
     * @param string $table the table that the check constraint will be added to.
651
     * The name will be properly quoted by the method.
652
     * @param string $expression the SQL of the `CHECK` constraint.
653
     * @return string the SQL statement for adding a check constraint to an existing table.
654
     * @since 2.0.13
655
     */
656 2
    public function addCheck($name, $table, $expression)
657
    {
658 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
659 2
            . $this->db->quoteColumnName($name) . ' CHECK (' . $this->db->quoteSql($expression) . ')';
660
    }
661
662
    /**
663
     * Creates a SQL command for dropping a check constraint.
664
     * @param string $name the name of the check constraint to be dropped.
665
     * The name will be properly quoted by the method.
666
     * @param string $table the table whose check constraint is to be dropped.
667
     * The name will be properly quoted by the method.
668
     * @return string the SQL statement for dropping a check constraint.
669
     * @since 2.0.13
670
     */
671 2
    public function dropCheck($name, $table)
672
    {
673 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
674 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
675
    }
676
677
    /**
678
     * Creates a SQL command for adding a default value constraint to an existing table.
679
     * @param string $name the name of the default value constraint.
680
     * The name will be properly quoted by the method.
681
     * @param string $table the table that the default value constraint will be added to.
682
     * The name will be properly quoted by the method.
683
     * @param string $column the name of the column to that the constraint will be added on.
684
     * The name will be properly quoted by the method.
685
     * @param mixed $value default value.
686
     * @return string the SQL statement for adding a default value constraint to an existing table.
687
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
688
     * @since 2.0.13
689
     */
690
    public function addDefaultValue($name, $table, $column, $value)
691
    {
692
        throw new NotSupportedException($this->db->getDriverName() . ' does not support adding default value constraints.');
693
    }
694
695
    /**
696
     * Creates a SQL command for dropping a default value constraint.
697
     * @param string $name the name of the default value constraint to be dropped.
698
     * The name will be properly quoted by the method.
699
     * @param string $table the table whose default value constraint is to be dropped.
700
     * The name will be properly quoted by the method.
701
     * @return string the SQL statement for dropping a default value constraint.
702
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
703
     * @since 2.0.13
704
     */
705
    public function dropDefaultValue($name, $table)
706
    {
707
        throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
708
    }
709
710
    /**
711
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
712
     * The sequence will be reset such that the primary key of the next new row inserted
713
     * will have the specified value or 1.
714
     * @param string $table the name of the table whose primary key sequence will be reset
715
     * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
716
     * the next new row's primary key will have a value 1.
717
     * @return string the SQL statement for resetting sequence
718
     * @throws NotSupportedException if this is not supported by the underlying DBMS
719
     */
720
    public function resetSequence($table, $value = null)
721
    {
722
        throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
723
    }
724
725
    /**
726
     * Builds a SQL statement for enabling or disabling integrity check.
727
     * @param bool $check whether to turn on or off the integrity check.
728
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
729
     * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
730
     * @return string the SQL statement for checking integrity
731
     * @throws NotSupportedException if this is not supported by the underlying DBMS
732
     */
733
    public function checkIntegrity($check = true, $schema = '', $table = '')
734
    {
735
        throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
736
    }
737
738
    /**
739
     * Builds a SQL command for adding comment to column.
740
     *
741
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
742
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
743
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
744
     * @return string the SQL statement for adding comment on column
745
     * @since 2.0.8
746
     */
747 2
    public function addCommentOnColumn($table, $column, $comment)
748
    {
749 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment);
750
    }
751
752
    /**
753
     * Builds a SQL command for adding comment to table.
754
     *
755
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
756
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
757
     * @return string the SQL statement for adding comment on table
758
     * @since 2.0.8
759
     */
760 1
    public function addCommentOnTable($table, $comment)
761
    {
762 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment);
763
    }
764
765
    /**
766
     * Builds a SQL command for adding comment to column.
767
     *
768
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
769
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
770
     * @return string the SQL statement for adding comment on column
771
     * @since 2.0.8
772
     */
773 2
    public function dropCommentFromColumn($table, $column)
774
    {
775 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL';
776
    }
777
778
    /**
779
     * Builds a SQL command for adding comment to table.
780
     *
781
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
782
     * @return string the SQL statement for adding comment on column
783
     * @since 2.0.8
784
     */
785 1
    public function dropCommentFromTable($table)
786
    {
787 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL';
788
    }
789
790
    /**
791
     * Creates a SQL View.
792
     *
793
     * @param string $viewName the name of the view to be created.
794
     * @param string|Query $subQuery the select statement which defines the view.
795
     * This can be either a string or a [[Query]] object.
796
     * @return string the `CREATE VIEW` SQL statement.
797
     * @since 2.0.14
798
     */
799 3
    public function createView($viewName, $subQuery)
800
    {
801 3
        if ($subQuery instanceof Query) {
802 3
            [$rawQuery, $params] = $this->build($subQuery);
0 ignored issues
show
Bug introduced by
The variable $rawQuery does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $params seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
803 3
            array_walk(
804 3
                $params,
805 3
                function(&$param) {
806 3
                    $param = $this->db->quoteValue($param);
807 3
                }
808
            );
809 3
            $subQuery = strtr($rawQuery, $params);
810
        }
811
812 3
        return 'CREATE VIEW ' . $this->db->quoteTableName($viewName) . ' AS ' . $subQuery;
813
    }
814
815
    /**
816
     * Drops a SQL View.
817
     *
818
     * @param string $viewName the name of the view to be dropped.
819
     * @return string the `DROP VIEW` SQL statement.
820
     * @since 2.0.14
821
     */
822 3
    public function dropView($viewName)
823
    {
824 3
        return 'DROP VIEW ' . $this->db->quoteTableName($viewName);
825
    }
826
827
    /**
828
     * Converts an abstract column type into a physical column type.
829
     *
830
     * The conversion is done using the type map specified in [[typeMap]].
831
     * The following abstract column types are supported (using MySQL as an example to explain the corresponding
832
     * physical types):
833
     *
834
     * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
835
     * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
836
     * - `upk`: an unsigned auto-incremental primary key type, will be converted into "int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY"
837
     * - `char`: char type, will be converted into "char(1)"
838
     * - `string`: string type, will be converted into "varchar(255)"
839
     * - `text`: a long string type, will be converted into "text"
840
     * - `smallint`: a small integer type, will be converted into "smallint(6)"
841
     * - `integer`: integer type, will be converted into "int(11)"
842
     * - `bigint`: a big integer type, will be converted into "bigint(20)"
843
     * - `boolean`: boolean type, will be converted into "tinyint(1)"
844
     * - `float``: float number type, will be converted into "float"
845
     * - `decimal`: decimal number type, will be converted into "decimal"
846
     * - `datetime`: datetime type, will be converted into "datetime"
847
     * - `timestamp`: timestamp type, will be converted into "timestamp"
848
     * - `time`: time type, will be converted into "time"
849
     * - `date`: date type, will be converted into "date"
850
     * - `money`: money type, will be converted into "decimal(19,4)"
851
     * - `binary`: binary data type, will be converted into "blob"
852
     *
853
     * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
854
     * the first part will be converted, and the rest of the parts will be appended to the converted result.
855
     * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
856
     *
857
     * For some of the abstract types you can also specify a length or precision constraint
858
     * by appending it in round brackets directly to the type.
859
     * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
860
     * If the underlying DBMS does not support these kind of constraints for a type it will
861
     * be ignored.
862
     *
863
     * If a type cannot be found in [[typeMap]], it will be returned without any change.
864
     * @param string|ColumnSchemaBuilder $type abstract column type
865
     * @return string physical column type.
866
     */
867 132
    public function getColumnType($type)
868
    {
869 132
        if ($type instanceof ColumnSchemaBuilder) {
870 27
            $type = $type->__toString();
871
        }
872
873 132
        if (isset($this->typeMap[$type])) {
874 117
            return $this->typeMap[$type];
875 71
        } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
876 37
            if (isset($this->typeMap[$matches[1]])) {
877 37
                return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
878
            }
879 46
        } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
880 43
            if (isset($this->typeMap[$matches[1]])) {
881 43
                return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
882
            }
883
        }
884
885 30
        return $type;
886
    }
887
888
    /**
889
     * @param array $columns
890
     * @param array $params the binding parameters to be populated
891
     * @param bool $distinct
892
     * @param string $selectOption
893
     * @return string the SELECT clause built from [[Query::$select]].
894
     */
895 995
    public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
896
    {
897 995
        $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
898 995
        if ($selectOption !== null) {
899
            $select .= ' ' . $selectOption;
900
        }
901
902 995
        if (empty($columns)) {
903 772
            return $select . ' *';
904
        }
905
906 480
        foreach ($columns as $i => $column) {
907 480
            if ($column instanceof Expression) {
908 6
                if (is_int($i)) {
909 6
                    $columns[$i] = $column->expression;
910
                } else {
911 3
                    $columns[$i] = $column->expression . ' AS ' . $this->db->quoteColumnName($i);
912
                }
913 6
                $params = array_merge($params, $column->params);
914 477
            } elseif ($column instanceof Query) {
915 3
                [$sql, $params] = $this->build($column, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
916 3
                $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
917 477
            } elseif (is_string($i)) {
918 23
                if (strpos($column, '(') === false) {
919 23
                    $column = $this->db->quoteColumnName($column);
920
                }
921 23
                $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
922 472
            } elseif (strpos($column, '(') === false) {
923 391
                if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
924 6
                    $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
925
                } else {
926 480
                    $columns[$i] = $this->db->quoteColumnName($column);
927
                }
928
            }
929
        }
930
931 480
        return $select . ' ' . implode(', ', $columns);
932
    }
933
934
    /**
935
     * @param array $tables
936
     * @param array $params the binding parameters to be populated
937
     * @return string the FROM clause built from [[Query::$from]].
938
     */
939 995
    public function buildFrom($tables, &$params)
940
    {
941 995
        if (empty($tables)) {
942 274
            return '';
943
        }
944
945 740
        $tables = $this->quoteTableNames($tables, $params);
946
947 740
        return 'FROM ' . implode(', ', $tables);
948
    }
949
950
    /**
951
     * @param array $joins
952
     * @param array $params the binding parameters to be populated
953
     * @return string the JOIN clause built from [[Query::$join]].
954
     * @throws Exception if the $joins parameter is not in proper format
955
     */
956 995
    public function buildJoin($joins, &$params)
957
    {
958 995
        if (empty($joins)) {
959 983
            return '';
960
        }
961
962 54
        foreach ($joins as $i => $join) {
963 54
            if (!is_array($join) || !isset($join[0], $join[1])) {
964
                throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
965
            }
966
            // 0:join type, 1:join table, 2:on-condition (optional)
967 54
            [$joinType, $table] = $join;
0 ignored issues
show
Bug introduced by
The variable $joinType does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $table does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
968 54
            $tables = $this->quoteTableNames((array) $table, $params);
969 54
            $table = reset($tables);
970 54
            $joins[$i] = "$joinType $table";
971 54
            if (isset($join[2])) {
972 54
                $condition = $this->buildCondition($join[2], $params);
973 54
                if ($condition !== '') {
974 54
                    $joins[$i] .= ' ON ' . $condition;
975
                }
976
            }
977
        }
978
979 54
        return implode($this->separator, $joins);
980
    }
981
982
    /**
983
     * Quotes table names passed.
984
     *
985
     * @param array $tables
986
     * @param array $params
987
     * @return array
988
     */
989 740
    private function quoteTableNames($tables, &$params)
990
    {
991 740
        foreach ($tables as $i => $table) {
992 740
            if ($table instanceof Query) {
993 10
                [$sql, $params] = $this->build($table, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
994 10
                $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
995 740
            } elseif (is_string($i)) {
996 76
                if (strpos($table, '(') === false) {
997 67
                    $table = $this->db->quoteTableName($table);
998
                }
999 76
                $tables[$i] = "$table " . $this->db->quoteTableName($i);
1000 722
            } elseif (strpos($table, '(') === false) {
1001 718
                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
1002 21
                    $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
1003
                } else {
1004 740
                    $tables[$i] = $this->db->quoteTableName($table);
1005
                }
1006
            }
1007
        }
1008
1009 740
        return $tables;
1010
    }
1011
1012
    /**
1013
     * @param string|array $condition
1014
     * @param array $params the binding parameters to be populated
1015
     * @return string the WHERE clause built from [[Query::$where]].
1016
     */
1017 1070
    public function buildWhere($condition, &$params)
1018
    {
1019 1070
        $where = $this->buildCondition($condition, $params);
1020
1021 1070
        return $where === '' ? '' : 'WHERE ' . $where;
1022
    }
1023
1024
    /**
1025
     * @param array $columns
1026
     * @param array $params the binding parameters to be populated
1027
     * @return string the GROUP BY clause
1028
     */
1029 995
    public function buildGroupBy($columns, &$params)
1030
    {
1031 995
        if (empty($columns)) {
1032 989
            return '';
1033
        }
1034 21
        foreach ($columns as $i => $column) {
1035 21
            if ($column instanceof Expression) {
1036 3
                $columns[$i] = $column->expression;
1037 3
                $params = array_merge($params, $column->params);
1038 21
            } elseif (strpos($column, '(') === false) {
1039 21
                $columns[$i] = $this->db->quoteColumnName($column);
1040
            }
1041
        }
1042
1043 21
        return 'GROUP BY ' . implode(', ', $columns);
1044
    }
1045
1046
    /**
1047
     * @param string|array $condition
1048
     * @param array $params the binding parameters to be populated
1049
     * @return string the HAVING clause built from [[Query::$having]].
1050
     */
1051 995
    public function buildHaving($condition, &$params)
1052
    {
1053 995
        $having = $this->buildCondition($condition, $params);
1054
1055 995
        return $having === '' ? '' : 'HAVING ' . $having;
1056
    }
1057
1058
    /**
1059
     * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL.
1060
     * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
1061
     * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
1062
     * @param int $limit the limit number. See [[Query::limit]] for more details.
1063
     * @param int $offset the offset number. See [[Query::offset]] for more details.
1064
     * @param array $params the binding parameters to be populated
1065
     * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
1066
     */
1067 995
    public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params)
1068
    {
1069 995
        $orderBy = $this->buildOrderBy($orderBy, $params);
1070 995
        if ($orderBy !== '') {
1071 171
            $sql .= $this->separator . $orderBy;
1072
        }
1073 995
        $limit = $this->buildLimit($limit, $offset);
1074 995
        if ($limit !== '') {
1075 67
            $sql .= $this->separator . $limit;
1076
        }
1077
1078 995
        return $sql;
1079
    }
1080
1081
    /**
1082
     * @param array $columns
1083
     * @param array $params the binding parameters to be populated
1084
     * @return string the ORDER BY clause built from [[Query::$orderBy]].
1085
     */
1086 995
    public function buildOrderBy($columns, &$params)
1087
    {
1088 995
        if (empty($columns)) {
1089 960
            return '';
1090
        }
1091 171
        $orders = [];
1092 171
        foreach ($columns as $name => $direction) {
1093 171
            if ($direction instanceof Expression) {
1094 3
                $orders[] = $direction->expression;
1095 3
                $params = array_merge($params, $direction->params);
1096
            } else {
1097 171
                $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
1098
            }
1099
        }
1100
1101 171
        return 'ORDER BY ' . implode(', ', $orders);
1102
    }
1103
1104
    /**
1105
     * @param int $limit
1106
     * @param int $offset
1107
     * @return string the LIMIT and OFFSET clauses
1108
     */
1109 344
    public function buildLimit($limit, $offset)
1110
    {
1111 344
        $sql = '';
1112 344
        if ($this->hasLimit($limit)) {
1113 17
            $sql = 'LIMIT ' . $limit;
1114
        }
1115 344
        if ($this->hasOffset($offset)) {
1116 3
            $sql .= ' OFFSET ' . $offset;
1117
        }
1118
1119 344
        return ltrim($sql);
1120
    }
1121
1122
    /**
1123
     * Checks to see if the given limit is effective.
1124
     * @param mixed $limit the given limit
1125
     * @return bool whether the limit is effective
1126
     */
1127 647
    protected function hasLimit($limit)
1128
    {
1129 647
        return ($limit instanceof Expression) || ctype_digit((string) $limit);
1130
    }
1131
1132
    /**
1133
     * Checks to see if the given offset is effective.
1134
     * @param mixed $offset the given offset
1135
     * @return bool whether the offset is effective
1136
     */
1137 647
    protected function hasOffset($offset)
1138
    {
1139 647
        return ($offset instanceof Expression) || ctype_digit((string) $offset) && (string) $offset !== '0';
1140
    }
1141
1142
    /**
1143
     * @param array $unions
1144
     * @param array $params the binding parameters to be populated
1145
     * @return string the UNION clause built from [[Query::$union]].
1146
     */
1147 692
    public function buildUnion($unions, &$params)
1148
    {
1149 692
        if (empty($unions)) {
1150 692
            return '';
1151
        }
1152
1153 8
        $result = '';
1154
1155 8
        foreach ($unions as $i => $union) {
1156 8
            $query = $union['query'];
1157 8
            if ($query instanceof Query) {
1158 8
                [$unions[$i]['query'], $params] = $this->build($query, $params);
1159
            }
1160
1161 8
            $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
1162
        }
1163
1164 8
        return trim($result);
1165
    }
1166
1167
    /**
1168
     * Processes columns and properly quotes them if necessary.
1169
     * It will join all columns into a string with comma as separators.
1170
     * @param string|array $columns the columns to be processed
1171
     * @return string the processing result
1172
     */
1173 32
    public function buildColumns($columns)
1174
    {
1175 32
        if (!is_array($columns)) {
1176 27
            if (strpos($columns, '(') !== false) {
1177
                return $columns;
1178
            }
1179
1180 27
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
1181
        }
1182 32
        foreach ($columns as $i => $column) {
1183 32
            if ($column instanceof Expression) {
1184
                $columns[$i] = $column->expression;
1185 32
            } elseif (strpos($column, '(') === false) {
1186 32
                $columns[$i] = $this->db->quoteColumnName($column);
1187
            }
1188
        }
1189
1190 32
        return is_array($columns) ? implode(', ', $columns) : $columns;
1191
    }
1192
1193
    /**
1194
     * Parses the condition specification and generates the corresponding SQL expression.
1195
     * @param string|array|Expression $condition the condition specification. Please refer to [[Query::where()]]
1196
     * on how to specify a condition.
1197
     * @param array $params the binding parameters to be populated
1198
     * @return string the generated SQL expression
1199
     */
1200 1070
    public function buildCondition($condition, &$params)
1201
    {
1202 1070
        if ($condition instanceof Expression) {
1203 9
            foreach ($condition->params as $n => $v) {
1204 9
                $params[$n] = $v;
1205
            }
1206
1207 9
            return $condition->expression;
1208 1070
        } elseif (!is_array($condition)) {
1209 1057
            return (string) $condition;
1210
        } elseif (empty($condition)) {
1211
            return '';
1212
        }
1213
1214 837
        if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
1215 487
            $operator = strtoupper($condition[0]);
1216 487
            if (isset($this->conditionBuilders[$operator])) {
1217 454
                $method = $this->conditionBuilders[$operator];
1218
            } else {
1219 39
                $method = 'buildSimpleCondition';
1220
            }
1221 487
            array_shift($condition);
1222 487
            return $this->$method($operator, $condition, $params);
1223
        }
1224
1225
        // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
1226 608
        return $this->buildHashCondition($condition, $params);
1227
    }
1228
1229
    /**
1230
     * Creates a condition based on column-value pairs.
1231
     * @param array $condition the condition specification.
1232
     * @param array $params the binding parameters to be populated
1233
     * @return string the generated SQL expression
1234
     */
1235 608
    public function buildHashCondition($condition, &$params)
1236
    {
1237 608
        $parts = [];
1238 608
        foreach ($condition as $column => $value) {
1239 608
            if (ArrayHelper::isTraversable($value) || $value instanceof Query) {
1240
                // IN condition
1241 75
                $parts[] = $this->buildInCondition('IN', [$column, $value], $params);
1242
            } else {
1243 593
                if (strpos($column, '(') === false) {
1244 587
                    $column = $this->db->quoteColumnName($column);
1245
                }
1246 593
                if ($value === null) {
1247 15
                    $parts[] = "$column IS NULL";
1248 593
                } elseif ($value instanceof Expression) {
1249 131
                    $parts[] = "$column=" . $value->expression;
1250 131
                    foreach ($value->params as $n => $v) {
1251 131
                        $params[$n] = $v;
1252
                    }
1253
                } else {
1254 593
                    $phName = self::PARAM_PREFIX . count($params);
1255 593
                    $parts[] = "$column=$phName";
1256 608
                    $params[$phName] = $value;
1257
                }
1258
            }
1259
        }
1260
1261 608
        return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
1262
    }
1263
1264
    /**
1265
     * Connects two or more SQL expressions with the `AND` or `OR` operator.
1266
     * @param string $operator the operator to use for connecting the given operands
1267
     * @param array $operands the SQL expressions to connect.
1268
     * @param array $params the binding parameters to be populated
1269
     * @return string the generated SQL expression
1270
     */
1271 202
    public function buildAndCondition($operator, $operands, &$params)
1272
    {
1273 202
        $parts = [];
1274 202
        foreach ($operands as $operand) {
1275 202
            if (is_array($operand)) {
1276 177
                $operand = $this->buildCondition($operand, $params);
1277
            }
1278 202
            if ($operand instanceof Expression) {
1279 6
                foreach ($operand->params as $n => $v) {
1280 6
                    $params[$n] = $v;
1281
                }
1282 6
                $operand = $operand->expression;
1283
            }
1284 202
            if ($operand !== '') {
1285 202
                $parts[] = $operand;
1286
            }
1287
        }
1288 202
        if (!empty($parts)) {
1289 202
            return '(' . implode(") $operator (", $parts) . ')';
1290
        }
1291
1292
        return '';
1293
    }
1294
1295
    /**
1296
     * Inverts an SQL expressions with `NOT` operator.
1297
     * @param string $operator the operator to use for connecting the given operands
1298
     * @param array $operands the SQL expressions to connect.
1299
     * @param array $params the binding parameters to be populated
1300
     * @return string the generated SQL expression
1301
     * @throws InvalidArgumentException if wrong number of operands have been given.
1302
     */
1303 6
    public function buildNotCondition($operator, $operands, &$params)
1304
    {
1305 6
        if (count($operands) !== 1) {
1306
            throw new InvalidArgumentException("Operator '$operator' requires exactly one operand.");
1307
        }
1308
1309 6
        $operand = reset($operands);
1310 6
        if (is_array($operand) || $operand instanceof Expression) {
1311 3
            $operand = $this->buildCondition($operand, $params);
1312
        }
1313 6
        if ($operand === '') {
1314
            return '';
1315
        }
1316
1317 6
        return "$operator ($operand)";
1318
    }
1319
1320
    /**
1321
     * Creates an SQL expressions with the `BETWEEN` operator.
1322
     * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
1323
     * @param array $operands the first operand is the column name. The second and third operands
1324
     * describe the interval that column value should be in.
1325
     * @param array $params the binding parameters to be populated
1326
     * @return string the generated SQL expression
1327
     * @throws InvalidArgumentException if wrong number of operands have been given.
1328
     */
1329 21
    public function buildBetweenCondition($operator, $operands, &$params)
1330
    {
1331 21
        if (!isset($operands[0], $operands[1], $operands[2])) {
1332
            throw new InvalidArgumentException("Operator '$operator' requires three operands.");
1333
        }
1334
1335 21
        [$column, $value1, $value2] = $operands;
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $value1 does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value2 does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1336
1337 21
        if (strpos($column, '(') === false) {
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1338 21
            $column = $this->db->quoteColumnName($column);
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1339
        }
1340 21
        if ($value1 instanceof Expression) {
1341 12
            foreach ($value1->params as $n => $v) {
1342
                $params[$n] = $v;
1343
            }
1344 12
            $phName1 = $value1->expression;
1345
        } else {
1346 9
            $phName1 = self::PARAM_PREFIX . count($params);
1347 9
            $params[$phName1] = $value1;
1348
        }
1349 21
        if ($value2 instanceof Expression) {
1350 6
            foreach ($value2->params as $n => $v) {
1351
                $params[$n] = $v;
1352
            }
1353 6
            $phName2 = $value2->expression;
1354
        } else {
1355 15
            $phName2 = self::PARAM_PREFIX . count($params);
1356 15
            $params[$phName2] = $value2;
1357
        }
1358
1359 21
        return "$column $operator $phName1 AND $phName2";
0 ignored issues
show
Bug introduced by
The variable $column does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1360
    }
1361
1362
    /**
1363
     * Creates an SQL expressions with the `IN` operator.
1364
     * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
1365
     * @param array $operands the first operand is the column name. If it is an array
1366
     * a composite IN condition will be generated.
1367
     * The second operand is an array of values that column value should be among.
1368
     * If it is an empty array the generated expression will be a `false` value if
1369
     * operator is `IN` and empty if operator is `NOT IN`.
1370
     * @param array $params the binding parameters to be populated
1371
     * @return string the generated SQL expression
1372
     * @throws Exception if wrong number of operands have been given.
1373
     */
1374 235
    public function buildInCondition($operator, $operands, &$params)
1375
    {
1376 235
        if (!isset($operands[0], $operands[1])) {
1377
            throw new Exception("Operator '$operator' requires two operands.");
1378
        }
1379
1380 235
        [$column, $values] = $operands;
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1381
1382 235
        if ($column === []) {
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1383
            // no columns to test against
1384
            return $operator === 'IN' ? '0=1' : '';
1385
        }
1386
1387 235
        if ($values instanceof Query) {
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1388 14
            return $this->buildSubqueryInCondition($operator, $column, $values, $params);
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1389
        }
1390 221
        if (!is_array($values) && !$values instanceof \Traversable) {
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1391
            // ensure values is an array
1392 3
            $values = (array) $values;
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1393
        }
1394
1395 221
        if ($column instanceof \Traversable || ((is_array($column) || $column instanceof \Countable) && count($column) > 1)) {
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1396 15
            return $this->buildCompositeInCondition($operator, $column, $values, $params);
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $values does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1397 209
        } elseif (is_array($column)) {
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1398 136
            $column = reset($column);
1399
        }
1400
1401 209
        $sqlValues = [];
1402 209
        foreach ($values as $i => $value) {
1403 209
            if (is_array($value) || $value instanceof \ArrayAccess) {
1404
                $value = isset($value[$column]) ? $value[$column] : null;
0 ignored issues
show
Bug introduced by
The variable $column does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1405
            }
1406 209
            if ($value === null) {
1407
                $sqlValues[$i] = 'NULL';
1408 209
            } elseif ($value instanceof Expression) {
1409
                $sqlValues[$i] = $value->expression;
1410
                foreach ($value->params as $n => $v) {
1411
                    $params[$n] = $v;
1412
                }
1413
            } else {
1414 209
                $phName = self::PARAM_PREFIX . count($params);
1415 209
                $params[$phName] = $value;
1416 209
                $sqlValues[$i] = $phName;
1417
            }
1418
        }
1419
1420 209
        if (empty($sqlValues)) {
1421 18
            return $operator === 'IN' ? '0=1' : '';
1422
        }
1423
1424 209
        if (strpos($column, '(') === false) {
1425 209
            $column = $this->db->quoteColumnName($column);
1426
        }
1427
1428 209
        if (count($sqlValues) > 1) {
1429 145
            return "$column $operator (" . implode(', ', $sqlValues) . ')';
1430
        }
1431
1432 140
        $operator = $operator === 'IN' ? '=' : '<>';
1433 140
        return $column . $operator . reset($sqlValues);
1434
    }
1435
1436
    /**
1437
     * Builds SQL for IN condition.
1438
     *
1439
     * @param string $operator
1440
     * @param array $columns
1441
     * @param Query $values
1442
     * @param array $params
1443
     * @return string SQL
1444
     */
1445 14
    protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
1446
    {
1447 14
        [$sql, $params] = $this->build($values, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1448 14
        if (is_array($columns)) {
1449 4
            foreach ($columns as $i => $col) {
1450 4
                if (strpos($col, '(') === false) {
1451 4
                    $columns[$i] = $this->db->quoteColumnName($col);
1452
                }
1453
            }
1454
1455 4
            return '(' . implode(', ', $columns) . ") $operator ($sql)";
1456
        }
1457
1458 10
        if (strpos($columns, '(') === false) {
1459 10
            $columns = $this->db->quoteColumnName($columns);
1460
        }
1461
1462 10
        return "$columns $operator ($sql)";
1463
    }
1464
1465
    /**
1466
     * Builds SQL for IN condition.
1467
     *
1468
     * @param string $operator
1469
     * @param array|\Traversable $columns
1470
     * @param array $values
1471
     * @param array $params
1472
     * @return string SQL
1473
     */
1474 10
    protected function buildCompositeInCondition($operator, $columns, $values, &$params)
1475
    {
1476 10
        $vss = [];
1477 10
        foreach ($values as $value) {
1478 10
            $vs = [];
1479 10
            foreach ($columns as $column) {
1480 10
                if (isset($value[$column])) {
1481 10
                    $phName = self::PARAM_PREFIX . count($params);
1482 10
                    $params[$phName] = $value[$column];
1483 10
                    $vs[] = $phName;
1484
                } else {
1485 10
                    $vs[] = 'NULL';
1486
                }
1487
            }
1488 10
            $vss[] = '(' . implode(', ', $vs) . ')';
1489
        }
1490
1491 10
        if (empty($vss)) {
1492
            return $operator === 'IN' ? '0=1' : '';
1493
        }
1494
1495 10
        $sqlColumns = [];
1496 10
        foreach ($columns as $i => $column) {
1497 10
            $sqlColumns[] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
1498
        }
1499
1500 10
        return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')';
1501
    }
1502
1503
    /**
1504
     * Creates an SQL expressions with the `LIKE` operator.
1505
     * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
1506
     * @param array $operands an array of two or three operands
1507
     *
1508
     * - The first operand is the column name.
1509
     * - The second operand is a single value or an array of values that column value
1510
     *   should be compared with. If it is an empty array the generated expression will
1511
     *   be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
1512
     *   is `NOT LIKE` or `OR NOT LIKE`.
1513
     * - An optional third operand can also be provided to specify how to escape special characters
1514
     *   in the value(s). The operand should be an array of mappings from the special characters to their
1515
     *   escaped counterparts. If this operand is not provided, a default escape mapping will be used.
1516
     *   You may use `false` or an empty array to indicate the values are already escaped and no escape
1517
     *   should be applied. Note that when using an escape mapping (or the third operand is not provided),
1518
     *   the values will be automatically enclosed within a pair of percentage characters.
1519
     * @param array $params the binding parameters to be populated
1520
     * @return string the generated SQL expression
1521
     * @throws InvalidArgumentException if wrong number of operands have been given.
1522
     */
1523 75
    public function buildLikeCondition($operator, $operands, &$params)
1524
    {
1525 75
        if (!isset($operands[0], $operands[1])) {
1526
            throw new InvalidArgumentException("Operator '$operator' requires two operands.");
1527
        }
1528
1529 75
        $escape = isset($operands[2]) ? $operands[2] : $this->likeEscapingReplacements;
1530 75
        unset($operands[2]);
1531
1532 75
        if (!preg_match('/^(AND |OR |)(((NOT |))I?LIKE)/', $operator, $matches)) {
1533
            throw new InvalidArgumentException("Invalid operator '$operator'.");
1534
        }
1535 75
        $andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
1536 75
        $not = !empty($matches[3]);
1537 75
        $operator = $matches[2];
1538
1539 75
        [$column, $values] = $operands;
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1540
1541 75
        if (!is_array($values)) {
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1542 31
            $values = [$values];
0 ignored issues
show
Bug introduced by
The variable $values seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1543
        }
1544
1545 75
        if (empty($values)) {
1546 16
            return $not ? '' : '0=1';
1547
        }
1548
1549 59
        if (strpos($column, '(') === false) {
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1550 59
            $column = $this->db->quoteColumnName($column);
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1551
        }
1552
1553 59
        $parts = [];
1554 59
        foreach ($values as $value) {
1555 59
            if ($value instanceof Expression) {
1556 24
                foreach ($value->params as $n => $v) {
1557
                    $params[$n] = $v;
1558
                }
1559 24
                $phName = $value->expression;
1560
            } else {
1561 47
                $phName = self::PARAM_PREFIX . count($params);
1562 47
                $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
1563
            }
1564 59
            $escapeSql = '';
1565 59
            if ($this->likeEscapeCharacter !== null) {
1566 17
                $escapeSql = " ESCAPE '{$this->likeEscapeCharacter}'";
1567
            }
1568 59
            $parts[] = "{$column} {$operator} {$phName}{$escapeSql}";
0 ignored issues
show
Bug introduced by
The variable $column does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1569
        }
1570
1571 59
        return implode($andor, $parts);
1572
    }
1573
1574
    /**
1575
     * Creates an SQL expressions with the `EXISTS` operator.
1576
     * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
1577
     * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
1578
     * @param array $params the binding parameters to be populated
1579
     * @return string the generated SQL expression
1580
     * @throws InvalidArgumentException if the operand is not a [[Query]] object.
1581
     */
1582 18
    public function buildExistsCondition($operator, $operands, &$params)
1583
    {
1584 18
        if ($operands[0] instanceof Query) {
1585 18
            [$sql, $params] = $this->build($operands[0], $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1586 18
            return "$operator ($sql)";
1587
        }
1588
1589
        throw new InvalidArgumentException('Subquery for EXISTS operator must be a Query object.');
1590
    }
1591
1592
    /**
1593
     * Creates an SQL expressions like `"column" operator value`.
1594
     * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
1595
     * @param array $operands contains two column names.
1596
     * @param array $params the binding parameters to be populated
1597
     * @return string the generated SQL expression
1598
     * @throws InvalidArgumentException if wrong number of operands have been given.
1599
     */
1600 39
    public function buildSimpleCondition($operator, $operands, &$params)
1601
    {
1602 39
        if (count($operands) !== 2) {
1603
            throw new InvalidArgumentException("Operator '$operator' requires two operands.");
1604
        }
1605
1606 39
        [$column, $value] = $operands;
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1607
1608 39
        if (strpos($column, '(') === false) {
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1609 39
            $column = $this->db->quoteColumnName($column);
0 ignored issues
show
Bug introduced by
The variable $column seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1610
        }
1611
1612 39
        if ($value === null) {
1613
            return "$column $operator NULL";
0 ignored issues
show
Bug introduced by
The variable $column does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1614 39
        } elseif ($value instanceof Expression) {
1615 6
            foreach ($value->params as $n => $v) {
1616 3
                $params[$n] = $v;
1617
            }
1618
1619 6
            return "$column $operator {$value->expression}";
1620 33
        } elseif ($value instanceof Query) {
1621 3
            [$sql, $params] = $this->build($value, $params);
0 ignored issues
show
Bug introduced by
The variable $sql does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1622 3
            return "$column $operator ($sql)";
1623
        }
1624
1625 30
        $phName = self::PARAM_PREFIX . count($params);
1626 30
        $params[$phName] = $value;
1627 30
        return "$column $operator $phName";
1628
    }
1629
1630
    /**
1631
     * Creates a SELECT EXISTS() SQL statement.
1632
     * @param string $rawSql the subquery in a raw form to select from.
1633
     * @return string the SELECT EXISTS() SQL statement.
1634
     * @since 2.0.8
1635
     */
1636 61
    public function selectExists($rawSql)
1637
    {
1638 61
        return 'SELECT EXISTS(' . $rawSql . ')';
1639
    }
1640
}
1641