Completed
Push — 2.1-master-merge ( 240673 )
by Alexander
13:45
created

QueryBuilder::quoteTableNames()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 6.9811
c 0
b 0
f 0
ccs 14
cts 14
cp 1
cc 7
eloc 15
nc 7
nop 2
crap 7
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
14
/**
15
 * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object.
16
 *
17
 * SQL statements are created from [[Query]] objects using the [[build()]]-method.
18
 *
19
 * QueryBuilder is also used by [[Command]] to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE.
20
 *
21
 * For more details and usage information on QueryBuilder, see the [guide article on query builders](guide:db-query-builder).
22
 *
23
 * @author Qiang Xue <[email protected]>
24
 * @since 2.0
25
 */
26
class QueryBuilder extends \yii\base\BaseObject
27
{
28
    /**
29
     * The prefix for automatically generated query binding parameters.
30
     */
31
    const PARAM_PREFIX = ':qp';
32
33
    /**
34
     * @var Connection the database connection.
35
     */
36
    public $db;
37
    /**
38
     * @var string the separator between different fragments of a SQL statement.
39
     * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
40
     */
41
    public $separator = ' ';
42
    /**
43
     * @var array the abstract column types mapped to physical column types.
44
     * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
45
     * Child classes should override this property to declare supported type mappings.
46
     */
47
    public $typeMap = [];
48
49
    /**
50
     * @var array map of query condition to builder methods.
51
     * These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
52
     */
53
    protected $conditionBuilders = [
54
        'NOT' => 'buildNotCondition',
55
        'AND' => 'buildAndCondition',
56
        'OR' => 'buildAndCondition',
57
        'BETWEEN' => 'buildBetweenCondition',
58
        'NOT BETWEEN' => 'buildBetweenCondition',
59
        'IN' => 'buildInCondition',
60
        'NOT IN' => 'buildInCondition',
61
        'LIKE' => 'buildLikeCondition',
62
        'NOT LIKE' => 'buildLikeCondition',
63
        'OR LIKE' => 'buildLikeCondition',
64
        'OR NOT LIKE' => 'buildLikeCondition',
65
        'EXISTS' => 'buildExistsCondition',
66
        'NOT EXISTS' => 'buildExistsCondition',
67
    ];
68
    /**
69
     * @var array map of chars to their replacements in LIKE conditions.
70
     * By default it's configured to escape `%`, `_` and `\` with `\`.
71
     * @since 2.0.12.
72
     */
73
    protected $likeEscapingReplacements = [
74
        '%' => '\%',
75
        '_' => '\_',
76
        '\\' => '\\\\',
77
    ];
78
    /**
79
     * @var string|null character used to escape special characters in LIKE conditions.
80
     * By default it's assumed to be `\`.
81
     * @since 2.0.12
82
     */
83
    protected $likeEscapeCharacter;
84
85
86
    /**
87
     * Constructor.
88
     * @param Connection $connection the database connection.
89
     * @param array $config name-value pairs that will be used to initialize the object properties
90
     */
91 1169
    public function __construct($connection, $config = [])
92
    {
93 1169
        $this->db = $connection;
94 1169
        parent::__construct($config);
95 1169
    }
96
97
    /**
98
     * Generates a SELECT SQL statement from a [[Query]] object.
99
     * @param Query $query the [[Query]] object from which the SQL statement will be generated.
100
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
101
     * be included in the result with the additional parameters generated during the query building process.
102
     * @return array the generated SQL statement (the first array element) and the corresponding
103
     * parameters to be bound to the SQL statement (the second array element). The parameters returned
104
     * include those provided in `$params`.
105
     */
106 658
    public function build($query, $params = [])
107
    {
108 658
        $query = $query->prepare($this);
109
110 658
        $params = empty($params) ? $query->params : array_merge($params, $query->params);
111
112
        $clauses = [
113 658
            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
114 658
            $this->buildFrom($query->from, $params),
115 658
            $this->buildJoin($query->join, $params),
116 658
            $this->buildWhere($query->where, $params),
117 658
            $this->buildGroupBy($query->groupBy, $params),
118 658
            $this->buildHaving($query->having, $params),
119
        ];
120
121 658
        $sql = implode($this->separator, array_filter($clauses));
122 658
        $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset, $params);
123
124 658
        $union = $this->buildUnion($query->union, $params);
125 658
        if ($union !== '') {
126 8
            $sql = "($sql){$this->separator}$union";
127
        }
128
129 658
        return [$sql, $params];
130
    }
131
132
    /**
133
     * Creates an INSERT SQL statement.
134
     *
135
     * For example,
136
     *
137
     * ```php
138
     * $sql = $queryBuilder->insert('user', [
139
     *     'name' => 'Sam',
140
     *     'age' => 30,
141
     * ], $params);
142
     * ```
143
     *
144
     * The method will properly escape the table and column names.
145
     *
146
     * @param string $table the table that new rows will be inserted into.
147
     * @param array|\yii\db\Query $columns the column data (name => value) to be inserted into the table or instance
148
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
149
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
150
     * @param array $params the binding parameters that will be generated by this method.
151
     * They should be bound to the DB command later.
152
     * @return string the INSERT SQL
153
     */
154 266
    public function insert($table, $columns, &$params)
155
    {
156 266
        $schema = $this->db->getSchema();
157 266
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
158 266
            $columnSchemas = $tableSchema->columns;
159
        } else {
160
            $columnSchemas = [];
161
        }
162 266
        $names = [];
163 266
        $placeholders = [];
164 266
        $values = ' DEFAULT VALUES';
165 266
        if ($columns instanceof \yii\db\Query) {
166 10
            [$names, $values, $params] = $this->prepareInsertSelectSubQuery($columns, $schema);
167
        } else {
168 260
            foreach ($columns as $name => $value) {
169 258
                $names[] = $schema->quoteColumnName($name);
170 258
                if ($value instanceof Expression) {
171 5
                    $placeholders[] = $value->expression;
172 5
                    foreach ($value->params as $n => $v) {
173 5
                        $params[$n] = $v;
174
                    }
175 255
                } elseif ($value instanceof \yii\db\Query) {
176 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...
177 2
                    $placeholders[] = "($sql)";
178
                } else {
179 255
                    $phName = self::PARAM_PREFIX . count($params);
180 255
                    $placeholders[] = $phName;
181 258
                    $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
182
                }
183
            }
184
        }
185
186 260
        return 'INSERT INTO ' . $schema->quoteTableName($table)
187 260
            . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
188 260
            . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
189
    }
190
191
    /**
192
     * Prepare select-subquery and field names for INSERT INTO ... SELECT SQL statement.
193
     *
194
     * @param \yii\db\Query $columns Object, which represents select query.
195
     * @param \yii\db\Schema $schema Schema object to quote column name.
196
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
197
     * be included in the result with the additional parameters generated during the query building process.
198
     * @return array
199
     * @throws InvalidArgumentException if query's select does not contain named parameters only.
200
     * @since 2.0.11
201
     */
202 15
    protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
203
    {
204 15
        if (!is_array($columns->select) || empty($columns->select) || in_array('*', $columns->select)) {
205 9
            throw new InvalidArgumentException('Expected select query object with enumerated (named) parameters');
206
        }
207
208 6
        [$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...
209 6
        $names = [];
210 6
        $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...
211 6
        foreach ($columns->select as $title => $field) {
212 6
            if (is_string($title)) {
213 3
                $names[] = $schema->quoteColumnName($title);
214 3
            } elseif (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $field, $matches)) {
215 3
                $names[] = $schema->quoteColumnName($matches[2]);
216
            } else {
217 6
                $names[] = $schema->quoteColumnName($field);
218
            }
219
        }
220
221 6
        return [$names, $values, $params];
222
    }
223
224
    /**
225
     * Generates a batch INSERT SQL statement.
226
     *
227
     * For example,
228
     *
229
     * ```php
230
     * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [
231
     *     ['Tom', 30],
232
     *     ['Jane', 20],
233
     *     ['Linda', 25],
234
     * ]);
235
     * ```
236
     *
237
     * Note that the values in each row must match the corresponding column names.
238
     *
239
     * The method will properly escape the column names, and quote the values to be inserted.
240
     *
241
     * @param string $table the table that new rows will be inserted into.
242
     * @param array $columns the column names
243
     * @param array|\Generator $rows the rows to be batch inserted into the table
244
     * @return string the batch INSERT SQL statement
245
     */
246 22
    public function batchInsert($table, $columns, $rows)
247
    {
248 22
        if (empty($rows)) {
249 2
            return '';
250
        }
251
252 21
        $schema = $this->db->getSchema();
253 21
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
254 15
            $columnSchemas = $tableSchema->columns;
255
        } else {
256 6
            $columnSchemas = [];
257
        }
258
259 21
        $values = [];
260 21
        foreach ($rows as $row) {
261 19
            $vs = [];
262 19
            foreach ($row as $i => $value) {
263 19
                if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
264 10
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
265
                }
266 19
                if (is_string($value)) {
267 13
                    $value = $schema->quoteValue($value);
268 11
                } elseif (is_float($value)) {
269
                    // ensure type cast always has . as decimal separator in all locales
270 2
                    $value = str_replace(',', '.', (string) $value);
271 11
                } elseif ($value === false) {
272 4
                    $value = 0;
273 11
                } elseif ($value === null) {
274 6
                    $value = 'NULL';
275
                }
276 19
                $vs[] = $value;
277
            }
278 19
            $values[] = '(' . implode(', ', $vs) . ')';
279
        }
280 21
        if (empty($values)) {
281 2
            return '';
282
        }
283
284 19
        foreach ($columns as $i => $name) {
285 17
            $columns[$i] = $schema->quoteColumnName($name);
286
        }
287
288 19
        return 'INSERT INTO ' . $schema->quoteTableName($table)
289 19
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
290
    }
291
292
    /**
293
     * Creates an UPDATE SQL statement.
294
     *
295
     * For example,
296
     *
297
     * ```php
298
     * $params = [];
299
     * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params);
300
     * ```
301
     *
302
     * The method will properly escape the table and column names.
303
     *
304
     * @param string $table the table to be updated.
305
     * @param array $columns the column data (name => value) to be updated.
306
     * @param array|string $condition the condition that will be put in the WHERE part. Please
307
     * refer to [[Query::where()]] on how to specify condition.
308
     * @param array $params the binding parameters that will be modified by this method
309
     * so that they can be bound to the DB command later.
310
     * @return string the UPDATE SQL
311
     */
312 92
    public function update($table, $columns, $condition, &$params)
313
    {
314 92
        if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
315 92
            $columnSchemas = $tableSchema->columns;
316
        } else {
317
            $columnSchemas = [];
318
        }
319
320 92
        $lines = [];
321 92
        foreach ($columns as $name => $value) {
322 92
            if ($value instanceof Expression) {
323 6
                $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
324 6
                foreach ($value->params as $n => $v) {
325 6
                    $params[$n] = $v;
326
                }
327
            } else {
328 86
                $phName = self::PARAM_PREFIX . count($params);
329 86
                $lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
330 92
                $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
331
            }
332
        }
333
334 92
        $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
335 92
        $where = $this->buildWhere($condition, $params);
336
337 92
        return $where === '' ? $sql : $sql . ' ' . $where;
338
    }
339
340
    /**
341
     * Creates a DELETE SQL statement.
342
     *
343
     * For example,
344
     *
345
     * ```php
346
     * $sql = $queryBuilder->delete('user', 'status = 0');
347
     * ```
348
     *
349
     * The method will properly escape the table and column names.
350
     *
351
     * @param string $table the table where the data will be deleted from.
352
     * @param array|string $condition the condition that will be put in the WHERE part. Please
353
     * refer to [[Query::where()]] on how to specify condition.
354
     * @param array $params the binding parameters that will be modified by this method
355
     * so that they can be bound to the DB command later.
356
     * @return string the DELETE SQL
357
     */
358 308
    public function delete($table, $condition, &$params)
359
    {
360 308
        $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
361 308
        $where = $this->buildWhere($condition, $params);
362
363 308
        return $where === '' ? $sql : $sql . ' ' . $where;
364
    }
365
366
    /**
367
     * Builds a SQL statement for creating a new DB table.
368
     *
369
     * The columns in the new  table should be specified as name-definition pairs (e.g. 'name' => 'string'),
370
     * where name stands for a column name which will be properly quoted by the method, and definition
371
     * stands for the column type which can contain an abstract DB type.
372
     * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
373
     *
374
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
375
     * inserted into the generated SQL.
376
     *
377
     * For example,
378
     *
379
     * ```php
380
     * $sql = $queryBuilder->createTable('user', [
381
     *  'id' => 'pk',
382
     *  'name' => 'string',
383
     *  'age' => 'integer',
384
     * ]);
385
     * ```
386
     *
387
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
388
     * @param array $columns the columns (name => definition) in the new table.
389
     * @param string $options additional SQL fragment that will be appended to the generated SQL.
390
     * @return string the SQL statement for creating a new DB table.
391
     */
392 110
    public function createTable($table, $columns, $options = null)
393
    {
394 110
        $cols = [];
395 110
        foreach ($columns as $name => $type) {
396 110
            if (is_string($name)) {
397 110
                $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
398
            } else {
399 110
                $cols[] = "\t" . $type;
400
            }
401
        }
402 110
        $sql = 'CREATE TABLE ' . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
403
404 110
        return $options === null ? $sql : $sql . ' ' . $options;
405
    }
406
407
    /**
408
     * Builds a SQL statement for renaming a DB table.
409
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
410
     * @param string $newName the new table name. The name will be properly quoted by the method.
411
     * @return string the SQL statement for renaming a DB table.
412
     */
413 1
    public function renameTable($oldName, $newName)
414
    {
415 1
        return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
416
    }
417
418
    /**
419
     * Builds a SQL statement for dropping a DB table.
420
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
421
     * @return string the SQL statement for dropping a DB table.
422
     */
423 19
    public function dropTable($table)
424
    {
425 19
        return 'DROP TABLE ' . $this->db->quoteTableName($table);
426
    }
427
428
    /**
429
     * Builds a SQL statement for adding a primary key constraint to an existing table.
430
     * @param string $name the name of the primary key constraint.
431
     * @param string $table the table that the primary key constraint will be added to.
432
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
433
     * @return string the SQL statement for adding a primary key constraint to an existing table.
434
     */
435 6
    public function addPrimaryKey($name, $table, $columns)
436
    {
437 6
        if (is_string($columns)) {
438 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
439
        }
440
441 6
        foreach ($columns as $i => $col) {
442 6
            $columns[$i] = $this->db->quoteColumnName($col);
443
        }
444
445 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
446 6
            . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
447 6
            . implode(', ', $columns) . ')';
448
    }
449
450
    /**
451
     * Builds a SQL statement for removing a primary key constraint to an existing table.
452
     * @param string $name the name of the primary key constraint to be removed.
453
     * @param string $table the table that the primary key constraint will be removed from.
454
     * @return string the SQL statement for removing a primary key constraint from an existing table.
455
     */
456 2
    public function dropPrimaryKey($name, $table)
457
    {
458 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
459 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
460
    }
461
462
    /**
463
     * Builds a SQL statement for truncating a DB table.
464
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
465
     * @return string the SQL statement for truncating a DB table.
466
     */
467 9
    public function truncateTable($table)
468
    {
469 9
        return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table);
470
    }
471
472
    /**
473
     * Builds a SQL statement for adding a new DB column.
474
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
475
     * @param string $column the name of the new column. The name will be properly quoted by the method.
476
     * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
477
     * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
478
     * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
479
     * @return string the SQL statement for adding a new column.
480
     */
481 4
    public function addColumn($table, $column, $type)
482
    {
483 4
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
484 4
            . ' ADD ' . $this->db->quoteColumnName($column) . ' '
485 4
            . $this->getColumnType($type);
486
    }
487
488
    /**
489
     * Builds a SQL statement for dropping a DB column.
490
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
491
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
492
     * @return string the SQL statement for dropping a DB column.
493
     */
494
    public function dropColumn($table, $column)
495
    {
496
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
497
            . ' DROP COLUMN ' . $this->db->quoteColumnName($column);
498
    }
499
500
    /**
501
     * Builds a SQL statement for renaming a column.
502
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
503
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
504
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
505
     * @return string the SQL statement for renaming a DB column.
506
     */
507
    public function renameColumn($table, $oldName, $newName)
508
    {
509
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
510
            . ' RENAME COLUMN ' . $this->db->quoteColumnName($oldName)
511
            . ' TO ' . $this->db->quoteColumnName($newName);
512
    }
513
514
    /**
515
     * Builds a SQL statement for changing the definition of a column.
516
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
517
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
518
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
519
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
520
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
521
     * will become 'varchar(255) not null'.
522
     * @return string the SQL statement for changing the definition of a column.
523
     */
524 1
    public function alterColumn($table, $column, $type)
525
    {
526 1
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
527 1
            . $this->db->quoteColumnName($column) . ' '
528 1
            . $this->db->quoteColumnName($column) . ' '
529 1
            . $this->getColumnType($type);
530
    }
531
532
    /**
533
     * Builds a SQL statement for adding a foreign key constraint to an existing table.
534
     * The method will properly quote the table and column names.
535
     * @param string $name the name of the foreign key constraint.
536
     * @param string $table the table that the foreign key constraint will be added to.
537
     * @param string|array $columns the name of the column to that the constraint will be added on.
538
     * If there are multiple columns, separate them with commas or use an array to represent them.
539
     * @param string $refTable the table that the foreign key references to.
540
     * @param string|array $refColumns the name of the column that the foreign key references to.
541
     * If there are multiple columns, separate them with commas or use an array to represent them.
542
     * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
543
     * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
544
     * @return string the SQL statement for adding a foreign key constraint to an existing table.
545
     */
546 8
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
547
    {
548 8
        $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
549 8
            . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
550 8
            . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
551 8
            . ' REFERENCES ' . $this->db->quoteTableName($refTable)
552 8
            . ' (' . $this->buildColumns($refColumns) . ')';
553 8
        if ($delete !== null) {
554 4
            $sql .= ' ON DELETE ' . $delete;
555
        }
556 8
        if ($update !== null) {
557 4
            $sql .= ' ON UPDATE ' . $update;
558
        }
559
560 8
        return $sql;
561
    }
562
563
    /**
564
     * Builds a SQL statement for dropping a foreign key constraint.
565
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
566
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
567
     * @return string the SQL statement for dropping a foreign key constraint.
568
     */
569 3
    public function dropForeignKey($name, $table)
570
    {
571 3
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
572 3
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
573
    }
574
575
    /**
576
     * Builds a SQL statement for creating a new index.
577
     * @param string $name the name of the index. The name will be properly quoted by the method.
578
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
579
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
580
     * separate them with commas or use an array to represent them. Each column name will be properly quoted
581
     * by the method, unless a parenthesis is found in the name.
582
     * @param bool $unique whether to add UNIQUE constraint on the created index.
583
     * @return string the SQL statement for creating a new index.
584
     */
585 6
    public function createIndex($name, $table, $columns, $unique = false)
586
    {
587 6
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
588 6
            . $this->db->quoteTableName($name) . ' ON '
589 6
            . $this->db->quoteTableName($table)
590 6
            . ' (' . $this->buildColumns($columns) . ')';
591
    }
592
593
    /**
594
     * Builds a SQL statement for dropping an index.
595
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
596
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
597
     * @return string the SQL statement for dropping an index.
598
     */
599 4
    public function dropIndex($name, $table)
600
    {
601 4
        return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
602
    }
603
604
    /**
605
     * Creates a SQL command for adding an unique constraint to an existing table.
606
     * @param string $name the name of the unique constraint.
607
     * The name will be properly quoted by the method.
608
     * @param string $table the table that the unique constraint will be added to.
609
     * The name will be properly quoted by the method.
610
     * @param string|array $columns the name of the column to that the constraint will be added on.
611
     * If there are multiple columns, separate them with commas.
612
     * The name will be properly quoted by the method.
613
     * @return string the SQL statement for adding an unique constraint to an existing table.
614
     * @since 2.0.13
615
     */
616 6
    public function addUnique($name, $table, $columns)
617
    {
618 6
        if (is_string($columns)) {
619 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
620
        }
621 6
        foreach ($columns as $i => $col) {
622 6
            $columns[$i] = $this->db->quoteColumnName($col);
623
        }
624
625 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
626 6
            . $this->db->quoteColumnName($name) . ' UNIQUE ('
627 6
            . implode(', ', $columns) . ')';
628
    }
629
630
    /**
631
     * Creates a SQL command for dropping an unique constraint.
632
     * @param string $name the name of the unique constraint to be dropped.
633
     * The name will be properly quoted by the method.
634
     * @param string $table the table whose unique constraint is to be dropped.
635
     * The name will be properly quoted by the method.
636
     * @return string the SQL statement for dropping an unique constraint.
637
     * @since 2.0.13
638
     */
639 2
    public function dropUnique($name, $table)
640
    {
641 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
642 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
643
    }
644
645
    /**
646
     * Creates a SQL command for adding a check constraint to an existing table.
647
     * @param string $name the name of the check constraint.
648
     * The name will be properly quoted by the method.
649
     * @param string $table the table that the check constraint will be added to.
650
     * The name will be properly quoted by the method.
651
     * @param string $expression the SQL of the `CHECK` constraint.
652
     * @return string the SQL statement for adding a check constraint to an existing table.
653
     * @since 2.0.13
654
     */
655 2
    public function addCheck($name, $table, $expression)
656
    {
657 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
658 2
            . $this->db->quoteColumnName($name) . ' CHECK (' . $this->db->quoteSql($expression) . ')';
659
    }
660
661
    /**
662
     * Creates a SQL command for dropping a check constraint.
663
     * @param string $name the name of the check constraint to be dropped.
664
     * The name will be properly quoted by the method.
665
     * @param string $table the table whose check constraint is to be dropped.
666
     * The name will be properly quoted by the method.
667
     * @return string the SQL statement for dropping a check constraint.
668
     * @since 2.0.13
669
     */
670 2
    public function dropCheck($name, $table)
671
    {
672 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
673 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
674
    }
675
676
    /**
677
     * Creates a SQL command for adding a default value constraint to an existing table.
678
     * @param string $name the name of the default value constraint.
679
     * The name will be properly quoted by the method.
680
     * @param string $table the table that the default value constraint will be added to.
681
     * The name will be properly quoted by the method.
682
     * @param string $column the name of the column to that the constraint will be added on.
683
     * The name will be properly quoted by the method.
684
     * @param mixed $value default value.
685
     * @return string the SQL statement for adding a default value constraint to an existing table.
686
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
687
     * @since 2.0.13
688
     */
689
    public function addDefaultValue($name, $table, $column, $value)
690
    {
691
        throw new NotSupportedException($this->db->getDriverName() . ' does not support adding default value constraints.');
692
    }
693
694
    /**
695
     * Creates a SQL command for dropping a default value constraint.
696
     * @param string $name the name of the default value constraint to be dropped.
697
     * The name will be properly quoted by the method.
698
     * @param string $table the table whose default value constraint is to be dropped.
699
     * The name will be properly quoted by the method.
700
     * @return string the SQL statement for dropping a default value constraint.
701
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
702
     * @since 2.0.13
703
     */
704
    public function dropDefaultValue($name, $table)
705
    {
706
        throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
707
    }
708
709
    /**
710
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
711
     * The sequence will be reset such that the primary key of the next new row inserted
712
     * will have the specified value or 1.
713
     * @param string $table the name of the table whose primary key sequence will be reset
714
     * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
715
     * the next new row's primary key will have a value 1.
716
     * @return string the SQL statement for resetting sequence
717
     * @throws NotSupportedException if this is not supported by the underlying DBMS
718
     */
719
    public function resetSequence($table, $value = null)
720
    {
721
        throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
722
    }
723
724
    /**
725
     * Builds a SQL statement for enabling or disabling integrity check.
726
     * @param bool $check whether to turn on or off the integrity check.
727
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
728
     * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
729
     * @return string the SQL statement for checking integrity
730
     * @throws NotSupportedException if this is not supported by the underlying DBMS
731
     */
732
    public function checkIntegrity($check = true, $schema = '', $table = '')
733
    {
734
        throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
735
    }
736
737
    /**
738
     * Builds a SQL command for adding comment to column.
739
     *
740
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
741
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
742
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
743
     * @return string the SQL statement for adding comment on column
744
     * @since 2.0.8
745
     */
746 2
    public function addCommentOnColumn($table, $column, $comment)
747
    {
748 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment);
749
    }
750
751
    /**
752
     * Builds a SQL command for adding comment to table.
753
     *
754
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
755
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
756
     * @return string the SQL statement for adding comment on table
757
     * @since 2.0.8
758
     */
759 1
    public function addCommentOnTable($table, $comment)
760
    {
761 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment);
762
    }
763
764
    /**
765
     * Builds a SQL command for adding comment to column.
766
     *
767
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
768
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
769
     * @return string the SQL statement for adding comment on column
770
     * @since 2.0.8
771
     */
772 2
    public function dropCommentFromColumn($table, $column)
773
    {
774 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL';
775
    }
776
777
    /**
778
     * Builds a SQL command for adding comment to table.
779
     *
780
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
781
     * @return string the SQL statement for adding comment on column
782
     * @since 2.0.8
783
     */
784 1
    public function dropCommentFromTable($table)
785
    {
786 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL';
787
    }
788
789
    /**
790
     * Converts an abstract column type into a physical column type.
791
     *
792
     * The conversion is done using the type map specified in [[typeMap]].
793
     * The following abstract column types are supported (using MySQL as an example to explain the corresponding
794
     * physical types):
795
     *
796
     * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
797
     * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
798
     * - `upk`: an unsigned auto-incremental primary key type, will be converted into "int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY"
799
     * - `char`: char type, will be converted into "char(1)"
800
     * - `string`: string type, will be converted into "varchar(255)"
801
     * - `text`: a long string type, will be converted into "text"
802
     * - `smallint`: a small integer type, will be converted into "smallint(6)"
803
     * - `integer`: integer type, will be converted into "int(11)"
804
     * - `bigint`: a big integer type, will be converted into "bigint(20)"
805
     * - `boolean`: boolean type, will be converted into "tinyint(1)"
806
     * - `float``: float number type, will be converted into "float"
807
     * - `decimal`: decimal number type, will be converted into "decimal"
808
     * - `datetime`: datetime type, will be converted into "datetime"
809
     * - `timestamp`: timestamp type, will be converted into "timestamp"
810
     * - `time`: time type, will be converted into "time"
811
     * - `date`: date type, will be converted into "date"
812
     * - `money`: money type, will be converted into "decimal(19,4)"
813
     * - `binary`: binary data type, will be converted into "blob"
814
     *
815
     * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
816
     * the first part will be converted, and the rest of the parts will be appended to the converted result.
817
     * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
818
     *
819
     * For some of the abstract types you can also specify a length or precision constraint
820
     * by appending it in round brackets directly to the type.
821
     * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
822
     * If the underlying DBMS does not support these kind of constraints for a type it will
823
     * be ignored.
824
     *
825
     * If a type cannot be found in [[typeMap]], it will be returned without any change.
826
     * @param string|ColumnSchemaBuilder $type abstract column type
827
     * @return string physical column type.
828
     */
829 114
    public function getColumnType($type)
830
    {
831 114
        if ($type instanceof ColumnSchemaBuilder) {
832 13
            $type = $type->__toString();
833
        }
834
835 114
        if (isset($this->typeMap[$type])) {
836 99
            return $this->typeMap[$type];
837 55
        } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
838 31
            if (isset($this->typeMap[$matches[1]])) {
839 31
                return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
840
            }
841 32
        } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
842 29
            if (isset($this->typeMap[$matches[1]])) {
843 29
                return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
844
            }
845
        }
846
847 24
        return $type;
848
    }
849
850
    /**
851
     * @param array $columns
852
     * @param array $params the binding parameters to be populated
853
     * @param bool $distinct
854
     * @param string $selectOption
855
     * @return string the SELECT clause built from [[Query::$select]].
856
     */
857 956
    public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
858
    {
859 956
        $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
860 956
        if ($selectOption !== null) {
861
            $select .= ' ' . $selectOption;
862
        }
863
864 956
        if (empty($columns)) {
865 772
            return $select . ' *';
866
        }
867
868 442
        foreach ($columns as $i => $column) {
869 442
            if ($column instanceof Expression) {
870 6
                if (is_int($i)) {
871 6
                    $columns[$i] = $column->expression;
872
                } else {
873 3
                    $columns[$i] = $column->expression . ' AS ' . $this->db->quoteColumnName($i);
874
                }
875 6
                $params = array_merge($params, $column->params);
876 439
            } elseif ($column instanceof Query) {
877 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...
878 3
                $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
879 439
            } elseif (is_string($i)) {
880 20
                if (strpos($column, '(') === false) {
881 20
                    $column = $this->db->quoteColumnName($column);
882
                }
883 20
                $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
884 434
            } elseif (strpos($column, '(') === false) {
885 353
                if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
886 6
                    $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
887
                } else {
888 442
                    $columns[$i] = $this->db->quoteColumnName($column);
889
                }
890
            }
891
        }
892
893 442
        return $select . ' ' . implode(', ', $columns);
894
    }
895
896
    /**
897
     * @param array $tables
898
     * @param array $params the binding parameters to be populated
899
     * @return string the FROM clause built from [[Query::$from]].
900
     */
901 956
    public function buildFrom($tables, &$params)
902
    {
903 956
        if (empty($tables)) {
904 274
            return '';
905
        }
906
907 701
        $tables = $this->quoteTableNames($tables, $params);
908
909 701
        return 'FROM ' . implode(', ', $tables);
910
    }
911
912
    /**
913
     * @param array $joins
914
     * @param array $params the binding parameters to be populated
915
     * @return string the JOIN clause built from [[Query::$join]].
916
     * @throws Exception if the $joins parameter is not in proper format
917
     */
918 956
    public function buildJoin($joins, &$params)
919
    {
920 956
        if (empty($joins)) {
921 944
            return '';
922
        }
923
924 54
        foreach ($joins as $i => $join) {
925 54
            if (!is_array($join) || !isset($join[0], $join[1])) {
926
                throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
927
            }
928
            // 0:join type, 1:join table, 2:on-condition (optional)
929 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...
930 54
            $tables = $this->quoteTableNames((array) $table, $params);
931 54
            $table = reset($tables);
932 54
            $joins[$i] = "$joinType $table";
933 54
            if (isset($join[2])) {
934 54
                $condition = $this->buildCondition($join[2], $params);
935 54
                if ($condition !== '') {
936 54
                    $joins[$i] .= ' ON ' . $condition;
937
                }
938
            }
939
        }
940
941 54
        return implode($this->separator, $joins);
942
    }
943
944
    /**
945
     * Quotes table names passed.
946
     *
947
     * @param array $tables
948
     * @param array $params
949
     * @return array
950
     */
951 701
    private function quoteTableNames($tables, &$params)
952
    {
953 701
        foreach ($tables as $i => $table) {
954 701
            if ($table instanceof Query) {
955 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...
956 10
                $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
957 701
            } elseif (is_string($i)) {
958 73
                if (strpos($table, '(') === false) {
959 64
                    $table = $this->db->quoteTableName($table);
960
                }
961 73
                $tables[$i] = "$table " . $this->db->quoteTableName($i);
962 683
            } elseif (strpos($table, '(') === false) {
963 679
                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
964 21
                    $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
965
                } else {
966 701
                    $tables[$i] = $this->db->quoteTableName($table);
967
                }
968
            }
969
        }
970
971 701
        return $tables;
972
    }
973
974
    /**
975
     * @param string|array $condition
976
     * @param array $params the binding parameters to be populated
977
     * @return string the WHERE clause built from [[Query::$where]].
978
     */
979 1019
    public function buildWhere($condition, &$params)
980
    {
981 1019
        $where = $this->buildCondition($condition, $params);
982
983 1019
        return $where === '' ? '' : 'WHERE ' . $where;
984
    }
985
986
    /**
987
     * @param array $columns
988
     * @param array $params the binding parameters to be populated
989
     * @return string the GROUP BY clause
990
     */
991 956
    public function buildGroupBy($columns, &$params)
992
    {
993 956
        if (empty($columns)) {
994 950
            return '';
995
        }
996 18
        foreach ($columns as $i => $column) {
997 18
            if ($column instanceof Expression) {
998 3
                $columns[$i] = $column->expression;
999 3
                $params = array_merge($params, $column->params);
1000 18
            } elseif (strpos($column, '(') === false) {
1001 18
                $columns[$i] = $this->db->quoteColumnName($column);
1002
            }
1003
        }
1004
1005 18
        return 'GROUP BY ' . implode(', ', $columns);
1006
    }
1007
1008
    /**
1009
     * @param string|array $condition
1010
     * @param array $params the binding parameters to be populated
1011
     * @return string the HAVING clause built from [[Query::$having]].
1012
     */
1013 956
    public function buildHaving($condition, &$params)
1014
    {
1015 956
        $having = $this->buildCondition($condition, $params);
1016
1017 956
        return $having === '' ? '' : 'HAVING ' . $having;
1018
    }
1019
1020
    /**
1021
     * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL.
1022
     * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
1023
     * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
1024
     * @param int $limit the limit number. See [[Query::limit]] for more details.
1025
     * @param int $offset the offset number. See [[Query::offset]] for more details.
1026
     * @param array $params the binding parameters to be populated
1027
     * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
1028
     */
1029 956
    public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params)
1030
    {
1031 956
        $orderBy = $this->buildOrderBy($orderBy, $params);
1032 956
        if ($orderBy !== '') {
1033 155
            $sql .= $this->separator . $orderBy;
1034
        }
1035 956
        $limit = $this->buildLimit($limit, $offset);
1036 956
        if ($limit !== '') {
1037 65
            $sql .= $this->separator . $limit;
1038
        }
1039
1040 956
        return $sql;
1041
    }
1042
1043
    /**
1044
     * @param array $columns
1045
     * @param array $params the binding parameters to be populated
1046
     * @return string the ORDER BY clause built from [[Query::$orderBy]].
1047
     */
1048 956
    public function buildOrderBy($columns, &$params)
1049
    {
1050 956
        if (empty($columns)) {
1051 923
            return '';
1052
        }
1053 155
        $orders = [];
1054 155
        foreach ($columns as $name => $direction) {
1055 155
            if ($direction instanceof Expression) {
1056 3
                $orders[] = $direction->expression;
1057 3
                $params = array_merge($params, $direction->params);
1058
            } else {
1059 155
                $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
1060
            }
1061
        }
1062
1063 155
        return 'ORDER BY ' . implode(', ', $orders);
1064
    }
1065
1066
    /**
1067
     * @param int $limit
1068
     * @param int $offset
1069
     * @return string the LIMIT and OFFSET clauses
1070
     */
1071 320
    public function buildLimit($limit, $offset)
1072
    {
1073 320
        $sql = '';
1074 320
        if ($this->hasLimit($limit)) {
1075 16
            $sql = 'LIMIT ' . $limit;
1076
        }
1077 320
        if ($this->hasOffset($offset)) {
1078 3
            $sql .= ' OFFSET ' . $offset;
1079
        }
1080
1081 320
        return ltrim($sql);
1082
    }
1083
1084
    /**
1085
     * Checks to see if the given limit is effective.
1086
     * @param mixed $limit the given limit
1087
     * @return bool whether the limit is effective
1088
     */
1089 618
    protected function hasLimit($limit)
1090
    {
1091 618
        return ($limit instanceof Expression) || ctype_digit((string) $limit);
1092
    }
1093
1094
    /**
1095
     * Checks to see if the given offset is effective.
1096
     * @param mixed $offset the given offset
1097
     * @return bool whether the offset is effective
1098
     */
1099 618
    protected function hasOffset($offset)
1100
    {
1101 618
        return ($offset instanceof Expression) || ctype_digit((string) $offset) && (string) $offset !== '0';
1102
    }
1103
1104
    /**
1105
     * @param array $unions
1106
     * @param array $params the binding parameters to be populated
1107
     * @return string the UNION clause built from [[Query::$union]].
1108
     */
1109 658
    public function buildUnion($unions, &$params)
1110
    {
1111 658
        if (empty($unions)) {
1112 658
            return '';
1113
        }
1114
1115 8
        $result = '';
1116
1117 8
        foreach ($unions as $i => $union) {
1118 8
            $query = $union['query'];
1119 8
            if ($query instanceof Query) {
1120 8
                [$unions[$i]['query'], $params] = $this->build($query, $params);
1121
            }
1122
1123 8
            $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
1124
        }
1125
1126 8
        return trim($result);
1127
    }
1128
1129
    /**
1130
     * Processes columns and properly quotes them if necessary.
1131
     * It will join all columns into a string with comma as separators.
1132
     * @param string|array $columns the columns to be processed
1133
     * @return string the processing result
1134
     */
1135 32
    public function buildColumns($columns)
1136
    {
1137 32
        if (!is_array($columns)) {
1138 27
            if (strpos($columns, '(') !== false) {
1139
                return $columns;
1140
            }
1141
1142 27
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
1143
        }
1144 32
        foreach ($columns as $i => $column) {
1145 32
            if ($column instanceof Expression) {
1146
                $columns[$i] = $column->expression;
1147 32
            } elseif (strpos($column, '(') === false) {
1148 32
                $columns[$i] = $this->db->quoteColumnName($column);
1149
            }
1150
        }
1151
1152 32
        return is_array($columns) ? implode(', ', $columns) : $columns;
1153
    }
1154
1155
    /**
1156
     * Parses the condition specification and generates the corresponding SQL expression.
1157
     * @param string|array|Expression $condition the condition specification. Please refer to [[Query::where()]]
1158
     * on how to specify a condition.
1159
     * @param array $params the binding parameters to be populated
1160
     * @return string the generated SQL expression
1161
     */
1162 1019
    public function buildCondition($condition, &$params)
1163
    {
1164 1019
        if ($condition instanceof Expression) {
1165 9
            foreach ($condition->params as $n => $v) {
1166 9
                $params[$n] = $v;
1167
            }
1168
1169 9
            return $condition->expression;
1170 1019
        } elseif (!is_array($condition)) {
1171 1013
            return (string) $condition;
1172
        } elseif (empty($condition)) {
1173
            return '';
1174
        }
1175
1176 794
        if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
1177 479
            $operator = strtoupper($condition[0]);
1178 479
            if (isset($this->conditionBuilders[$operator])) {
1179 449
                $method = $this->conditionBuilders[$operator];
1180
            } else {
1181 36
                $method = 'buildSimpleCondition';
1182
            }
1183 479
            array_shift($condition);
1184 479
            return $this->$method($operator, $condition, $params);
1185
        }
1186
1187
        // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
1188 568
        return $this->buildHashCondition($condition, $params);
1189
    }
1190
1191
    /**
1192
     * Creates a condition based on column-value pairs.
1193
     * @param array $condition the condition specification.
1194
     * @param array $params the binding parameters to be populated
1195
     * @return string the generated SQL expression
1196
     */
1197 568
    public function buildHashCondition($condition, &$params)
1198
    {
1199 568
        $parts = [];
1200 568
        foreach ($condition as $column => $value) {
1201 568
            if (ArrayHelper::isTraversable($value) || $value instanceof Query) {
1202
                // IN condition
1203 72
                $parts[] = $this->buildInCondition('IN', [$column, $value], $params);
1204
            } else {
1205 553
                if (strpos($column, '(') === false) {
1206 547
                    $column = $this->db->quoteColumnName($column);
1207
                }
1208 553
                if ($value === null) {
1209 12
                    $parts[] = "$column IS NULL";
1210 553
                } elseif ($value instanceof Expression) {
1211 122
                    $parts[] = "$column=" . $value->expression;
1212 122
                    foreach ($value->params as $n => $v) {
1213 122
                        $params[$n] = $v;
1214
                    }
1215
                } else {
1216 553
                    $phName = self::PARAM_PREFIX . count($params);
1217 553
                    $parts[] = "$column=$phName";
1218 568
                    $params[$phName] = $value;
1219
                }
1220
            }
1221
        }
1222
1223 568
        return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
1224
    }
1225
1226
    /**
1227
     * Connects two or more SQL expressions with the `AND` or `OR` operator.
1228
     * @param string $operator the operator to use for connecting the given operands
1229
     * @param array $operands the SQL expressions to connect.
1230
     * @param array $params the binding parameters to be populated
1231
     * @return string the generated SQL expression
1232
     */
1233 198
    public function buildAndCondition($operator, $operands, &$params)
1234
    {
1235 198
        $parts = [];
1236 198
        foreach ($operands as $operand) {
1237 198
            if (is_array($operand)) {
1238 173
                $operand = $this->buildCondition($operand, $params);
1239
            }
1240 198
            if ($operand instanceof Expression) {
1241 6
                foreach ($operand->params as $n => $v) {
1242 6
                    $params[$n] = $v;
1243
                }
1244 6
                $operand = $operand->expression;
1245
            }
1246 198
            if ($operand !== '') {
1247 198
                $parts[] = $operand;
1248
            }
1249
        }
1250 198
        if (!empty($parts)) {
1251 198
            return '(' . implode(") $operator (", $parts) . ')';
1252
        }
1253
1254
        return '';
1255
    }
1256
1257
    /**
1258
     * Inverts an SQL expressions with `NOT` operator.
1259
     * @param string $operator the operator to use for connecting the given operands
1260
     * @param array $operands the SQL expressions to connect.
1261
     * @param array $params the binding parameters to be populated
1262
     * @return string the generated SQL expression
1263
     * @throws InvalidArgumentException if wrong number of operands have been given.
1264
     */
1265 6
    public function buildNotCondition($operator, $operands, &$params)
1266
    {
1267 6
        if (count($operands) !== 1) {
1268
            throw new InvalidArgumentException("Operator '$operator' requires exactly one operand.");
1269
        }
1270
1271 6
        $operand = reset($operands);
1272 6
        if (is_array($operand) || $operand instanceof Expression) {
1273 3
            $operand = $this->buildCondition($operand, $params);
1274
        }
1275 6
        if ($operand === '') {
1276
            return '';
1277
        }
1278
1279 6
        return "$operator ($operand)";
1280
    }
1281
1282
    /**
1283
     * Creates an SQL expressions with the `BETWEEN` operator.
1284
     * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
1285
     * @param array $operands the first operand is the column name. The second and third operands
1286
     * describe the interval that column value should be in.
1287
     * @param array $params the binding parameters to be populated
1288
     * @return string the generated SQL expression
1289
     * @throws InvalidArgumentException if wrong number of operands have been given.
1290
     */
1291 21
    public function buildBetweenCondition($operator, $operands, &$params)
1292
    {
1293 21
        if (!isset($operands[0], $operands[1], $operands[2])) {
1294
            throw new InvalidArgumentException("Operator '$operator' requires three operands.");
1295
        }
1296
1297 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...
1298
1299 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...
1300 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...
1301
        }
1302 21
        if ($value1 instanceof Expression) {
1303 12
            foreach ($value1->params as $n => $v) {
1304
                $params[$n] = $v;
1305
            }
1306 12
            $phName1 = $value1->expression;
1307
        } else {
1308 9
            $phName1 = self::PARAM_PREFIX . count($params);
1309 9
            $params[$phName1] = $value1;
1310
        }
1311 21
        if ($value2 instanceof Expression) {
1312 6
            foreach ($value2->params as $n => $v) {
1313
                $params[$n] = $v;
1314
            }
1315 6
            $phName2 = $value2->expression;
1316
        } else {
1317 15
            $phName2 = self::PARAM_PREFIX . count($params);
1318 15
            $params[$phName2] = $value2;
1319
        }
1320
1321 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...
1322
    }
1323
1324
    /**
1325
     * Creates an SQL expressions with the `IN` operator.
1326
     * @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
1327
     * @param array $operands the first operand is the column name. If it is an array
1328
     * a composite IN condition will be generated.
1329
     * The second operand is an array of values that column value should be among.
1330
     * If it is an empty array the generated expression will be a `false` value if
1331
     * operator is `IN` and empty if operator is `NOT IN`.
1332
     * @param array $params the binding parameters to be populated
1333
     * @return string the generated SQL expression
1334
     * @throws Exception if wrong number of operands have been given.
1335
     */
1336 231
    public function buildInCondition($operator, $operands, &$params)
1337
    {
1338 231
        if (!isset($operands[0], $operands[1])) {
1339
            throw new Exception("Operator '$operator' requires two operands.");
1340
        }
1341
1342 231
        [$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...
1343
1344 231
        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...
1345
            // no columns to test against
1346
            return $operator === 'IN' ? '0=1' : '';
1347
        }
1348
1349 231
        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...
1350 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...
1351
        }
1352 217
        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...
1353
            // ensure values is an array
1354 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...
1355
        }
1356
1357 217
        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...
1358 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...
1359 205
        } 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...
1360 136
            $column = reset($column);
1361
        }
1362
1363 205
        $sqlValues = [];
1364 205
        foreach ($values as $i => $value) {
1365 205
            if (is_array($value) || $value instanceof \ArrayAccess) {
1366
                $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...
1367
            }
1368 205
            if ($value === null) {
1369
                $sqlValues[$i] = 'NULL';
1370 205
            } elseif ($value instanceof Expression) {
1371
                $sqlValues[$i] = $value->expression;
1372
                foreach ($value->params as $n => $v) {
1373
                    $params[$n] = $v;
1374
                }
1375
            } else {
1376 205
                $phName = self::PARAM_PREFIX . count($params);
1377 205
                $params[$phName] = $value;
1378 205
                $sqlValues[$i] = $phName;
1379
            }
1380
        }
1381
1382 205
        if (empty($sqlValues)) {
1383 18
            return $operator === 'IN' ? '0=1' : '';
1384
        }
1385
1386 205
        if (strpos($column, '(') === false) {
1387 205
            $column = $this->db->quoteColumnName($column);
1388
        }
1389
1390 205
        if (count($sqlValues) > 1) {
1391 143
            return "$column $operator (" . implode(', ', $sqlValues) . ')';
1392
        }
1393
1394 138
        $operator = $operator === 'IN' ? '=' : '<>';
1395 138
        return $column . $operator . reset($sqlValues);
1396
    }
1397
1398
    /**
1399
     * Builds SQL for IN condition.
1400
     *
1401
     * @param string $operator
1402
     * @param array $columns
1403
     * @param Query $values
1404
     * @param array $params
1405
     * @return string SQL
1406
     */
1407 14
    protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
1408
    {
1409 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...
1410 14
        if (is_array($columns)) {
1411 4
            foreach ($columns as $i => $col) {
1412 4
                if (strpos($col, '(') === false) {
1413 4
                    $columns[$i] = $this->db->quoteColumnName($col);
1414
                }
1415
            }
1416
1417 4
            return '(' . implode(', ', $columns) . ") $operator ($sql)";
1418
        }
1419
1420 10
        if (strpos($columns, '(') === false) {
1421 10
            $columns = $this->db->quoteColumnName($columns);
1422
        }
1423
1424 10
        return "$columns $operator ($sql)";
1425
    }
1426
1427
    /**
1428
     * Builds SQL for IN condition.
1429
     *
1430
     * @param string $operator
1431
     * @param array|\Traversable $columns
1432
     * @param array $values
1433
     * @param array $params
1434
     * @return string SQL
1435
     */
1436 10
    protected function buildCompositeInCondition($operator, $columns, $values, &$params)
1437
    {
1438 10
        $vss = [];
1439 10
        foreach ($values as $value) {
1440 10
            $vs = [];
1441 10
            foreach ($columns as $column) {
1442 10
                if (isset($value[$column])) {
1443 10
                    $phName = self::PARAM_PREFIX . count($params);
1444 10
                    $params[$phName] = $value[$column];
1445 10
                    $vs[] = $phName;
1446
                } else {
1447 10
                    $vs[] = 'NULL';
1448
                }
1449
            }
1450 10
            $vss[] = '(' . implode(', ', $vs) . ')';
1451
        }
1452
1453 10
        if (empty($vss)) {
1454
            return $operator === 'IN' ? '0=1' : '';
1455
        }
1456
1457 10
        $sqlColumns = [];
1458 10
        foreach ($columns as $i => $column) {
1459 10
            $sqlColumns[] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
1460
        }
1461
1462 10
        return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')';
1463
    }
1464
1465
    /**
1466
     * Creates an SQL expressions with the `LIKE` operator.
1467
     * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
1468
     * @param array $operands an array of two or three operands
1469
     *
1470
     * - The first operand is the column name.
1471
     * - The second operand is a single value or an array of values that column value
1472
     *   should be compared with. If it is an empty array the generated expression will
1473
     *   be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
1474
     *   is `NOT LIKE` or `OR NOT LIKE`.
1475
     * - An optional third operand can also be provided to specify how to escape special characters
1476
     *   in the value(s). The operand should be an array of mappings from the special characters to their
1477
     *   escaped counterparts. If this operand is not provided, a default escape mapping will be used.
1478
     *   You may use `false` or an empty array to indicate the values are already escaped and no escape
1479
     *   should be applied. Note that when using an escape mapping (or the third operand is not provided),
1480
     *   the values will be automatically enclosed within a pair of percentage characters.
1481
     * @param array $params the binding parameters to be populated
1482
     * @return string the generated SQL expression
1483
     * @throws InvalidArgumentException if wrong number of operands have been given.
1484
     */
1485 75
    public function buildLikeCondition($operator, $operands, &$params)
1486
    {
1487 75
        if (!isset($operands[0], $operands[1])) {
1488
            throw new InvalidArgumentException("Operator '$operator' requires two operands.");
1489
        }
1490
1491 75
        $escape = isset($operands[2]) ? $operands[2] : $this->likeEscapingReplacements;
1492 75
        unset($operands[2]);
1493
1494 75
        if (!preg_match('/^(AND |OR |)(((NOT |))I?LIKE)/', $operator, $matches)) {
1495
            throw new InvalidArgumentException("Invalid operator '$operator'.");
1496
        }
1497 75
        $andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
1498 75
        $not = !empty($matches[3]);
1499 75
        $operator = $matches[2];
1500
1501 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...
1502
1503 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...
1504 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...
1505
        }
1506
1507 75
        if (empty($values)) {
1508 16
            return $not ? '' : '0=1';
1509
        }
1510
1511 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...
1512 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...
1513
        }
1514
1515 59
        $parts = [];
1516 59
        foreach ($values as $value) {
1517 59
            if ($value instanceof Expression) {
1518 24
                foreach ($value->params as $n => $v) {
1519
                    $params[$n] = $v;
1520
                }
1521 24
                $phName = $value->expression;
1522
            } else {
1523 47
                $phName = self::PARAM_PREFIX . count($params);
1524 47
                $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
1525
            }
1526 59
            $escapeSql = '';
1527 59
            if ($this->likeEscapeCharacter !== null) {
1528 17
                $escapeSql = " ESCAPE '{$this->likeEscapeCharacter}'";
1529
            }
1530 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...
1531
        }
1532
1533 59
        return implode($andor, $parts);
1534
    }
1535
1536
    /**
1537
     * Creates an SQL expressions with the `EXISTS` operator.
1538
     * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
1539
     * @param array $operands contains only one element which is a [[Query]] object representing the sub-query.
1540
     * @param array $params the binding parameters to be populated
1541
     * @return string the generated SQL expression
1542
     * @throws InvalidArgumentException if the operand is not a [[Query]] object.
1543
     */
1544 18
    public function buildExistsCondition($operator, $operands, &$params)
1545
    {
1546 18
        if ($operands[0] instanceof Query) {
1547 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...
1548 18
            return "$operator ($sql)";
1549
        }
1550
1551
        throw new InvalidArgumentException('Subquery for EXISTS operator must be a Query object.');
1552
    }
1553
1554
    /**
1555
     * Creates an SQL expressions like `"column" operator value`.
1556
     * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
1557
     * @param array $operands contains two column names.
1558
     * @param array $params the binding parameters to be populated
1559
     * @return string the generated SQL expression
1560
     * @throws InvalidArgumentException if wrong number of operands have been given.
1561
     */
1562 36
    public function buildSimpleCondition($operator, $operands, &$params)
1563
    {
1564 36
        if (count($operands) !== 2) {
1565
            throw new InvalidArgumentException("Operator '$operator' requires two operands.");
1566
        }
1567
1568 36
        [$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...
1569
1570 36
        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...
1571 36
            $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...
1572
        }
1573
1574 36
        if ($value === null) {
1575
            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...
1576 36
        } elseif ($value instanceof Expression) {
1577 6
            foreach ($value->params as $n => $v) {
1578 3
                $params[$n] = $v;
1579
            }
1580
1581 6
            return "$column $operator {$value->expression}";
1582 30
        } elseif ($value instanceof Query) {
1583 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...
1584 3
            return "$column $operator ($sql)";
1585
        }
1586
1587 27
        $phName = self::PARAM_PREFIX . count($params);
1588 27
        $params[$phName] = $value;
1589 27
        return "$column $operator $phName";
1590
    }
1591
1592
    /**
1593
     * Creates a SELECT EXISTS() SQL statement.
1594
     * @param string $rawSql the subquery in a raw form to select from.
1595
     * @return string the SELECT EXISTS() SQL statement.
1596
     * @since 2.0.8
1597
     */
1598 60
    public function selectExists($rawSql)
1599
    {
1600 60
        return 'SELECT EXISTS(' . $rawSql . ')';
1601
    }
1602
}
1603