Completed
Push — 2.1 ( b0b06b...3e2500 )
by Alexander
13:41
created

QueryBuilder::update()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 4
crap 2
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\db\conditions\ConditionInterface;
13
use yii\db\conditions\HashCondition;
14
use yii\helpers\StringHelper;
15
16
/**
17
 * QueryBuilder builds a SELECT SQL statement based on the specification given as a [[Query]] object.
18
 *
19
 * SQL statements are created from [[Query]] objects using the [[build()]]-method.
20
 *
21
 * QueryBuilder is also used by [[Command]] to build SQL statements such as INSERT, UPDATE, DELETE, CREATE TABLE.
22
 *
23
 * For more details and usage information on QueryBuilder, see the [guide article on query builders](guide:db-query-builder).
24
 *
25
 * @property string[] $expressionBuilders Array of builders that should be merged with the pre-defined ones in
26
 * [[expressionBuilders]] property. This property is write-only.
27
 *
28
 * @author Qiang Xue <[email protected]>
29
 * @since 2.0
30
 */
31
class QueryBuilder extends \yii\base\BaseObject
32
{
33
    /**
34
     * The prefix for automatically generated query binding parameters.
35
     */
36
    const PARAM_PREFIX = ':qp';
37
38
    /**
39
     * @var Connection the database connection.
40
     */
41
    public $db;
42
    /**
43
     * @var string the separator between different fragments of a SQL statement.
44
     * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
45
     */
46
    public $separator = ' ';
47
    /**
48
     * @var array the abstract column types mapped to physical column types.
49
     * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
50
     * Child classes should override this property to declare supported type mappings.
51
     */
52
    public $typeMap = [];
53
54
    /**
55
     * @var array map of condition aliases to condition classes. For example:
56
     *
57
     * ```php
58
     * return [
59
     *     'LIKE' => yii\db\condition\LikeCondition::class,
60
     * ];
61
     * ```
62
     *
63
     * This property is used by [[createConditionFromArray]] method.
64
     * See default condition classes list in [[defaultConditionClasses()]] method.
65
     *
66
     * In case you want to add custom conditions support, use the [[setConditionClasses()]] method.
67
     *
68
     * @see setConditonClasses()
69
     * @see defaultConditionClasses()
70
     * @since 2.0.14
71
     */
72
    protected $conditionClasses = [];
73
    /**
74
     * @var string[]|ExpressionBuilderInterface[] maps expression class to expression builder class.
75
     * For example:
76
     *
77
     * ```php
78
     * [
79
     *    yii\db\Expression::class => yii\db\ExpressionBuilder::class
80
     * ]
81
     * ```
82
     * This property is mainly used by [[buildExpression()]] to build SQL expressions form expression objects.
83
     * See default values in [[defaultExpressionBuilders()]] method.
84
     *
85
     *
86
     * To override existing builders or add custom, use [[setExpressionBuilder()]] method. New items will be added
87
     * to the end of this array.
88
     *
89
     * To find a builder, [[buildExpression()]] will check the expression class for its exact presence in this map.
90
     * In case it is NOT present, the array will be iterated in reverse direction, checking whether the expression
91
     * extends the class, defined in this map.
92
     *
93
     * @see setExpressionBuilders()
94
     * @see defaultExpressionBuilders()
95
     * @since 2.0.14
96
     */
97
    protected $expressionBuilders = [];
98
99
100
    /**
101
     * Constructor.
102
     * @param Connection $connection the database connection.
103
     * @param array $config name-value pairs that will be used to initialize the object properties
104
     */
105 1438
    public function __construct($connection, $config = [])
106
    {
107 1438
        $this->db = $connection;
108 1438
        parent::__construct($config);
109 1438
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 1438
    public function init()
115
    {
116 1438
        parent::init();
117
118 1438
        $this->expressionBuilders = array_merge($this->defaultExpressionBuilders(), $this->expressionBuilders);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->defau...is->expressionBuilders) of type array is incompatible with the declared type array<integer,string|obj...ssionBuilderInterface>> of property $expressionBuilders.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
119 1438
        $this->conditionClasses = array_merge($this->defaultConditionClasses(), $this->conditionClasses);
120 1438
    }
121
122
    /**
123
     * Contains array of default condition classes. Extend this method, if you want to change
124
     * default condition classes for the query builder. See [[conditionClasses]] docs for details.
125
     *
126
     * @return array
127
     * @see conditionClasses
128
     * @since 2.0.14
129
     */
130 1438
    protected function defaultConditionClasses()
131
    {
132
        return [
133 1438
            'NOT' => conditions\NotCondition::class,
134
            'AND' => conditions\AndCondition::class,
135
            'OR' => conditions\OrCondition::class,
136
            'BETWEEN' => conditions\BetweenCondition::class,
137
            'NOT BETWEEN' => conditions\BetweenCondition::class,
138
            'IN' => conditions\InCondition::class,
139
            'NOT IN' => conditions\InCondition::class,
140
            'LIKE' => conditions\LikeCondition::class,
141
            'NOT LIKE' => conditions\LikeCondition::class,
142
            'OR LIKE' => conditions\LikeCondition::class,
143
            'OR NOT LIKE' => conditions\LikeCondition::class,
144
            'EXISTS' => conditions\ExistsCondition::class,
145
            'NOT EXISTS' => conditions\ExistsCondition::class,
146
        ];
147
    }
148
149
    /**
150
     * Contains array of default expression builders. Extend this method and override it, if you want to change
151
     * default expression builders for this query builder. See [[expressionBuilders]] docs for details.
152
     *
153
     * @return array
154
     * @see $expressionBuilders
155
     * @since 2.0.14
156
     */
157 1438
    protected function defaultExpressionBuilders()
158
    {
159
        return [
160 1438
            Query::class => QueryExpressionBuilder::class,
161
            PdoValue::class => PdoValueBuilder::class,
162
            Expression::class => ExpressionBuilder::class,
163
            conditions\ConjunctionCondition::class => conditions\ConjunctionConditionBuilder::class,
164
            conditions\NotCondition::class => conditions\NotConditionBuilder::class,
165
            conditions\AndCondition::class => conditions\ConjunctionConditionBuilder::class,
166
            conditions\OrCondition::class => conditions\ConjunctionConditionBuilder::class,
167
            conditions\BetweenCondition::class => conditions\BetweenConditionBuilder::class,
168
            conditions\InCondition::class => conditions\InConditionBuilder::class,
169
            conditions\LikeCondition::class => conditions\LikeConditionBuilder::class,
170
            conditions\ExistsCondition::class => conditions\ExistsConditionBuilder::class,
171
            conditions\SimpleCondition::class => conditions\SimpleConditionBuilder::class,
172
            conditions\HashCondition::class => conditions\HashConditionBuilder::class,
173
            conditions\BetweenColumnsCondition::class => conditions\BetweenColumnsConditionBuilder::class,
174
        ];
175
    }
176
177
    /**
178
     * Setter for [[expressionBuilders]] property.
179
     *
180
     * @param string[] $builders array of builders that should be merged with the pre-defined ones
181
     * in [[expressionBuilders]] property.
182
     * @since 2.0.14
183
     * @see expressionBuilders
184
     */
185
    public function setExpressionBuilders($builders)
186
    {
187
        $this->expressionBuilders = array_merge($this->expressionBuilders, $builders);
188
    }
189
190
    /**
191
     * Setter for [[conditionClasses]] property.
192
     *
193
     * @param string[] $classes map of condition aliases to condition classes. For example:
194
     *
195
     * ```php
196
     * ['LIKE' => yii\db\condition\LikeCondition::class]
197
     * ```
198
     *
199
     * @since 2.0.14.2
200
     * @see conditionClasses
201
     */
202
    public function setConditionClasses($classes)
203
    {
204
        $this->conditionClasses = array_merge($this->conditionClasses, $classes);
205
    }
206
207
    /**
208
     * Generates a SELECT SQL statement from a [[Query]] object.
209
     *
210
     * @param Query $query the [[Query]] object from which the SQL statement will be generated.
211
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
212
     * be included in the result with the additional parameters generated during the query building process.
213
     * @return array the generated SQL statement (the first array element) and the corresponding
214
     * parameters to be bound to the SQL statement (the second array element). The parameters returned
215
     * include those provided in `$params`.
216
     */
217 819
    public function build($query, $params = [])
218
    {
219 819
        $query = $query->prepare($this);
220
221 819
        $params = empty($params) ? $query->params : array_merge($params, $query->params);
222
223
        $clauses = [
224 819
            $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption),
225 819
            $this->buildFrom($query->from, $params),
226 819
            $this->buildJoin($query->join, $params),
227 819
            $this->buildWhere($query->where, $params),
228 819
            $this->buildGroupBy($query->groupBy, $params),
229 819
            $this->buildHaving($query->having, $params),
230
        ];
231
232 819
        $sql = implode($this->separator, array_filter($clauses));
233 819
        $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset, $params);
234
235 819
        $union = $this->buildUnion($query->union, $params);
236 819
        if ($union !== '') {
237 8
            $sql = "($sql){$this->separator}$union";
238
        }
239
240 819
        return [$sql, $params];
241
    }
242
243
    /**
244
     * Builds given $expression
245
     *
246
     * @param ExpressionInterface $expression the expression to be built
247
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
248
     * be included in the result with the additional parameters generated during the expression building process.
249
     * @return string the SQL statement that will not be neither quoted nor encoded before passing to DBMS
250
     * @see ExpressionInterface
251
     * @see ExpressionBuilderInterface
252
     * @see expressionBuilders
253
     * @since 2.0.14
254
     * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
255
     */
256 1066
    public function buildExpression(ExpressionInterface $expression, &$params = [])
257
    {
258 1066
        $builder = $this->getExpressionBuilder($expression);
259
260 1066
        return $builder->build($expression, $params);
261
    }
262
263
    /**
264
     * Gets object of [[ExpressionBuilderInterface]] that is suitable for $expression.
265
     * Uses [[expressionBuilders]] array to find a suitable builder class.
266
     *
267
     * @param ExpressionInterface $expression
268
     * @return ExpressionBuilderInterface
269
     * @see expressionBuilders
270
     * @since 2.0.14
271
     * @throws InvalidArgumentException when $expression building is not supported by this QueryBuilder.
272
     */
273 1066
    public function getExpressionBuilder(ExpressionInterface $expression)
274
    {
275 1066
        $className = get_class($expression);
276
277 1066
        if (!isset($this->expressionBuilders[$className])) {
278
            foreach (array_reverse($this->expressionBuilders) as $expressionClass => $builderClass) {
279
                if (is_subclass_of($expression, $expressionClass)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $expressionClass can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
280
                    $this->expressionBuilders[$className] = $builderClass;
281
                    break;
282
                }
283
            }
284
285
            if (!isset($this->expressionBuilders[$className])) {
286
                throw new InvalidArgumentException('Expression of class ' . $className . ' can not be built in ' . get_class($this));
287
            }
288
        }
289
290 1066
        if ($this->expressionBuilders[$className] === __CLASS__) {
291
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (yii\db\QueryBuilder) is incompatible with the return type documented by yii\db\QueryBuilder::getExpressionBuilder of type yii\db\ExpressionBuilderInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
292
        }
293
294 1066
        if (!is_object($this->expressionBuilders[$className])) {
295 1022
            $this->expressionBuilders[$className] = new $this->expressionBuilders[$className]($this);
296
        }
297
298 1066
        return $this->expressionBuilders[$className];
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->expressionBuilders[$className]; of type string|object adds the type string to the return on line 298 which is incompatible with the return type documented by yii\db\QueryBuilder::getExpressionBuilder of type yii\db\ExpressionBuilderInterface.
Loading history...
299
    }
300
301
    /**
302
     * Creates an INSERT SQL statement.
303
     * For example,
304
     *
305
     * ```php
306
     * $sql = $queryBuilder->insert('user', [
307
     *     'name' => 'Sam',
308
     *     'age' => 30,
309
     * ], $params);
310
     * ```
311
     *
312
     * The method will properly escape the table and column names.
313
     *
314
     * @param string $table the table that new rows will be inserted into.
315
     * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
316
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
317
     * Passing of [[yii\db\Query|Query]] is available since version 2.0.11.
318
     * @param array $params the binding parameters that will be generated by this method.
319
     * They should be bound to the DB command later.
320
     * @return string the INSERT SQL
321
     */
322 534
    public function insert($table, $columns, &$params)
323
    {
324 534
        [$names, $placeholders, $values, $params] = $this->prepareInsertValues($table, $columns, $params);
0 ignored issues
show
Bug introduced by
The variable $names 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 $placeholders 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 $values 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...
325 525
        return 'INSERT INTO ' . $this->db->quoteTableName($table)
326 525
            . (!empty($names) ? ' (' . implode(', ', $names) . ')' : '')
327 525
            . (!empty($placeholders) ? ' VALUES (' . implode(', ', $placeholders) . ')' : $values);
328
    }
329
330
    /**
331
     * Prepares a `VALUES` part for an `INSERT` SQL statement.
332
     *
333
     * @param string $table the table that new rows will be inserted into.
334
     * @param array|Query $columns the column data (name => value) to be inserted into the table or instance
335
     * of [[yii\db\Query|Query]] to perform INSERT INTO ... SELECT SQL statement.
336
     * @param array $params the binding parameters that will be generated by this method.
337
     * They should be bound to the DB command later.
338
     * @return array array of column names, placeholders, values and params.
339
     * @since 2.0.14
340
     */
341 548
    protected function prepareInsertValues($table, $columns, $params = [])
342
    {
343 548
        $schema = $this->db->getSchema();
344 548
        $tableSchema = $schema->getTableSchema($table);
345 548
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
346 548
        $names = [];
347 548
        $placeholders = [];
348 548
        $values = ' DEFAULT VALUES';
349 548
        if ($columns instanceof Query) {
350 42
            [$names, $values, $params] = $this->prepareInsertSelectSubQuery($columns, $schema, $params);
351
        } else {
352 512
            foreach ($columns as $name => $value) {
353 508
                $names[] = $schema->quoteColumnName($name);
354 508
                $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
355
356 508
                if ($value instanceof ExpressionInterface) {
357 118
                    $placeholders[] = $this->buildExpression($value, $params);
358 501
                } elseif ($value instanceof \yii\db\Query) {
359
                    [$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...
360
                    $placeholders[] = "($sql)";
361
                } else {
362 508
                    $placeholders[] = $this->bindParam($value, $params);
363
                }
364
            }
365
        }
366 539
        return [$names, $placeholders, $values, $params];
367
    }
368
369
    /**
370
     * Prepare select-subquery and field names for INSERT INTO ... SELECT SQL statement.
371
     *
372
     * @param Query $columns Object, which represents select query.
373
     * @param \yii\db\Schema $schema Schema object to quote column name.
374
     * @param array $params the parameters to be bound to the generated SQL statement. These parameters will
375
     * be included in the result with the additional parameters generated during the query building process.
376
     * @return array array of column names, values and params.
377
     * @throws InvalidArgumentException if query's select does not contain named parameters only.
378
     * @since 2.0.11
379
     */
380 42
    protected function prepareInsertSelectSubQuery($columns, $schema, $params = [])
381
    {
382 42
        if (!is_array($columns->select) || empty($columns->select) || in_array('*', $columns->select)) {
383 9
            throw new InvalidArgumentException('Expected select query object with enumerated (named) parameters');
384
        }
385
386 33
        [$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...
387 33
        $names = [];
388 33
        $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...
389 33
        foreach ($columns->select as $title => $field) {
390 33
            if (is_string($title)) {
391 27
                $names[] = $schema->quoteColumnName($title);
392 24
            } elseif (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $field, $matches)) {
393 3
                $names[] = $schema->quoteColumnName($matches[2]);
394
            } else {
395 33
                $names[] = $schema->quoteColumnName($field);
396
            }
397
        }
398
399 33
        return [$names, $values, $params];
400
    }
401
402
    /**
403
     * Generates a batch INSERT SQL statement.
404
     *
405
     * For example,
406
     *
407
     * ```php
408
     * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [
409
     *     ['Tom', 30],
410
     *     ['Jane', 20],
411
     *     ['Linda', 25],
412
     * ]);
413
     * ```
414
     *
415
     * Note that the values in each row must match the corresponding column names.
416
     *
417
     * The method will properly escape the column names, and quote the values to be inserted.
418
     *
419
     * @param string $table the table that new rows will be inserted into.
420
     * @param array $columns the column names
421
     * @param array|\Generator $rows the rows to be batch inserted into the table
422
     * @param array $params the binding parameters. This parameter exists since 2.0.14
423
     * @return string the batch INSERT SQL statement
424
     */
425 27
    public function batchInsert($table, $columns, $rows, &$params = [])
426
    {
427 27
        if (empty($rows)) {
428 2
            return '';
429
        }
430
431 26
        $schema = $this->db->getSchema();
432 26
        if (($tableSchema = $schema->getTableSchema($table)) !== null) {
433 20
            $columnSchemas = $tableSchema->columns;
434
        } else {
435 6
            $columnSchemas = [];
436
        }
437
438 26
        $values = [];
439 26
        foreach ($rows as $row) {
440 24
            $vs = [];
441 24
            foreach ($row as $i => $value) {
442 24
                if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
443 15
                    $value = $columnSchemas[$columns[$i]]->dbTypecast($value);
444
                }
445 24
                if (is_string($value)) {
446 16
                    $value = $schema->quoteValue($value);
447 15
                } elseif (is_float($value)) {
448
                    // ensure type cast always has . as decimal separator in all locales
449 2
                    $value = StringHelper::floatToString($value);
450 15
                } elseif ($value === false) {
451 4
                    $value = 0;
452 15
                } elseif ($value === null) {
453 8
                    $value = 'NULL';
454 10
                } elseif ($value instanceof ExpressionInterface) {
455 6
                    $value = $this->buildExpression($value, $params);
456
                }
457 24
                $vs[] = $value;
458
            }
459 24
            $values[] = '(' . implode(', ', $vs) . ')';
460
        }
461 26
        if (empty($values)) {
462 2
            return '';
463
        }
464
465 24
        foreach ($columns as $i => $name) {
466 22
            $columns[$i] = $schema->quoteColumnName($name);
467
        }
468
469 24
        return 'INSERT INTO ' . $schema->quoteTableName($table)
470 24
        . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
471
    }
472
473
    /**
474
     * Creates an SQL statement to insert rows into a database table if
475
     * they do not already exist (matching unique constraints),
476
     * or update them if they do.
477
     *
478
     * For example,
479
     *
480
     * ```php
481
     * $sql = $queryBuilder->upsert('pages', [
482
     *     'name' => 'Front page',
483
     *     'url' => 'http://example.com/', // url is unique
484
     *     'visits' => 0,
485
     * ], [
486
     *     'visits' => new \yii\db\Expression('visits + 1'),
487
     * ], $params);
488
     * ```
489
     *
490
     * The method will properly escape the table and column names.
491
     *
492
     * @param string $table the table that new rows will be inserted into/updated in.
493
     * @param array|Query $insertColumns the column data (name => value) to be inserted into the table or instance
494
     * of [[Query]] to perform `INSERT INTO ... SELECT` SQL statement.
495
     * @param array|bool $updateColumns the column data (name => value) to be updated if they already exist.
496
     * If `true` is passed, the column data will be updated to match the insert column data.
497
     * If `false` is passed, no update will be performed if the column data already exists.
498
     * @param array $params the binding parameters that will be generated by this method.
499
     * They should be bound to the DB command later.
500
     * @return string the resulting SQL.
501
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
502
     * @since 2.0.14
503
     */
504
    public function upsert($table, $insertColumns, $updateColumns, &$params)
505
    {
506
        throw new NotSupportedException($this->db->getDriverName() . ' does not support upsert statements.');
507
    }
508
509
    /**
510
     * @param string $table
511
     * @param array|Query $insertColumns
512
     * @param array|bool $updateColumns
513
     * @param Constraint[] $constraints this parameter recieves a matched constraint list.
514
     * The constraints will be unique by their column names.
515
     * @return array
516
     * @since 2.0.14
517
     */
518 66
    protected function prepareUpsertColumns($table, $insertColumns, $updateColumns, &$constraints = [])
519
    {
520 66
        if ($insertColumns instanceof Query) {
521 24
            [$insertNames] = $this->prepareInsertSelectSubQuery($insertColumns, $this->db->getSchema());
0 ignored issues
show
Bug introduced by
The variable $insertNames 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...
522
        } else {
523 42
            $insertNames = array_map([$this->db, 'quoteColumnName'], array_keys($insertColumns));
524
        }
525 66
        $uniqueNames = $this->getTableUniqueColumnNames($table, $insertNames, $constraints);
0 ignored issues
show
Bug introduced by
The variable $insertNames 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...
526 66
        $uniqueNames = array_map([$this->db, 'quoteColumnName'], $uniqueNames);
527 66
        if ($updateColumns !== true) {
528 36
            return [$uniqueNames, $insertNames, null];
529
        }
530
531 30
        return [$uniqueNames, $insertNames, array_diff($insertNames, $uniqueNames)];
532
    }
533
534
    /**
535
     * Returns all column names belonging to constraints enforcing uniqueness (`PRIMARY KEY`, `UNIQUE INDEX`, etc.)
536
     * for the named table removing constraints which did not cover the specified column list.
537
     * The column list will be unique by column names.
538
     *
539
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
540
     * @param string[] $columns source column list.
541
     * @param Constraint[] $constraints this parameter optionally recieves a matched constraint list.
542
     * The constraints will be unique by their column names.
543
     * @return string[] column list.
544
     */
545 66
    private function getTableUniqueColumnNames($name, $columns, &$constraints = [])
546
    {
547 66
        $schema = $this->db->getSchema();
548 66
        if (!$schema instanceof ConstraintFinderInterface) {
549
            return [];
550
        }
551
552 66
        $constraints = [];
553 66
        $primaryKey = $schema->getTablePrimaryKey($name);
554 66
        if ($primaryKey !== null) {
555 49
            $constraints[] = $primaryKey;
556
        }
557 66
        foreach ($schema->getTableIndexes($name) as $constraint) {
558 66
            if ($constraint->isUnique) {
559 66
                $constraints[] = $constraint;
560
            }
561
        }
562 66
        $constraints = array_merge($constraints, $schema->getTableUniques($name));
563
        // Remove duplicates
564 66
        $constraints = array_combine(array_map(function (Constraint $constraint) {
565 66
            $columns = $constraint->columnNames;
566 66
            sort($columns, SORT_STRING);
567 66
            return json_encode($columns);
568 66
        }, $constraints), $constraints);
569 66
        $columnNames = [];
570
        // Remove all constraints which do not cover the specified column list
571 66
        $constraints = array_values(array_filter($constraints, function (Constraint $constraint) use ($schema, $columns, &$columnNames) {
572 66
            $constraintColumnNames = array_map([$schema, 'quoteColumnName'], $constraint->columnNames);
573 66
            $result = !array_diff($constraintColumnNames, $columns);
574 66
            if ($result) {
575 57
                $columnNames = array_merge($columnNames, $constraintColumnNames);
576
            }
577 66
            return $result;
578 66
        }));
579 66
        return array_unique($columnNames);
580
    }
581
582
    /**
583
     * Creates an UPDATE SQL statement.
584
     *
585
     * For example,
586
     *
587
     * ```php
588
     * $params = [];
589
     * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params);
590
     * ```
591
     *
592
     * The method will properly escape the table and column names.
593
     *
594
     * @param string $table the table to be updated.
595
     * @param array $columns the column data (name => value) to be updated.
596
     * @param array|string $condition the condition that will be put in the WHERE part. Please
597
     * refer to [[Query::where()]] on how to specify condition.
598
     * @param array $params the binding parameters that will be modified by this method
599
     * so that they can be bound to the DB command later.
600
     * @return string the UPDATE SQL
601
     */
602 136
    public function update($table, $columns, $condition, &$params)
603
    {
604 136
        [$lines, $params] = $this->prepareUpdateSets($table, $columns, $params);
0 ignored issues
show
Bug introduced by
The variable $lines 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...
605 136
        $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
606 136
        $where = $this->buildWhere($condition, $params);
607 136
        return $where === '' ? $sql : $sql . ' ' . $where;
608
    }
609
610
    /**
611
     * Prepares a `SET` parts for an `UPDATE` SQL statement.
612
     * @param string $table the table to be updated.
613
     * @param array $columns the column data (name => value) to be updated.
614
     * @param array $params the binding parameters that will be modified by this method
615
     * so that they can be bound to the DB command later.
616
     * @return array an array `SET` parts for an `UPDATE` SQL statement (the first array element) and params (the second array element).
617
     * @since 2.0.14
618
     */
619 168
    protected function prepareUpdateSets($table, $columns, $params = [])
620
    {
621 168
        $tableSchema = $this->db->getTableSchema($table);
622 168
        $columnSchemas = $tableSchema !== null ? $tableSchema->columns : [];
623 168
        $sets = [];
624 168
        foreach ($columns as $name => $value) {
625
626 168
            $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
627 168
            if ($value instanceof ExpressionInterface) {
628 93
                $placeholder = $this->buildExpression($value, $params);
629
            } else {
630 127
                $placeholder = $this->bindParam($value, $params);
631
            }
632
633 168
            $sets[] = $this->db->quoteColumnName($name) . '=' . $placeholder;
634
        }
635 168
        return [$sets, $params];
636
    }
637
638
    /**
639
     * Creates a DELETE SQL statement.
640
     *
641
     * For example,
642
     *
643
     * ```php
644
     * $sql = $queryBuilder->delete('user', 'status = 0');
645
     * ```
646
     *
647
     * The method will properly escape the table and column names.
648
     *
649
     * @param string $table the table where the data will be deleted from.
650
     * @param array|string $condition the condition that will be put in the WHERE part. Please
651
     * refer to [[Query::where()]] on how to specify condition.
652
     * @param array $params the binding parameters that will be modified by this method
653
     * so that they can be bound to the DB command later.
654
     * @return string the DELETE SQL
655
     */
656 358
    public function delete($table, $condition, &$params)
657
    {
658 358
        $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
659 358
        $where = $this->buildWhere($condition, $params);
660
661 358
        return $where === '' ? $sql : $sql . ' ' . $where;
662
    }
663
664
    /**
665
     * Builds a SQL statement for creating a new DB table.
666
     *
667
     * The columns in the new  table should be specified as name-definition pairs (e.g. 'name' => 'string'),
668
     * where name stands for a column name which will be properly quoted by the method, and definition
669
     * stands for the column type which can contain an abstract DB type.
670
     * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
671
     *
672
     * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
673
     * inserted into the generated SQL.
674
     *
675
     * For example,
676
     *
677
     * ```php
678
     * $sql = $queryBuilder->createTable('user', [
679
     *  'id' => 'pk',
680
     *  'name' => 'string',
681
     *  'age' => 'integer',
682
     * ]);
683
     * ```
684
     *
685
     * @param string $table the name of the table to be created. The name will be properly quoted by the method.
686
     * @param array $columns the columns (name => definition) in the new table.
687
     * @param string $options additional SQL fragment that will be appended to the generated SQL.
688
     * @return string the SQL statement for creating a new DB table.
689
     */
690 137
    public function createTable($table, $columns, $options = null)
691
    {
692 137
        $cols = [];
693 137
        foreach ($columns as $name => $type) {
694 137
            if (is_string($name)) {
695 137
                $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
696
            } else {
697 137
                $cols[] = "\t" . $type;
698
            }
699
        }
700 137
        $sql = 'CREATE TABLE ' . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
701
702 137
        return $options === null ? $sql : $sql . ' ' . $options;
703
    }
704
705
    /**
706
     * Builds a SQL statement for renaming a DB table.
707
     * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
708
     * @param string $newName the new table name. The name will be properly quoted by the method.
709
     * @return string the SQL statement for renaming a DB table.
710
     */
711 1
    public function renameTable($oldName, $newName)
712
    {
713 1
        return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
714
    }
715
716
    /**
717
     * Builds a SQL statement for dropping a DB table.
718
     * @param string $table the table to be dropped. The name will be properly quoted by the method.
719
     * @return string the SQL statement for dropping a DB table.
720
     */
721 39
    public function dropTable($table)
722
    {
723 39
        return 'DROP TABLE ' . $this->db->quoteTableName($table);
724
    }
725
726
    /**
727
     * Builds a SQL statement for adding a primary key constraint to an existing table.
728
     * @param string $name the name of the primary key constraint.
729
     * @param string $table the table that the primary key constraint will be added to.
730
     * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
731
     * @return string the SQL statement for adding a primary key constraint to an existing table.
732
     */
733 6
    public function addPrimaryKey($name, $table, $columns)
734
    {
735 6
        if (is_string($columns)) {
736 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
737
        }
738
739 6
        foreach ($columns as $i => $col) {
740 6
            $columns[$i] = $this->db->quoteColumnName($col);
741
        }
742
743 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
744 6
            . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
745 6
            . implode(', ', $columns) . ')';
746
    }
747
748
    /**
749
     * Builds a SQL statement for removing a primary key constraint to an existing table.
750
     * @param string $name the name of the primary key constraint to be removed.
751
     * @param string $table the table that the primary key constraint will be removed from.
752
     * @return string the SQL statement for removing a primary key constraint from an existing table.
753
     */
754 2
    public function dropPrimaryKey($name, $table)
755
    {
756 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
757 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
758
    }
759
760
    /**
761
     * Builds a SQL statement for truncating a DB table.
762
     * @param string $table the table to be truncated. The name will be properly quoted by the method.
763
     * @return string the SQL statement for truncating a DB table.
764
     */
765 11
    public function truncateTable($table)
766
    {
767 11
        return 'TRUNCATE TABLE ' . $this->db->quoteTableName($table);
768
    }
769
770
    /**
771
     * Builds a SQL statement for adding a new DB column.
772
     * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
773
     * @param string $column the name of the new column. The name will be properly quoted by the method.
774
     * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
775
     * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
776
     * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
777
     * @return string the SQL statement for adding a new column.
778
     */
779 4
    public function addColumn($table, $column, $type)
780
    {
781 4
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
782 4
            . ' ADD ' . $this->db->quoteColumnName($column) . ' '
783 4
            . $this->getColumnType($type);
784
    }
785
786
    /**
787
     * Builds a SQL statement for dropping a DB column.
788
     * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
789
     * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
790
     * @return string the SQL statement for dropping a DB column.
791
     */
792
    public function dropColumn($table, $column)
793
    {
794
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
795
            . ' DROP COLUMN ' . $this->db->quoteColumnName($column);
796
    }
797
798
    /**
799
     * Builds a SQL statement for renaming a column.
800
     * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
801
     * @param string $oldName the old name of the column. The name will be properly quoted by the method.
802
     * @param string $newName the new name of the column. The name will be properly quoted by the method.
803
     * @return string the SQL statement for renaming a DB column.
804
     */
805
    public function renameColumn($table, $oldName, $newName)
806
    {
807
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
808
            . ' RENAME COLUMN ' . $this->db->quoteColumnName($oldName)
809
            . ' TO ' . $this->db->quoteColumnName($newName);
810
    }
811
812
    /**
813
     * Builds a SQL statement for changing the definition of a column.
814
     * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
815
     * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
816
     * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
817
     * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
818
     * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
819
     * will become 'varchar(255) not null'.
820
     * @return string the SQL statement for changing the definition of a column.
821
     */
822 1
    public function alterColumn($table, $column, $type)
823
    {
824 1
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
825 1
            . $this->db->quoteColumnName($column) . ' '
826 1
            . $this->db->quoteColumnName($column) . ' '
827 1
            . $this->getColumnType($type);
828
    }
829
830
    /**
831
     * Builds a SQL statement for adding a foreign key constraint to an existing table.
832
     * The method will properly quote the table and column names.
833
     * @param string $name the name of the foreign key constraint.
834
     * @param string $table the table that the foreign key constraint will be added to.
835
     * @param string|array $columns the name of the column to that the constraint will be added on.
836
     * If there are multiple columns, separate them with commas or use an array to represent them.
837
     * @param string $refTable the table that the foreign key references to.
838
     * @param string|array $refColumns the name of the column that the foreign key references to.
839
     * If there are multiple columns, separate them with commas or use an array to represent them.
840
     * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
841
     * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
842
     * @return string the SQL statement for adding a foreign key constraint to an existing table.
843
     */
844 8
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
845
    {
846 8
        $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
847 8
            . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
848 8
            . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
849 8
            . ' REFERENCES ' . $this->db->quoteTableName($refTable)
850 8
            . ' (' . $this->buildColumns($refColumns) . ')';
851 8
        if ($delete !== null) {
852 4
            $sql .= ' ON DELETE ' . $delete;
853
        }
854 8
        if ($update !== null) {
855 4
            $sql .= ' ON UPDATE ' . $update;
856
        }
857
858 8
        return $sql;
859
    }
860
861
    /**
862
     * Builds a SQL statement for dropping a foreign key constraint.
863
     * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
864
     * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
865
     * @return string the SQL statement for dropping a foreign key constraint.
866
     */
867 3
    public function dropForeignKey($name, $table)
868
    {
869 3
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
870 3
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
871
    }
872
873
    /**
874
     * Builds a SQL statement for creating a new index.
875
     * @param string $name the name of the index. The name will be properly quoted by the method.
876
     * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
877
     * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
878
     * separate them with commas or use an array to represent them. Each column name will be properly quoted
879
     * by the method, unless a parenthesis is found in the name.
880
     * @param bool $unique whether to add UNIQUE constraint on the created index.
881
     * @return string the SQL statement for creating a new index.
882
     */
883 6
    public function createIndex($name, $table, $columns, $unique = false)
884
    {
885 6
        return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
886 6
            . $this->db->quoteTableName($name) . ' ON '
887 6
            . $this->db->quoteTableName($table)
888 6
            . ' (' . $this->buildColumns($columns) . ')';
889
    }
890
891
    /**
892
     * Builds a SQL statement for dropping an index.
893
     * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
894
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
895
     * @return string the SQL statement for dropping an index.
896
     */
897 4
    public function dropIndex($name, $table)
898
    {
899 4
        return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
900
    }
901
902
    /**
903
     * Creates a SQL command for adding an unique constraint to an existing table.
904
     * @param string $name the name of the unique constraint.
905
     * The name will be properly quoted by the method.
906
     * @param string $table the table that the unique constraint will be added to.
907
     * The name will be properly quoted by the method.
908
     * @param string|array $columns the name of the column to that the constraint will be added on.
909
     * If there are multiple columns, separate them with commas.
910
     * The name will be properly quoted by the method.
911
     * @return string the SQL statement for adding an unique constraint to an existing table.
912
     * @since 2.0.13
913
     */
914 6
    public function addUnique($name, $table, $columns)
915
    {
916 6
        if (is_string($columns)) {
917 4
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
918
        }
919 6
        foreach ($columns as $i => $col) {
920 6
            $columns[$i] = $this->db->quoteColumnName($col);
921
        }
922
923 6
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
924 6
            . $this->db->quoteColumnName($name) . ' UNIQUE ('
925 6
            . implode(', ', $columns) . ')';
926
    }
927
928
    /**
929
     * Creates a SQL command for dropping an unique constraint.
930
     * @param string $name the name of the unique constraint to be dropped.
931
     * The name will be properly quoted by the method.
932
     * @param string $table the table whose unique constraint is to be dropped.
933
     * The name will be properly quoted by the method.
934
     * @return string the SQL statement for dropping an unique constraint.
935
     * @since 2.0.13
936
     */
937 2
    public function dropUnique($name, $table)
938
    {
939 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
940 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
941
    }
942
943
    /**
944
     * Creates a SQL command for adding a check constraint to an existing table.
945
     * @param string $name the name of the check constraint.
946
     * The name will be properly quoted by the method.
947
     * @param string $table the table that the check constraint will be added to.
948
     * The name will be properly quoted by the method.
949
     * @param string $expression the SQL of the `CHECK` constraint.
950
     * @return string the SQL statement for adding a check constraint to an existing table.
951
     * @since 2.0.13
952
     */
953 2
    public function addCheck($name, $table, $expression)
954
    {
955 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
956 2
            . $this->db->quoteColumnName($name) . ' CHECK (' . $this->db->quoteSql($expression) . ')';
957
    }
958
959
    /**
960
     * Creates a SQL command for dropping a check constraint.
961
     * @param string $name the name of the check constraint to be dropped.
962
     * The name will be properly quoted by the method.
963
     * @param string $table the table whose check constraint is to be dropped.
964
     * The name will be properly quoted by the method.
965
     * @return string the SQL statement for dropping a check constraint.
966
     * @since 2.0.13
967
     */
968 2
    public function dropCheck($name, $table)
969
    {
970 2
        return 'ALTER TABLE ' . $this->db->quoteTableName($table)
971 2
            . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
972
    }
973
974
    /**
975
     * Creates a SQL command for adding a default value constraint to an existing table.
976
     * @param string $name the name of the default value constraint.
977
     * The name will be properly quoted by the method.
978
     * @param string $table the table that the default value constraint will be added to.
979
     * The name will be properly quoted by the method.
980
     * @param string $column the name of the column to that the constraint will be added on.
981
     * The name will be properly quoted by the method.
982
     * @param mixed $value default value.
983
     * @return string the SQL statement for adding a default value constraint to an existing table.
984
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
985
     * @since 2.0.13
986
     */
987
    public function addDefaultValue($name, $table, $column, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $table is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $column is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
988
    {
989
        throw new NotSupportedException($this->db->getDriverName() . ' does not support adding default value constraints.');
990
    }
991
992
    /**
993
     * Creates a SQL command for dropping a default value constraint.
994
     * @param string $name the name of the default value constraint to be dropped.
995
     * The name will be properly quoted by the method.
996
     * @param string $table the table whose default value constraint is to be dropped.
997
     * The name will be properly quoted by the method.
998
     * @return string the SQL statement for dropping a default value constraint.
999
     * @throws NotSupportedException if this is not supported by the underlying DBMS.
1000
     * @since 2.0.13
1001
     */
1002
    public function dropDefaultValue($name, $table)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $table is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1003
    {
1004
        throw new NotSupportedException($this->db->getDriverName() . ' does not support dropping default value constraints.');
1005
    }
1006
1007
    /**
1008
     * Creates a SQL statement for resetting the sequence value of a table's primary key.
1009
     * The sequence will be reset such that the primary key of the next new row inserted
1010
     * will have the specified value or 1.
1011
     * @param string $table the name of the table whose primary key sequence will be reset
1012
     * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
1013
     * the next new row's primary key will have a value 1.
1014
     * @return string the SQL statement for resetting sequence
1015
     * @throws NotSupportedException if this is not supported by the underlying DBMS
1016
     */
1017
    public function resetSequence($table, $value = null)
1018
    {
1019
        throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
1020
    }
1021
1022
    /**
1023
     * Builds a SQL statement for enabling or disabling integrity check.
1024
     * @param bool $check whether to turn on or off the integrity check.
1025
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
1026
     * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
1027
     * @return string the SQL statement for checking integrity
1028
     * @throws NotSupportedException if this is not supported by the underlying DBMS
1029
     */
1030
    public function checkIntegrity($check = true, $schema = '', $table = '')
1031
    {
1032
        throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
1033
    }
1034
1035
    /**
1036
     * Builds a SQL command for adding comment to column.
1037
     *
1038
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1039
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
1040
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1041
     * @return string the SQL statement for adding comment on column
1042
     * @since 2.0.8
1043
     */
1044 2
    public function addCommentOnColumn($table, $column, $comment)
1045
    {
1046 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS ' . $this->db->quoteValue($comment);
1047
    }
1048
1049
    /**
1050
     * Builds a SQL command for adding comment to table.
1051
     *
1052
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1053
     * @param string $comment the text of the comment to be added. The comment will be properly quoted by the method.
1054
     * @return string the SQL statement for adding comment on table
1055
     * @since 2.0.8
1056
     */
1057 1
    public function addCommentOnTable($table, $comment)
1058
    {
1059 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS ' . $this->db->quoteValue($comment);
1060
    }
1061
1062
    /**
1063
     * Builds a SQL command for adding comment to column.
1064
     *
1065
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1066
     * @param string $column the name of the column to be commented. The column name will be properly quoted by the method.
1067
     * @return string the SQL statement for adding comment on column
1068
     * @since 2.0.8
1069
     */
1070 2
    public function dropCommentFromColumn($table, $column)
1071
    {
1072 2
        return 'COMMENT ON COLUMN ' . $this->db->quoteTableName($table) . '.' . $this->db->quoteColumnName($column) . ' IS NULL';
1073
    }
1074
1075
    /**
1076
     * Builds a SQL command for adding comment to table.
1077
     *
1078
     * @param string $table the table whose column is to be commented. The table name will be properly quoted by the method.
1079
     * @return string the SQL statement for adding comment on column
1080
     * @since 2.0.8
1081
     */
1082 1
    public function dropCommentFromTable($table)
1083
    {
1084 1
        return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . ' IS NULL';
1085
    }
1086
1087
    /**
1088
     * Creates a SQL View.
1089
     *
1090
     * @param string $viewName the name of the view to be created.
1091
     * @param string|Query $subQuery the select statement which defines the view.
1092
     * This can be either a string or a [[Query]] object.
1093
     * @return string the `CREATE VIEW` SQL statement.
1094
     * @since 2.0.14
1095
     */
1096 3
    public function createView($viewName, $subQuery)
1097
    {
1098 3
        if ($subQuery instanceof Query) {
1099 3
            [$rawQuery, $params] = $this->build($subQuery);
0 ignored issues
show
Bug introduced by
The variable $rawQuery does not exist. Did you forget to declare it?

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

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

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

Let’s take a look at a simple example:

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

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

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

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

Loading history...
1100 3
            array_walk(
1101 3
                $params,
1102 3
                function(&$param) {
1103 3
                    $param = $this->db->quoteValue($param);
1104 3
                }
1105
            );
1106 3
            $subQuery = strtr($rawQuery, $params);
1107
        }
1108
1109 3
        return 'CREATE VIEW ' . $this->db->quoteTableName($viewName) . ' AS ' . $subQuery;
1110
    }
1111
1112
    /**
1113
     * Drops a SQL View.
1114
     *
1115
     * @param string $viewName the name of the view to be dropped.
1116
     * @return string the `DROP VIEW` SQL statement.
1117
     * @since 2.0.14
1118
     */
1119 3
    public function dropView($viewName)
1120
    {
1121 3
        return 'DROP VIEW ' . $this->db->quoteTableName($viewName);
1122
    }
1123
1124
    /**
1125
     * Converts an abstract column type into a physical column type.
1126
     *
1127
     * The conversion is done using the type map specified in [[typeMap]].
1128
     * The following abstract column types are supported (using MySQL as an example to explain the corresponding
1129
     * physical types):
1130
     *
1131
     * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
1132
     * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
1133
     * - `upk`: an unsigned auto-incremental primary key type, will be converted into "int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY"
1134
     * - `char`: char type, will be converted into "char(1)"
1135
     * - `string`: string type, will be converted into "varchar(255)"
1136
     * - `text`: a long string type, will be converted into "text"
1137
     * - `smallint`: a small integer type, will be converted into "smallint(6)"
1138
     * - `integer`: integer type, will be converted into "int(11)"
1139
     * - `bigint`: a big integer type, will be converted into "bigint(20)"
1140
     * - `boolean`: boolean type, will be converted into "tinyint(1)"
1141
     * - `float``: float number type, will be converted into "float"
1142
     * - `decimal`: decimal number type, will be converted into "decimal"
1143
     * - `datetime`: datetime type, will be converted into "datetime"
1144
     * - `timestamp`: timestamp type, will be converted into "timestamp"
1145
     * - `time`: time type, will be converted into "time"
1146
     * - `date`: date type, will be converted into "date"
1147
     * - `money`: money type, will be converted into "decimal(19,4)"
1148
     * - `binary`: binary data type, will be converted into "blob"
1149
     *
1150
     * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
1151
     * the first part will be converted, and the rest of the parts will be appended to the converted result.
1152
     * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
1153
     *
1154
     * For some of the abstract types you can also specify a length or precision constraint
1155
     * by appending it in round brackets directly to the type.
1156
     * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
1157
     * If the underlying DBMS does not support these kind of constraints for a type it will
1158
     * be ignored.
1159
     *
1160
     * If a type cannot be found in [[typeMap]], it will be returned without any change.
1161
     * @param string|ColumnSchemaBuilder $type abstract column type
1162
     * @return string physical column type.
1163
     */
1164 141
    public function getColumnType($type)
1165
    {
1166 141
        if ($type instanceof ColumnSchemaBuilder) {
1167 33
            $type = $type->__toString();
1168
        }
1169
1170 141
        if (isset($this->typeMap[$type])) {
1171 128
            return $this->typeMap[$type];
1172 77
        } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
1173 39
            if (isset($this->typeMap[$matches[1]])) {
1174 39
                return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
1175
            }
1176 52
        } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
1177 49
            if (isset($this->typeMap[$matches[1]])) {
1178 49
                return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
1179
            }
1180
        }
1181
1182 32
        return $type;
1183
    }
1184
1185
    /**
1186
     * @param array $columns
1187
     * @param array $params the binding parameters to be populated
1188
     * @param bool $distinct
1189
     * @param string $selectOption
1190
     * @return string the SELECT clause built from [[Query::$select]].
1191
     */
1192 1160
    public function buildSelect($columns, &$params, $distinct = false, $selectOption = null)
1193
    {
1194 1160
        $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
1195 1160
        if ($selectOption !== null) {
1196
            $select .= ' ' . $selectOption;
1197
        }
1198
1199 1160
        if (empty($columns)) {
1200 895
            return $select . ' *';
1201
        }
1202
1203 546
        foreach ($columns as $i => $column) {
1204 546
            if ($column instanceof ExpressionInterface) {
1205 42
                if (is_int($i)) {
1206 6
                    $columns[$i] = $this->buildExpression($column, $params);
1207
                } else {
1208 42
                    $columns[$i] = $this->buildExpression($column, $params) . ' AS ' . $this->db->quoteColumnName($i);
1209
                }
1210 537
            } elseif ($column instanceof Query) {
1211
                [$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...
1212
                $columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
1213 537
            } elseif (is_string($i)) {
1214 23
                if (strpos($column, '(') === false) {
1215 23
                    $column = $this->db->quoteColumnName($column);
1216
                }
1217 23
                $columns[$i] = "$column AS " . $this->db->quoteColumnName($i);
1218 532
            } elseif (strpos($column, '(') === false) {
1219 448
                if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
1220 6
                    $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
1221
                } else {
1222 546
                    $columns[$i] = $this->db->quoteColumnName($column);
1223
                }
1224
            }
1225
        }
1226
1227 546
        return $select . ' ' . implode(', ', $columns);
1228
    }
1229
1230
    /**
1231
     * @param array $tables
1232
     * @param array $params the binding parameters to be populated
1233
     * @return string the FROM clause built from [[Query::$from]].
1234
     */
1235 1160
    public function buildFrom($tables, &$params)
1236
    {
1237 1160
        if (empty($tables)) {
1238 349
            return '';
1239
        }
1240
1241 848
        $tables = $this->quoteTableNames($tables, $params);
1242
1243 848
        return 'FROM ' . implode(', ', $tables);
1244
    }
1245
1246
    /**
1247
     * @param array $joins
1248
     * @param array $params the binding parameters to be populated
1249
     * @return string the JOIN clause built from [[Query::$join]].
1250
     * @throws Exception if the $joins parameter is not in proper format
1251
     */
1252 1160
    public function buildJoin($joins, &$params)
1253
    {
1254 1160
        if (empty($joins)) {
1255 1148
            return '';
1256
        }
1257
1258 54
        foreach ($joins as $i => $join) {
1259 54
            if (!is_array($join) || !isset($join[0], $join[1])) {
1260
                throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
1261
            }
1262
            // 0:join type, 1:join table, 2:on-condition (optional)
1263 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...
1264 54
            $tables = $this->quoteTableNames((array) $table, $params);
1265 54
            $table = reset($tables);
1266 54
            $joins[$i] = "$joinType $table";
1267 54
            if (isset($join[2])) {
1268 54
                $condition = $this->buildCondition($join[2], $params);
1269 54
                if ($condition !== '') {
1270 54
                    $joins[$i] .= ' ON ' . $condition;
1271
                }
1272
            }
1273
        }
1274
1275 54
        return implode($this->separator, $joins);
1276
    }
1277
1278
    /**
1279
     * Quotes table names passed.
1280
     *
1281
     * @param array $tables
1282
     * @param array $params
1283
     * @return array
1284
     */
1285 848
    private function quoteTableNames($tables, &$params)
1286
    {
1287 848
        foreach ($tables as $i => $table) {
1288 848
            if ($table instanceof Query) {
1289 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...
1290 10
                $tables[$i] = "($sql) " . $this->db->quoteTableName($i);
1291 848
            } elseif (is_string($i)) {
1292 79
                if (strpos($table, '(') === false) {
1293 70
                    $table = $this->db->quoteTableName($table);
1294
                }
1295 79
                $tables[$i] = "$table " . $this->db->quoteTableName($i);
1296 827
            } elseif (strpos($table, '(') === false) {
1297 820
                if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
1298 21
                    $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
1299
                } else {
1300 848
                    $tables[$i] = $this->db->quoteTableName($table);
1301
                }
1302
            }
1303
        }
1304
1305 848
        return $tables;
1306
    }
1307
1308
    /**
1309
     * @param string|array $condition
1310
     * @param array $params the binding parameters to be populated
1311
     * @return string the WHERE clause built from [[Query::$where]].
1312
     */
1313 1246
    public function buildWhere($condition, &$params)
1314
    {
1315 1246
        $where = $this->buildCondition($condition, $params);
1316
1317 1246
        return $where === '' ? '' : 'WHERE ' . $where;
1318
    }
1319
1320
    /**
1321
     * @param array $columns
1322
     * @param array $params the binding parameters to be populated
1323
     * @return string the GROUP BY clause
1324
     */
1325 1160
    public function buildGroupBy($columns, &$params)
1326
    {
1327 1160
        if (empty($columns)) {
1328 1154
            return '';
1329
        }
1330 21
        foreach ($columns as $i => $column) {
1331 21
            if ($column instanceof ExpressionInterface) {
1332 3
                $columns[$i] = $this->buildExpression($column);
1333 3
                $params = array_merge($params, $column->params);
0 ignored issues
show
Bug introduced by
Accessing params on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1334 21
            } elseif (strpos($column, '(') === false) {
1335 21
                $columns[$i] = $this->db->quoteColumnName($column);
1336
            }
1337
        }
1338
1339 21
        return 'GROUP BY ' . implode(', ', $columns);
1340
    }
1341
1342
    /**
1343
     * @param string|array $condition
1344
     * @param array $params the binding parameters to be populated
1345
     * @return string the HAVING clause built from [[Query::$having]].
1346
     */
1347 1160
    public function buildHaving($condition, &$params)
1348
    {
1349 1160
        $having = $this->buildCondition($condition, $params);
1350
1351 1160
        return $having === '' ? '' : 'HAVING ' . $having;
1352
    }
1353
1354
    /**
1355
     * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL.
1356
     * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
1357
     * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
1358
     * @param int $limit the limit number. See [[Query::limit]] for more details.
1359
     * @param int $offset the offset number. See [[Query::offset]] for more details.
1360
     * @param array $params the binding parameters to be populated
1361
     * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
1362
     */
1363 1160
    public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset, &$params)
1364
    {
1365 1160
        $orderBy = $this->buildOrderBy($orderBy, $params);
1366 1160
        if ($orderBy !== '') {
1367 180
            $sql .= $this->separator . $orderBy;
1368
        }
1369 1160
        $limit = $this->buildLimit($limit, $offset);
1370 1160
        if ($limit !== '') {
1371 91
            $sql .= $this->separator . $limit;
1372
        }
1373
1374 1160
        return $sql;
1375
    }
1376
1377
    /**
1378
     * @param array $columns
1379
     * @param array $params the binding parameters to be populated
1380
     * @return string the ORDER BY clause built from [[Query::$orderBy]].
1381
     */
1382 1160
    public function buildOrderBy($columns, &$params)
1383
    {
1384 1160
        if (empty($columns)) {
1385 1125
            return '';
1386
        }
1387 180
        $orders = [];
1388 180
        foreach ($columns as $name => $direction) {
1389 180
            if ($direction instanceof ExpressionInterface) {
1390 3
                $orders[] = $this->buildExpression($direction);
1391 3
                $params = array_merge($params, $direction->params);
0 ignored issues
show
Bug introduced by
Accessing params on the interface yii\db\ExpressionInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1392
            } else {
1393 180
                $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
1394
            }
1395
        }
1396
1397 180
        return 'ORDER BY ' . implode(', ', $orders);
1398
    }
1399
1400
    /**
1401
     * @param int $limit
1402
     * @param int $offset
1403
     * @return string the LIMIT and OFFSET clauses
1404
     */
1405 420
    public function buildLimit($limit, $offset)
1406
    {
1407 420
        $sql = '';
1408 420
        if ($this->hasLimit($limit)) {
1409 25
            $sql = 'LIMIT ' . $limit;
1410
        }
1411 420
        if ($this->hasOffset($offset)) {
1412 3
            $sql .= ' OFFSET ' . $offset;
1413
        }
1414
1415 420
        return ltrim($sql);
1416
    }
1417
1418
    /**
1419
     * Checks to see if the given limit is effective.
1420
     * @param mixed $limit the given limit
1421
     * @return bool whether the limit is effective
1422
     */
1423 761
    protected function hasLimit($limit)
1424
    {
1425 761
        return ($limit instanceof ExpressionInterface) || ctype_digit((string) $limit);
1426
    }
1427
1428
    /**
1429
     * Checks to see if the given offset is effective.
1430
     * @param mixed $offset the given offset
1431
     * @return bool whether the offset is effective
1432
     */
1433 761
    protected function hasOffset($offset)
1434
    {
1435 761
        return ($offset instanceof ExpressionInterface) || ctype_digit((string) $offset) && (string) $offset !== '0';
1436
    }
1437
1438
    /**
1439
     * @param array $unions
1440
     * @param array $params the binding parameters to be populated
1441
     * @return string the UNION clause built from [[Query::$union]].
1442
     */
1443 819
    public function buildUnion($unions, &$params)
1444
    {
1445 819
        if (empty($unions)) {
1446 819
            return '';
1447
        }
1448
1449 8
        $result = '';
1450
1451 8
        foreach ($unions as $i => $union) {
1452 8
            $query = $union['query'];
1453 8
            if ($query instanceof Query) {
1454 8
                [$unions[$i]['query'], $params] = $this->build($query, $params);
1455
            }
1456
1457 8
            $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) ';
1458
        }
1459
1460 8
        return trim($result);
1461
    }
1462
1463
    /**
1464
     * Processes columns and properly quotes them if necessary.
1465
     * It will join all columns into a string with comma as separators.
1466
     * @param string|array $columns the columns to be processed
1467
     * @return string the processing result
1468
     */
1469 32
    public function buildColumns($columns)
1470
    {
1471 32
        if (!is_array($columns)) {
1472 27
            if (strpos($columns, '(') !== false) {
1473
                return $columns;
1474
            }
1475
1476 27
            $rawColumns = $columns;
1477 27
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
1478 27
            if ($columns === false) {
1479
                throw new InvalidArgumentException("$rawColumns is not valid columns.");
1480
            }
1481
        }
1482 32
        foreach ($columns as $i => $column) {
1483 32
            if ($column instanceof ExpressionInterface) {
1484
                $columns[$i] = $this->buildExpression($column);
1485 32
            } elseif (strpos($column, '(') === false) {
1486 32
                $columns[$i] = $this->db->quoteColumnName($column);
1487
            }
1488
        }
1489
1490 32
        return implode(', ', $columns);
1491
    }
1492
1493
    /**
1494
     * Parses the condition specification and generates the corresponding SQL expression.
1495
     * @param string|array|ExpressionInterface $condition the condition specification. Please refer to [[Query::where()]]
1496
     * on how to specify a condition.
1497
     * @param array $params the binding parameters to be populated
1498
     * @return string the generated SQL expression
1499
     */
1500 1246
    public function buildCondition($condition, &$params)
1501
    {
1502 1246
        if (is_array($condition)) {
1503 982
            if (empty($condition)) {
1504 3
                return '';
1505
            }
1506
1507 982
            $condition = $this->createConditionFromArray($condition);
1508
        }
1509
1510 1246
        if ($condition instanceof ExpressionInterface) {
1511 1003
            return $this->buildExpression($condition, $params);
1512
        }
1513
1514 1227
        return (string) $condition;
1515
    }
1516
1517
    /**
1518
     * Transforms $condition defined in array format (as described in [[Query::where()]]
1519
     * to instance of [[yii\db\condition\ConditionInterface|ConditionInterface]] according to
1520
     * [[conditionClasses]] map.
1521
     *
1522
     * @param string|array $condition
1523
     * @see conditionClasses
1524
     * @return ConditionInterface
1525
     * @since 2.0.14
1526
     */
1527 982
    public function createConditionFromArray($condition)
1528
    {
1529 982
        if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
1530 589
            $operator = strtoupper(array_shift($condition));
1531 589
            if (isset($this->conditionClasses[$operator])) {
1532 511
                $className = $this->conditionClasses[$operator];
1533
            } else {
1534 84
                $className = 'yii\db\conditions\SimpleCondition';
1535
            }
1536
            /** @var ConditionInterface $className */
1537 589
            return $className::fromArrayDefinition($operator, $condition);
1538
        }
1539
1540
        // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
1541 693
        return new HashCondition($condition);
0 ignored issues
show
Bug introduced by
It seems like $condition defined by parameter $condition on line 1527 can also be of type string; however, yii\db\conditions\HashCondition::__construct() does only seem to accept array|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1542
    }
1543
1544
    /**
1545
     * Creates a SELECT EXISTS() SQL statement.
1546
     * @param string $rawSql the subquery in a raw form to select from.
1547
     * @return string the SELECT EXISTS() SQL statement.
1548
     * @since 2.0.8
1549
     */
1550 70
    public function selectExists($rawSql)
1551
    {
1552 70
        return 'SELECT EXISTS(' . $rawSql . ')';
1553
    }
1554
1555
    /**
1556
     * Helper method to add $value to $params array using [[PARAM_PREFIX]].
1557
     *
1558
     * @param string|null $value
1559
     * @param array $params passed by reference
1560
     * @return string the placeholder name in $params array
1561
     *
1562
     * @since 2.0.14
1563
     */
1564 1031
    public function bindParam($value, &$params)
1565
    {
1566 1031
        $phName = self::PARAM_PREFIX . count($params);
1567 1031
        $params[$phName] = $value;
1568
1569 1031
        return $phName;
1570
    }
1571
}
1572