Completed
Push — master ( d4cc87...ceca16 )
by Dmitry
09:41
created

Query::column()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 16
cts 16
cp 1
rs 6.9811
c 0
b 0
f 0
cc 7
eloc 14
nc 7
nop 1
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;
11
use yii\base\Component;
12
13
/**
14
 * Query represents a SELECT SQL statement in a way that is independent of DBMS.
15
 *
16
 * Query provides a set of methods to facilitate the specification of different clauses
17
 * in a SELECT statement. These methods can be chained together.
18
 *
19
 * By calling [[createCommand()]], we can get a [[Command]] instance which can be further
20
 * used to perform/execute the DB query against a database.
21
 *
22
 * For example,
23
 *
24
 * ```php
25
 * $query = new Query;
26
 * // compose the query
27
 * $query->select('id, name')
28
 *     ->from('user')
29
 *     ->limit(10);
30
 * // build and execute the query
31
 * $rows = $query->all();
32
 * // alternatively, you can create DB command and execute it
33
 * $command = $query->createCommand();
34
 * // $command->sql returns the actual SQL
35
 * $rows = $command->queryAll();
36
 * ```
37
 *
38
 * Query internally uses the [[QueryBuilder]] class to generate the SQL statement.
39
 *
40
 * A more detailed usage guide on how to work with Query can be found in the [guide article on Query Builder](guide:db-query-builder).
41
 *
42
 * @author Qiang Xue <[email protected]>
43
 * @author Carsten Brandt <[email protected]>
44
 * @since 2.0
45
 */
46
class Query extends Component implements QueryInterface
47
{
48
    use QueryTrait;
49
50
    /**
51
     * @var array the columns being selected. For example, `['id', 'name']`.
52
     * This is used to construct the SELECT clause in a SQL statement. If not set, it means selecting all columns.
53
     * @see select()
54
     */
55
    public $select;
56
    /**
57
     * @var string additional option that should be appended to the 'SELECT' keyword. For example,
58
     * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
59
     */
60
    public $selectOption;
61
    /**
62
     * @var boolean whether to select distinct rows of data only. If this is set true,
63
     * the SELECT clause would be changed to SELECT DISTINCT.
64
     */
65
    public $distinct;
66
    /**
67
     * @var array the table(s) to be selected from. For example, `['user', 'post']`.
68
     * This is used to construct the FROM clause in a SQL statement.
69
     * @see from()
70
     */
71
    public $from;
72
    /**
73
     * @var array how to group the query results. For example, `['company', 'department']`.
74
     * This is used to construct the GROUP BY clause in a SQL statement.
75
     */
76
    public $groupBy;
77
    /**
78
     * @var array how to join with other tables. Each array element represents the specification
79
     * of one join which has the following structure:
80
     *
81
     * ```php
82
     * [$joinType, $tableName, $joinCondition]
83
     * ```
84
     *
85
     * For example,
86
     *
87
     * ```php
88
     * [
89
     *     ['INNER JOIN', 'user', 'user.id = author_id'],
90
     *     ['LEFT JOIN', 'team', 'team.id = team_id'],
91
     * ]
92
     * ```
93
     */
94
    public $join;
95
    /**
96
     * @var string|array the condition to be applied in the GROUP BY clause.
97
     * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
98
     */
99
    public $having;
100
    /**
101
     * @var array this is used to construct the UNION clause(s) in a SQL statement.
102
     * Each array element is an array of the following structure:
103
     *
104
     * - `query`: either a string or a [[Query]] object representing a query
105
     * - `all`: boolean, whether it should be `UNION ALL` or `UNION`
106
     */
107
    public $union;
108
    /**
109
     * @var array list of query parameter values indexed by parameter placeholders.
110
     * For example, `[':name' => 'Dan', ':age' => 31]`.
111
     */
112
    public $params = [];
113
114
115
    /**
116
     * Creates a DB command that can be used to execute this query.
117
     * @param Connection $db the database connection used to generate the SQL statement.
118
     * If this parameter is not given, the `db` application component will be used.
119
     * @return Command the created DB command instance.
120
     */
121 148
    public function createCommand($db = null)
122
    {
123 148
        if ($db === null) {
124 13
            $db = Yii::$app->getDb();
125 13
        }
126 148
        list ($sql, $params) = $db->getQueryBuilder()->build($this);
127
128 148
        return $db->createCommand($sql, $params);
129
    }
130
131
    /**
132
     * Prepares for building SQL.
133
     * This method is called by [[QueryBuilder]] when it starts to build SQL from a query object.
134
     * You may override this method to do some final preparation work when converting a query into a SQL statement.
135
     * @param QueryBuilder $builder
136
     * @return $this a prepared query instance which will be used by [[QueryBuilder]] to build the SQL
137
     */
138 452
    public function prepare($builder)
0 ignored issues
show
Unused Code introduced by
The parameter $builder 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...
139
    {
140 452
        return $this;
141
    }
142
143
    /**
144
     * Starts a batch query.
145
     *
146
     * A batch query supports fetching data in batches, which can keep the memory usage under a limit.
147
     * This method will return a [[BatchQueryResult]] object which implements the [[\Iterator]] interface
148
     * and can be traversed to retrieve the data in batches.
149
     *
150
     * For example,
151
     *
152
     * ```php
153
     * $query = (new Query)->from('user');
154
     * foreach ($query->batch() as $rows) {
155
     *     // $rows is an array of 100 or fewer rows from user table
156
     * }
157
     * ```
158
     *
159
     * @param integer $batchSize the number of records to be fetched in each batch.
160
     * @param Connection $db the database connection. If not set, the "db" application component will be used.
161
     * @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface
162
     * and can be traversed to retrieve the data in batches.
163
     */
164 6
    public function batch($batchSize = 100, $db = null)
165
    {
166 6
        return Yii::createObject([
167 6
            'class' => BatchQueryResult::className(),
168 6
            'query' => $this,
169 6
            'batchSize' => $batchSize,
170 6
            'db' => $db,
171 6
            'each' => false,
172 6
        ]);
173
    }
174
175
    /**
176
     * Starts a batch query and retrieves data row by row.
177
     * This method is similar to [[batch()]] except that in each iteration of the result,
178
     * only one row of data is returned. For example,
179
     *
180
     * ```php
181
     * $query = (new Query)->from('user');
182
     * foreach ($query->each() as $row) {
183
     * }
184
     * ```
185
     *
186
     * @param integer $batchSize the number of records to be fetched in each batch.
187
     * @param Connection $db the database connection. If not set, the "db" application component will be used.
188
     * @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface
189
     * and can be traversed to retrieve the data in batches.
190
     */
191 3
    public function each($batchSize = 100, $db = null)
192
    {
193 3
        return Yii::createObject([
194 3
            'class' => BatchQueryResult::className(),
195 3
            'query' => $this,
196 3
            'batchSize' => $batchSize,
197 3
            'db' => $db,
198 3
            'each' => true,
199 3
        ]);
200
    }
201
202
    /**
203
     * Executes the query and returns all results as an array.
204
     * @param Connection $db the database connection used to generate the SQL statement.
205
     * If this parameter is not given, the `db` application component will be used.
206
     * @return array the query results. If the query results in nothing, an empty array will be returned.
207
     */
208 253
    public function all($db = null)
209
    {
210 253
        $rows = $this->createCommand($db)->queryAll();
211 253
        return $this->populate($rows);
212
    }
213
214
    /**
215
     * Converts the raw query results into the format as specified by this query.
216
     * This method is internally used to convert the data fetched from database
217
     * into the format as required by this query.
218
     * @param array $rows the raw query result from database
219
     * @return array the converted query result
220
     */
221 98
    public function populate($rows)
222
    {
223 98
        if ($this->indexBy === null) {
224 98
            return $rows;
225
        }
226 3
        $result = [];
227 3
        foreach ($rows as $row) {
228 3
            if (is_string($this->indexBy)) {
229 3
                $key = $row[$this->indexBy];
230 3
            } else {
231
                $key = call_user_func($this->indexBy, $row);
232
            }
233 3
            $result[$key] = $row;
234 3
        }
235 3
        return $result;
236
    }
237
238
    /**
239
     * Executes the query and returns a single row of result.
240
     * @param Connection $db the database connection used to generate the SQL statement.
241
     * If this parameter is not given, the `db` application component will be used.
242
     * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
243
     * results in nothing.
244
     */
245 277
    public function one($db = null)
246
    {
247 277
        return $this->createCommand($db)->queryOne();
248
    }
249
250
    /**
251
     * Returns the query result as a scalar value.
252
     * The value returned will be the first column in the first row of the query results.
253
     * @param Connection $db the database connection used to generate the SQL statement.
254
     * If this parameter is not given, the `db` application component will be used.
255
     * @return string|null|false the value of the first column in the first row of the query result.
256
     * False is returned if the query result is empty.
257
     */
258 8
    public function scalar($db = null)
259
    {
260 8
        return $this->createCommand($db)->queryScalar();
261
    }
262
263
    /**
264
     * Executes the query and returns the first column of the result.
265
     * @param Connection $db the database connection used to generate the SQL statement.
266
     * If this parameter is not given, the `db` application component will be used.
267
     * @return array the first column of the query result. An empty array is returned if the query results in nothing.
268
     */
269 18
    public function column($db = null)
270
    {
271 18
        if ($this->indexBy === null) {
272 18
            return $this->createCommand($db)->queryColumn();
273
        }
274
275 3
        if (is_string($this->indexBy) && is_array($this->select) && count($this->select) === 1) {
276 3
            $this->select[] = $this->indexBy;
277 3
        }
278 3
        $rows = $this->createCommand($db)->queryAll();
279 3
        $results = [];
280 3
        foreach ($rows as $row) {
281 3
            $value = reset($row);
282
283 3
            if ($this->indexBy instanceof \Closure) {
284 3
                $results[call_user_func($this->indexBy, $row)] = $value;
285 3
            } else {
286 3
                $results[$row[$this->indexBy]] = $value;
287
            }
288 3
        }
289 3
        return $results;
290
    }
291
292
    /**
293
     * Returns the number of records.
294
     * @param string $q the COUNT expression. Defaults to '*'.
295
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
296
     * @param Connection $db the database connection used to generate the SQL statement.
297
     * If this parameter is not given (or null), the `db` application component will be used.
298
     * @return integer|string number of records. The result may be a string depending on the
299
     * underlying database engine and to support integer values higher than a 32bit PHP integer can handle.
300
     */
301 66
    public function count($q = '*', $db = null)
302
    {
303 66
        return $this->queryScalar("COUNT($q)", $db);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->queryScalar("COUNT({$q})", $db); of type string|null|false adds false to the return on line 303 which is incompatible with the return type declared by the interface yii\db\QueryInterface::count of type integer. It seems like you forgot to handle an error condition.
Loading history...
304
    }
305
306
    /**
307
     * Returns the sum of the specified column values.
308
     * @param string $q the column name or expression.
309
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
310
     * @param Connection $db the database connection used to generate the SQL statement.
311
     * If this parameter is not given, the `db` application component will be used.
312
     * @return mixed the sum of the specified column values.
313
     */
314 3
    public function sum($q, $db = null)
315
    {
316 3
        return $this->queryScalar("SUM($q)", $db);
317
    }
318
319
    /**
320
     * Returns the average of the specified column values.
321
     * @param string $q the column name or expression.
322
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
323
     * @param Connection $db the database connection used to generate the SQL statement.
324
     * If this parameter is not given, the `db` application component will be used.
325
     * @return mixed the average of the specified column values.
326
     */
327 3
    public function average($q, $db = null)
328
    {
329 3
        return $this->queryScalar("AVG($q)", $db);
330
    }
331
332
    /**
333
     * Returns the minimum of the specified column values.
334
     * @param string $q the column name or expression.
335
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
336
     * @param Connection $db the database connection used to generate the SQL statement.
337
     * If this parameter is not given, the `db` application component will be used.
338
     * @return mixed the minimum of the specified column values.
339
     */
340 3
    public function min($q, $db = null)
341
    {
342 3
        return $this->queryScalar("MIN($q)", $db);
343
    }
344
345
    /**
346
     * Returns the maximum of the specified column values.
347
     * @param string $q the column name or expression.
348
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
349
     * @param Connection $db the database connection used to generate the SQL statement.
350
     * If this parameter is not given, the `db` application component will be used.
351
     * @return mixed the maximum of the specified column values.
352
     */
353 3
    public function max($q, $db = null)
354
    {
355 3
        return $this->queryScalar("MAX($q)", $db);
356
    }
357
358
    /**
359
     * Returns a value indicating whether the query result contains any row of data.
360
     * @param Connection $db the database connection used to generate the SQL statement.
361
     * If this parameter is not given, the `db` application component will be used.
362
     * @return boolean whether the query result contains any row of data.
363
     */
364 45
    public function exists($db = null)
365
    {
366 45
        $command = $this->createCommand($db);
367 45
        $params = $command->params;
368 45
        $command->setSql($command->db->getQueryBuilder()->selectExists($command->getSql()));
369 45
        $command->bindValues($params);
370 45
        return (boolean)$command->queryScalar();
371
    }
372
373
    /**
374
     * Queries a scalar value by setting [[select]] first.
375
     * Restores the value of select to make this query reusable.
376
     * @param string|Expression $selectExpression
377
     * @param Connection|null $db
378
     * @return boolean|string
379
     */
380 63
    protected function queryScalar($selectExpression, $db)
381
    {
382 63
        $select = $this->select;
383 63
        $limit = $this->limit;
384 63
        $offset = $this->offset;
385
386 63
        $this->select = [$selectExpression];
387 63
        $this->limit = null;
388 63
        $this->offset = null;
389 63
        $command = $this->createCommand($db);
390
391 63
        $this->select = $select;
392 63
        $this->limit = $limit;
393 63
        $this->offset = $offset;
394
395 63
        if (empty($this->groupBy) && empty($this->having) && empty($this->union) && !$this->distinct) {
396 62
            return $command->queryScalar();
397
        } else {
398 7
            return (new Query)->select([$selectExpression])
399 7
                ->from(['c' => $this])
400 7
                ->createCommand($command->db)
401 7
                ->queryScalar();
402
        }
403
    }
404
405
    /**
406
     * Sets the SELECT part of the query.
407
     * @param string|array|Expression $columns the columns to be selected.
408
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
409
     * Columns can be prefixed with table names (e.g. "user.id") and/or contain column aliases (e.g. "user.id AS user_id").
410
     * The method will automatically quote the column names unless a column contains some parenthesis
411
     * (which means the column contains a DB expression). A DB expression may also be passed in form of
412
     * an [[Expression]] object.
413
     *
414
     * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should
415
     * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts.
416
     *
417
     * When the columns are specified as an array, you may also use array keys as the column aliases (if a column
418
     * does not need alias, do not use a string key).
419
     *
420
     * Starting from version 2.0.1, you may also select sub-queries as columns by specifying each such column
421
     * as a `Query` instance representing the sub-query.
422
     *
423
     * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
424
     * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
425
     * @return $this the query object itself
426
     */
427 180
    public function select($columns, $option = null)
428
    {
429 180
        if ($columns instanceof Expression) {
430 3
            $columns = [$columns];
431 180
        } elseif (!is_array($columns)) {
432 68
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
433 68
        }
434 180
        $this->select = $columns;
435 180
        $this->selectOption = $option;
436 180
        return $this;
437
    }
438
439
    /**
440
     * Add more columns to the SELECT part of the query.
441
     *
442
     * Note, that if [[select]] has not been specified before, you should include `*` explicitly
443
     * if you want to select all remaining columns too:
444
     *
445
     * ```php
446
     * $query->addSelect(["*", "CONCAT(first_name, ' ', last_name) AS full_name"])->one();
447
     * ```
448
     *
449
     * @param string|array|Expression $columns the columns to add to the select. See [[select()]] for more
450
     * details about the format of this parameter.
451
     * @return $this the query object itself
452
     * @see select()
453
     */
454 9
    public function addSelect($columns)
455
    {
456 9
        if ($columns instanceof Expression) {
457 3
            $columns = [$columns];
458 9
        } elseif (!is_array($columns)) {
459 3
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
460 3
        }
461 9
        if ($this->select === null) {
462 3
            $this->select = $columns;
463 3
        } else {
464 9
            $this->select = array_merge($this->select, $columns);
465
        }
466 9
        return $this;
467
    }
468
469
    /**
470
     * Sets the value indicating whether to SELECT DISTINCT or not.
471
     * @param boolean $value whether to SELECT DISTINCT or not.
472
     * @return $this the query object itself
473
     */
474 6
    public function distinct($value = true)
475
    {
476 6
        $this->distinct = $value;
477 6
        return $this;
478
    }
479
480
    /**
481
     * Sets the FROM part of the query.
482
     * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'user'`)
483
     * or an array (e.g. `['user', 'profile']`) specifying one or several table names.
484
     * Table names can contain schema prefixes (e.g. `'public.user'`) and/or table aliases (e.g. `'user u'`).
485
     * The method will automatically quote the table names unless it contains some parenthesis
486
     * (which means the table is given as a sub-query or DB expression).
487
     *
488
     * When the tables are specified as an array, you may also use the array keys as the table aliases
489
     * (if a table does not need alias, do not use a string key).
490
     *
491
     * Use a Query object to represent a sub-query. In this case, the corresponding array key will be used
492
     * as the alias for the sub-query.
493
     *
494
     * Here are some examples:
495
     *
496
     * ```php
497
     * // SELECT * FROM  `user` `u`, `profile`;
498
     * $query = (new \yii\db\Query)->from(['u' => 'user', 'profile']);
499
     *
500
     * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`;
501
     * $subquery = (new \yii\db\Query)->from('user')->where(['active' => true])
502
     * $query = (new \yii\db\Query)->from(['activeusers' => $subquery]);
503
     *
504
     * // subquery can also be a string with plain SQL wrapped in parenthesis
505
     * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`;
506
     * $subquery = "(SELECT * FROM `user` WHERE `active` = 1)";
507
     * $query = (new \yii\db\Query)->from(['activeusers' => $subquery]);
508
     * ```
509
     *
510
     * @return $this the query object itself
511
     */
512 193
    public function from($tables)
513
    {
514 193
        if (!is_array($tables)) {
515 175
            $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
516 175
        }
517 193
        $this->from = $tables;
518 193
        return $this;
519
    }
520
521
    /**
522
     * Sets the WHERE part of the query.
523
     *
524
     * The method requires a `$condition` parameter, and optionally a `$params` parameter
525
     * specifying the values to be bound to the query.
526
     *
527
     * The `$condition` parameter should be either a string (e.g. `'id=1'`) or an array.
528
     *
529
     * @inheritdoc
530
     *
531
     * @param string|array|Expression $condition the conditions that should be put in the WHERE part.
532
     * @param array $params the parameters (name => value) to be bound to the query.
533
     * @return $this the query object itself
534
     * @see andWhere()
535
     * @see orWhere()
536
     * @see QueryInterface::where()
537
     */
538 453
    public function where($condition, $params = [])
539
    {
540 453
        $this->where = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type object<yii\db\Expression>. However, the property $where is declared as type string|array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
541 453
        $this->addParams($params);
542 453
        return $this;
543
    }
544
545
    /**
546
     * Adds an additional WHERE condition to the existing one.
547
     * The new condition and the existing one will be joined using the 'AND' operator.
548
     * @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]]
549
     * on how to specify this parameter.
550
     * @param array $params the parameters (name => value) to be bound to the query.
551
     * @return $this the query object itself
552
     * @see where()
553
     * @see orWhere()
554
     */
555 243
    public function andWhere($condition, $params = [])
556
    {
557 243
        if ($this->where === null) {
558 217
            $this->where = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type object<yii\db\Expression>. However, the property $where is declared as type string|array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
559 217
        } else {
560 65
            $this->where = ['and', $this->where, $condition];
561
        }
562 243
        $this->addParams($params);
563 243
        return $this;
564
    }
565
566
    /**
567
     * Adds an additional WHERE condition to the existing one.
568
     * The new condition and the existing one will be joined using the 'OR' operator.
569
     * @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]]
570
     * on how to specify this parameter.
571
     * @param array $params the parameters (name => value) to be bound to the query.
572
     * @return $this the query object itself
573
     * @see where()
574
     * @see andWhere()
575
     */
576 3
    public function orWhere($condition, $params = [])
577
    {
578 3
        if ($this->where === null) {
579
            $this->where = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type object<yii\db\Expression>. However, the property $where is declared as type string|array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
580
        } else {
581 3
            $this->where = ['or', $this->where, $condition];
582
        }
583 3
        $this->addParams($params);
584 3
        return $this;
585
    }
586
587
    /**
588
     * Adds a filtering condition for a specific column and allow the user to choose a filter operator.
589
     *
590
     * It adds an additional WHERE condition for the given field and determines the comparison operator
591
     * based on the first few characters of the given value.
592
     * The condition is added in the same way as in [[andFilterWhere]] so [[isEmpty()|empty values]] are ignored.
593
     * The new condition and the existing one will be joined using the 'AND' operator.
594
     *
595
     * The comparison operator is intelligently determined based on the first few characters in the given value.
596
     * In particular, it recognizes the following operators if they appear as the leading characters in the given value:
597
     *
598
     * - `<`: the column must be less than the given value.
599
     * - `>`: the column must be greater than the given value.
600
     * - `<=`: the column must be less than or equal to the given value.
601
     * - `>=`: the column must be greater than or equal to the given value.
602
     * - `<>`: the column must not be the same as the given value.
603
     * - `=`: the column must be equal to the given value.
604
     * - If none of the above operators is detected, the `$defaultOperator` will be used.
605
     *
606
     * @param string $name the column name.
607
     * @param string $value the column value optionally prepended with the comparison operator.
608
     * @param string $defaultOperator The operator to use, when no operator is given in `$value`.
609
     * Defaults to `=`, performing an exact match.
610
     * @return $this The query object itself
611
     * @since 2.0.8
612
     */
613 3
    public function andFilterCompare($name, $value, $defaultOperator = '=')
614
    {
615 3
        if (preg_match('/^(<>|>=|>|<=|<|=)/', $value, $matches)) {
616 3
            $operator = $matches[1];
617 3
            $value = substr($value, strlen($operator));
618 3
        } else {
619 3
            $operator = $defaultOperator;
620
        }
621 3
        return $this->andFilterWhere([$operator, $name, $value]);
622
    }
623
624
    /**
625
     * Appends a JOIN part to the query.
626
     * The first parameter specifies what type of join it is.
627
     * @param string $type the type of join, such as INNER JOIN, LEFT JOIN.
628
     * @param string|array $table the table to be joined.
629
     *
630
     * Use a string to represent the name of the table to be joined.
631
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
632
     * The method will automatically quote the table name unless it contains some parenthesis
633
     * (which means the table is given as a sub-query or DB expression).
634
     *
635
     * Use an array to represent joining with a sub-query. The array must contain only one element.
636
     * The value must be a [[Query]] object representing the sub-query while the corresponding key
637
     * represents the alias for the sub-query.
638
     *
639
     * @param string|array $on the join condition that should appear in the ON part.
640
     * Please refer to [[where()]] on how to specify this parameter.
641
     *
642
     * Note that the array format of [[where()]] is designed to match columns to values instead of columns to columns, so
643
     * the following would **not** work as expected: `['post.author_id' => 'user.id']`, it would
644
     * match the `post.author_id` column value against the string `'user.id'`.
645
     * It is recommended to use the string syntax here which is more suited for a join:
646
     *
647
     * ```php
648
     * 'post.author_id = user.id'
649
     * ```
650
     *
651
     * @param array $params the parameters (name => value) to be bound to the query.
652
     * @return $this the query object itself
653
     */
654 36
    public function join($type, $table, $on = '', $params = [])
655
    {
656 36
        $this->join[] = [$type, $table, $on];
657 36
        return $this->addParams($params);
658
    }
659
660
    /**
661
     * Appends an INNER JOIN part to the query.
662
     * @param string|array $table the table to be joined.
663
     *
664
     * Use a string to represent the name of the table to be joined.
665
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
666
     * The method will automatically quote the table name unless it contains some parenthesis
667
     * (which means the table is given as a sub-query or DB expression).
668
     *
669
     * Use an array to represent joining with a sub-query. The array must contain only one element.
670
     * The value must be a [[Query]] object representing the sub-query while the corresponding key
671
     * represents the alias for the sub-query.
672
     *
673
     * @param string|array $on the join condition that should appear in the ON part.
674
     * Please refer to [[join()]] on how to specify this parameter.
675
     * @param array $params the parameters (name => value) to be bound to the query.
676
     * @return $this the query object itself
677
     */
678
    public function innerJoin($table, $on = '', $params = [])
679
    {
680
        $this->join[] = ['INNER JOIN', $table, $on];
681
        return $this->addParams($params);
682
    }
683
684
    /**
685
     * Appends a LEFT OUTER JOIN part to the query.
686
     * @param string|array $table the table to be joined.
687
     *
688
     * Use a string to represent the name of the table to be joined.
689
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
690
     * The method will automatically quote the table name unless it contains some parenthesis
691
     * (which means the table is given as a sub-query or DB expression).
692
     *
693
     * Use an array to represent joining with a sub-query. The array must contain only one element.
694
     * The value must be a [[Query]] object representing the sub-query while the corresponding key
695
     * represents the alias for the sub-query.
696
     *
697
     * @param string|array $on the join condition that should appear in the ON part.
698
     * Please refer to [[join()]] on how to specify this parameter.
699
     * @param array $params the parameters (name => value) to be bound to the query
700
     * @return $this the query object itself
701
     */
702 3
    public function leftJoin($table, $on = '', $params = [])
703
    {
704 3
        $this->join[] = ['LEFT JOIN', $table, $on];
705 3
        return $this->addParams($params);
706
    }
707
708
    /**
709
     * Appends a RIGHT OUTER JOIN part to the query.
710
     * @param string|array $table the table to be joined.
711
     *
712
     * Use a string to represent the name of the table to be joined.
713
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
714
     * The method will automatically quote the table name unless it contains some parenthesis
715
     * (which means the table is given as a sub-query or DB expression).
716
     *
717
     * Use an array to represent joining with a sub-query. The array must contain only one element.
718
     * The value must be a [[Query]] object representing the sub-query while the corresponding key
719
     * represents the alias for the sub-query.
720
     *
721
     * @param string|array $on the join condition that should appear in the ON part.
722
     * Please refer to [[join()]] on how to specify this parameter.
723
     * @param array $params the parameters (name => value) to be bound to the query
724
     * @return $this the query object itself
725
     */
726
    public function rightJoin($table, $on = '', $params = [])
727
    {
728
        $this->join[] = ['RIGHT JOIN', $table, $on];
729
        return $this->addParams($params);
730
    }
731
732
    /**
733
     * Sets the GROUP BY part of the query.
734
     * @param string|array|Expression $columns the columns to be grouped by.
735
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
736
     * The method will automatically quote the column names unless a column contains some parenthesis
737
     * (which means the column contains a DB expression).
738
     *
739
     * Note that if your group-by is an expression containing commas, you should always use an array
740
     * to represent the group-by information. Otherwise, the method will not be able to correctly determine
741
     * the group-by columns.
742
     *
743
     * Since version 2.0.7, an [[Expression]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
744
     * @return $this the query object itself
745
     * @see addGroupBy()
746
     */
747 12
    public function groupBy($columns)
748
    {
749 12
        if ($columns instanceof Expression) {
750 3
            $columns = [$columns];
751 12
        } elseif (!is_array($columns)) {
752 12
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
753 12
        }
754 12
        $this->groupBy = $columns;
755 12
        return $this;
756
    }
757
758
    /**
759
     * Adds additional group-by columns to the existing ones.
760
     * @param string|array $columns additional columns to be grouped by.
761
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
762
     * The method will automatically quote the column names unless a column contains some parenthesis
763
     * (which means the column contains a DB expression).
764
     *
765
     * Note that if your group-by is an expression containing commas, you should always use an array
766
     * to represent the group-by information. Otherwise, the method will not be able to correctly determine
767
     * the group-by columns.
768
     *
769
     * Since version 2.0.7, an [[Expression]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
770
     * @return $this the query object itself
771
     * @see groupBy()
772
     */
773 3
    public function addGroupBy($columns)
774
    {
775 3
        if ($columns instanceof Expression) {
776
            $columns = [$columns];
777 3
        } elseif (!is_array($columns)) {
778 3
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
779 3
        }
780 3
        if ($this->groupBy === null) {
781
            $this->groupBy = $columns;
782
        } else {
783 3
            $this->groupBy = array_merge($this->groupBy, $columns);
784
        }
785 3
        return $this;
786
    }
787
788
    /**
789
     * Sets the HAVING part of the query.
790
     * @param string|array|Expression $condition the conditions to be put after HAVING.
791
     * Please refer to [[where()]] on how to specify this parameter.
792
     * @param array $params the parameters (name => value) to be bound to the query.
793
     * @return $this the query object itself
794
     * @see andHaving()
795
     * @see orHaving()
796
     */
797 4
    public function having($condition, $params = [])
798
    {
799 4
        $this->having = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type object<yii\db\Expression>. However, the property $having is declared as type string|array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
800 4
        $this->addParams($params);
801 4
        return $this;
802
    }
803
804
    /**
805
     * Adds an additional HAVING condition to the existing one.
806
     * The new condition and the existing one will be joined using the 'AND' operator.
807
     * @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]]
808
     * on how to specify this parameter.
809
     * @param array $params the parameters (name => value) to be bound to the query.
810
     * @return $this the query object itself
811
     * @see having()
812
     * @see orHaving()
813
     */
814 3
    public function andHaving($condition, $params = [])
815
    {
816 3
        if ($this->having === null) {
817
            $this->having = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type object<yii\db\Expression>. However, the property $having is declared as type string|array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
818
        } else {
819 3
            $this->having = ['and', $this->having, $condition];
820
        }
821 3
        $this->addParams($params);
822 3
        return $this;
823
    }
824
825
    /**
826
     * Adds an additional HAVING condition to the existing one.
827
     * The new condition and the existing one will be joined using the 'OR' operator.
828
     * @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]]
829
     * on how to specify this parameter.
830
     * @param array $params the parameters (name => value) to be bound to the query.
831
     * @return $this the query object itself
832
     * @see having()
833
     * @see andHaving()
834
     */
835 3
    public function orHaving($condition, $params = [])
836
    {
837 3
        if ($this->having === null) {
838
            $this->having = $condition;
0 ignored issues
show
Documentation Bug introduced by
It seems like $condition can also be of type object<yii\db\Expression>. However, the property $having is declared as type string|array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
839
        } else {
840 3
            $this->having = ['or', $this->having, $condition];
841
        }
842 3
        $this->addParams($params);
843 3
        return $this;
844
    }
845
846
    /**
847
     * Appends a SQL statement using UNION operator.
848
     * @param string|Query $sql the SQL statement to be appended using UNION
849
     * @param boolean $all TRUE if using UNION ALL and FALSE if using UNION
850
     * @return $this the query object itself
851
     */
852 10
    public function union($sql, $all = false)
853
    {
854 10
        $this->union[] = ['query' => $sql, 'all' => $all];
855 10
        return $this;
856
    }
857
858
    /**
859
     * Sets the parameters to be bound to the query.
860
     * @param array $params list of query parameter values indexed by parameter placeholders.
861
     * For example, `[':name' => 'Dan', ':age' => 31]`.
862
     * @return $this the query object itself
863
     * @see addParams()
864
     */
865 6
    public function params($params)
866
    {
867 6
        $this->params = $params;
868 6
        return $this;
869
    }
870
871
    /**
872
     * Adds additional parameters to be bound to the query.
873
     * @param array $params list of query parameter values indexed by parameter placeholders.
874
     * For example, `[':name' => 'Dan', ':age' => 31]`.
875
     * @return $this the query object itself
876
     * @see params()
877
     */
878 623
    public function addParams($params)
879
    {
880 623
        if (!empty($params)) {
881 29
            if (empty($this->params)) {
882 29
                $this->params = $params;
883 29
            } else {
884 6
                foreach ($params as $name => $value) {
885 6
                    if (is_int($name)) {
886
                        $this->params[] = $value;
887
                    } else {
888 6
                        $this->params[$name] = $value;
889
                    }
890 6
                }
891
            }
892 29
        }
893 623
        return $this;
894
    }
895
896
    /**
897
     * Creates a new Query object and copies its property values from an existing one.
898
     * The properties being copies are the ones to be used by query builders.
899
     * @param Query $from the source query object
900
     * @return Query the new Query object
901
     */
902 274
    public static function create($from)
903
    {
904 274
        return new self([
905 274
            'where' => $from->where,
906 274
            'limit' => $from->limit,
907 274
            'offset' => $from->offset,
908 274
            'orderBy' => $from->orderBy,
909 274
            'indexBy' => $from->indexBy,
910 274
            'select' => $from->select,
911 274
            'selectOption' => $from->selectOption,
912 274
            'distinct' => $from->distinct,
913 274
            'from' => $from->from,
914 274
            'groupBy' => $from->groupBy,
915 274
            'join' => $from->join,
916 274
            'having' => $from->having,
917 274
            'union' => $from->union,
918 274
            'params' => $from->params,
919 274
        ]);
920
    }
921
}
922