Passed
Pull Request — master (#20134)
by Wilmer
10:17 queued 29s
created

Query::from()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 4
nop 1
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @link https://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license https://www.yiiframework.com/license/
7
 */
8
9
namespace yii\db;
10
11
use Yii;
12
use yii\base\Component;
13
use yii\base\InvalidArgumentException;
14
use yii\helpers\ArrayHelper;
15
use yii\base\InvalidConfigException;
16
17
/**
18
 * Query represents a SELECT SQL statement in a way that is independent of DBMS.
19
 *
20
 * Query provides a set of methods to facilitate the specification of different clauses
21
 * in a SELECT statement. These methods can be chained together.
22
 *
23
 * By calling [[createCommand()]], we can get a [[Command]] instance which can be further
24
 * used to perform/execute the DB query against a database.
25
 *
26
 * For example,
27
 *
28
 * ```php
29
 * $query = new Query;
30
 * // compose the query
31
 * $query->select('id, name')
32
 *     ->from('user')
33
 *     ->limit(10);
34
 * // build and execute the query
35
 * $rows = $query->all();
36
 * // alternatively, you can create DB command and execute it
37
 * $command = $query->createCommand();
38
 * // $command->sql returns the actual SQL
39
 * $rows = $command->queryAll();
40
 * ```
41
 *
42
 * Query internally uses the [[QueryBuilder]] class to generate the SQL statement.
43
 *
44
 * 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).
45
 *
46
 * @property-read string[] $tablesUsedInFrom Table names indexed by aliases.
47
 *
48
 * @author Qiang Xue <[email protected]>
49
 * @author Carsten Brandt <[email protected]>
50
 * @since 2.0
51
 */
52
class Query extends Component implements QueryInterface, ExpressionInterface
53
{
54
    use QueryTrait;
55
56
    /**
57
     * @var array|null the columns being selected. For example, `['id', 'name']`.
58
     * This is used to construct the SELECT clause in a SQL statement. If not set, it means selecting all columns.
59
     * @see select()
60
     */
61
    public $select;
62
    /**
63
     * @var string|null additional option that should be appended to the 'SELECT' keyword. For example,
64
     * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
65
     */
66
    public $selectOption;
67
    /**
68
     * @var bool whether to select distinct rows of data only. If this is set true,
69
     * the SELECT clause would be changed to SELECT DISTINCT.
70
     */
71
    public $distinct = false;
72
    /**
73
     * @var array|null the table(s) to be selected from. For example, `['user', 'post']`.
74
     * This is used to construct the FROM clause in a SQL statement.
75
     * @see from()
76
     */
77
    public $from;
78
    /**
79
     * @var array|null how to group the query results. For example, `['company', 'department']`.
80
     * This is used to construct the GROUP BY clause in a SQL statement.
81
     */
82
    public $groupBy;
83
    /**
84
     * @var array|null how to join with other tables. Each array element represents the specification
85
     * of one join which has the following structure:
86
     *
87
     * ```php
88
     * [$joinType, $tableName, $joinCondition]
89
     * ```
90
     *
91
     * For example,
92
     *
93
     * ```php
94
     * [
95
     *     ['INNER JOIN', 'user', 'user.id = author_id'],
96
     *     ['LEFT JOIN', 'team', 'team.id = team_id'],
97
     * ]
98
     * ```
99
     */
100
    public $join;
101
    /**
102
     * @var string|array|ExpressionInterface|null the condition to be applied in the GROUP BY clause.
103
     * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
104
     */
105
    public $having;
106
    /**
107
     * @var array|null this is used to construct the UNION clause(s) in a SQL statement.
108
     * Each array element is an array of the following structure:
109
     *
110
     * - `query`: either a string or a [[Query]] object representing a query
111
     * - `all`: boolean, whether it should be `UNION ALL` or `UNION`
112
     */
113
    public $union;
114
    /**
115
     * @var array|null this is used to construct the WITH section in a SQL query.
116
     * Each array element is an array of the following structure:
117
     *
118
     * - `query`: either a string or a [[Query]] object representing a query
119
     * - `alias`: string, alias of query for further usage
120
     * - `recursive`: boolean, whether it should be `WITH RECURSIVE` or `WITH`
121
     * @see withQuery()
122
     * @since 2.0.35
123
     */
124
    public $withQueries;
125
    /**
126
     * @var array|null list of query parameter values indexed by parameter placeholders.
127
     * For example, `[':name' => 'Dan', ':age' => 31]`.
128
     */
129
    public $params = [];
130
    /**
131
     * @var int|bool|null the default number of seconds that query results can remain valid in cache.
132
     * Use 0 to indicate that the cached data will never expire.
133
     * Use a negative number to indicate that query cache should not be used.
134
     * Use boolean `true` to indicate that [[Connection::queryCacheDuration]] should be used.
135
     * @see cache()
136
     * @since 2.0.14
137
     */
138
    public $queryCacheDuration;
139
    /**
140
     * @var \yii\caching\Dependency|null the dependency to be associated with the cached query result for this query
141
     * @see cache()
142
     * @since 2.0.14
143
     */
144
    public $queryCacheDependency;
145
146
147
    /**
148
     * Creates a DB command that can be used to execute this query.
149
     * @param Connection|null $db the database connection used to generate the SQL statement.
150
     * If this parameter is not given, the `db` application component will be used.
151
     * @return Command the created DB command instance.
152
     */
153 394
    public function createCommand($db = null)
154
    {
155 394
        if ($db === null) {
156 43
            $db = Yii::$app->getDb();
157
        }
158 394
        list($sql, $params) = $db->getQueryBuilder()->build($this);
159
160 394
        $command = $db->createCommand($sql, $params);
161 394
        $this->setCommandCache($command);
162
163 394
        return $command;
164
    }
165
166
    /**
167
     * Prepares for building SQL.
168
     * This method is called by [[QueryBuilder]] when it starts to build SQL from a query object.
169
     * You may override this method to do some final preparation work when converting a query into a SQL statement.
170
     * @param QueryBuilder $builder
171
     * @return $this a prepared query instance which will be used by [[QueryBuilder]] to build the SQL
172
     */
173 898
    public function prepare($builder)
0 ignored issues
show
Unused Code introduced by
The parameter $builder is not used and could be removed. ( Ignorable by Annotation )

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

173
    public function prepare(/** @scrutinizer ignore-unused */ $builder)

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

Loading history...
174
    {
175 898
        return $this;
176
    }
177
178
    /**
179
     * Starts a batch query.
180
     *
181
     * A batch query supports fetching data in batches, which can keep the memory usage under a limit.
182
     * This method will return a [[BatchQueryResult]] object which implements the [[\Iterator]] interface
183
     * and can be traversed to retrieve the data in batches.
184
     *
185
     * For example,
186
     *
187
     * ```php
188
     * $query = (new Query)->from('user');
189
     * foreach ($query->batch() as $rows) {
190
     *     // $rows is an array of 100 or fewer rows from user table
191
     * }
192
     * ```
193
     *
194
     * @param int $batchSize the number of records to be fetched in each batch.
195
     * @param Connection|null $db the database connection. If not set, the "db" application component will be used.
196
     * @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface
197
     * and can be traversed to retrieve the data in batches.
198
     */
199 12
    public function batch($batchSize = 100, $db = null)
200
    {
201 12
        return Yii::createObject([
202 12
            'class' => BatchQueryResult::className(),
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

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

202
            'class' => /** @scrutinizer ignore-deprecated */ BatchQueryResult::className(),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
203 12
            'query' => $this,
204 12
            'batchSize' => $batchSize,
205 12
            'db' => $db,
206 12
            'each' => false,
207 12
        ]);
208
    }
209
210
    /**
211
     * Starts a batch query and retrieves data row by row.
212
     *
213
     * This method is similar to [[batch()]] except that in each iteration of the result,
214
     * only one row of data is returned. For example,
215
     *
216
     * ```php
217
     * $query = (new Query)->from('user');
218
     * foreach ($query->each() as $row) {
219
     * }
220
     * ```
221
     *
222
     * @param int $batchSize the number of records to be fetched in each batch.
223
     * @param Connection|null $db the database connection. If not set, the "db" application component will be used.
224
     * @return BatchQueryResult the batch query result. It implements the [[\Iterator]] interface
225
     * and can be traversed to retrieve the data in batches.
226
     */
227 3
    public function each($batchSize = 100, $db = null)
228
    {
229 3
        return Yii::createObject([
230 3
            'class' => BatchQueryResult::className(),
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

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

230
            'class' => /** @scrutinizer ignore-deprecated */ BatchQueryResult::className(),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
231 3
            'query' => $this,
232 3
            'batchSize' => $batchSize,
233 3
            'db' => $db,
234 3
            'each' => true,
235 3
        ]);
236
    }
237
238
    /**
239
     * Executes the query and returns all results as an array.
240
     * @param Connection|null $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 the query results. If the query results in nothing, an empty array will be returned.
243
     */
244 498
    public function all($db = null)
245
    {
246 498
        if ($this->emulateExecution) {
247 12
            return [];
248
        }
249
250 492
        $rows = $this->createCommand($db)->queryAll();
251
252 492
        return $this->populate($rows);
253
    }
254
255
    /**
256
     * Converts the raw query results into the format as specified by this query.
257
     * This method is internally used to convert the data fetched from database
258
     * into the format as required by this query.
259
     * @param array $rows the raw query result from database
260
     * @return array the converted query result
261
     */
262 651
    public function populate($rows)
263
    {
264 651
        if ($this->indexBy === null) {
265 642
            return $rows;
266
        }
267 27
        $result = [];
268 27
        foreach ($rows as $row) {
269 27
            $result[ArrayHelper::getValue($row, $this->indexBy)] = $row;
270
        }
271
272 27
        return $result;
273
    }
274
275
    /**
276
     * Executes the query and returns a single row of result.
277
     * @param Connection|null $db the database connection used to generate the SQL statement.
278
     * If this parameter is not given, the `db` application component will be used.
279
     * @return array|bool the first row (in terms of an array) of the query result. False is returned if the query
280
     * results in nothing.
281
     */
282 499
    public function one($db = null)
283
    {
284 499
        if ($this->emulateExecution) {
285 6
            return false;
286
        }
287
288 493
        return $this->createCommand($db)->queryOne();
289
    }
290
291
    /**
292
     * Returns the query result as a scalar value.
293
     * The value returned will be the first column in the first row of the query results.
294
     * @param Connection|null $db the database connection used to generate the SQL statement.
295
     * If this parameter is not given, the `db` application component will be used.
296
     * @return string|int|null|false the value of the first column in the first row of the query result.
297
     * False is returned if the query result is empty.
298
     */
299 36
    public function scalar($db = null)
300
    {
301 36
        if ($this->emulateExecution) {
302 6
            return null;
303
        }
304
305 30
        return $this->createCommand($db)->queryScalar();
306
    }
307
308
    /**
309
     * Executes the query and returns the first column of the result.
310
     * @param Connection|null $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 array the first column of the query result. An empty array is returned if the query results in nothing.
313
     */
314 82
    public function column($db = null)
315
    {
316 82
        if ($this->emulateExecution) {
317 6
            return [];
318
        }
319
320 76
        if ($this->indexBy === null) {
321 70
            return $this->createCommand($db)->queryColumn();
322
        }
323
324 9
        if (is_string($this->indexBy) && is_array($this->select) && count($this->select) === 1) {
325 9
            if (strpos($this->indexBy, '.') === false && count($tables = $this->getTablesUsedInFrom()) > 0) {
326 9
                $this->select[] = key($tables) . '.' . $this->indexBy;
327
            } else {
328 3
                $this->select[] = $this->indexBy;
329
            }
330
        }
331 9
        $rows = $this->createCommand($db)->queryAll();
332 9
        $results = [];
333 9
        $column = null;
334 9
        if (is_string($this->indexBy)) {
335 9
            if (($dotPos = strpos($this->indexBy, '.')) === false) {
336 9
                $column = $this->indexBy;
337
            } else {
338 3
                $column = substr($this->indexBy, $dotPos + 1);
339
            }
340
        }
341 9
        foreach ($rows as $row) {
342 9
            $value = reset($row);
343
344 9
            if ($this->indexBy instanceof \Closure) {
345 3
                $results[call_user_func($this->indexBy, $row)] = $value;
346
            } else {
347 9
                $results[$row[$column]] = $value;
348
            }
349
        }
350
351 9
        return $results;
352
    }
353
354
    /**
355
     * Returns the number of records.
356
     * @param string $q the COUNT expression. Defaults to '*'.
357
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
358
     * @param Connection|null $db the database connection used to generate the SQL statement.
359
     * If this parameter is not given (or null), the `db` application component will be used.
360
     * @return int|string|null number of records. The result may be a string depending on the
361
     * underlying database engine and to support integer values higher than a 32bit PHP integer can handle.
362
     */
363 96
    public function count($q = '*', $db = null)
364
    {
365 96
        if ($this->emulateExecution) {
366 6
            return 0;
367
        }
368
369 96
        return $this->queryScalar("COUNT($q)", $db);
370
    }
371
372
    /**
373
     * Returns the sum of the specified column values.
374
     * @param string $q the column name or expression.
375
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
376
     * @param Connection|null $db the database connection used to generate the SQL statement.
377
     * If this parameter is not given, the `db` application component will be used.
378
     * @return mixed the sum of the specified column values.
379
     */
380 9
    public function sum($q, $db = null)
381
    {
382 9
        if ($this->emulateExecution) {
383 6
            return 0;
384
        }
385
386 3
        return $this->queryScalar("SUM($q)", $db);
387
    }
388
389
    /**
390
     * Returns the average of the specified column values.
391
     * @param string $q the column name or expression.
392
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
393
     * @param Connection|null $db the database connection used to generate the SQL statement.
394
     * If this parameter is not given, the `db` application component will be used.
395
     * @return mixed the average of the specified column values.
396
     */
397 9
    public function average($q, $db = null)
398
    {
399 9
        if ($this->emulateExecution) {
400 6
            return 0;
401
        }
402
403 3
        return $this->queryScalar("AVG($q)", $db);
404
    }
405
406
    /**
407
     * Returns the minimum of the specified column values.
408
     * @param string $q the column name or expression.
409
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
410
     * @param Connection|null $db the database connection used to generate the SQL statement.
411
     * If this parameter is not given, the `db` application component will be used.
412
     * @return mixed the minimum of the specified column values.
413
     */
414 9
    public function min($q, $db = null)
415
    {
416 9
        return $this->queryScalar("MIN($q)", $db);
417
    }
418
419
    /**
420
     * Returns the maximum of the specified column values.
421
     * @param string $q the column name or expression.
422
     * Make sure you properly [quote](guide:db-dao#quoting-table-and-column-names) column names in the expression.
423
     * @param Connection|null $db the database connection used to generate the SQL statement.
424
     * If this parameter is not given, the `db` application component will be used.
425
     * @return mixed the maximum of the specified column values.
426
     */
427 9
    public function max($q, $db = null)
428
    {
429 9
        return $this->queryScalar("MAX($q)", $db);
430
    }
431
432
    /**
433
     * Returns a value indicating whether the query result contains any row of data.
434
     * @param Connection|null $db the database connection used to generate the SQL statement.
435
     * If this parameter is not given, the `db` application component will be used.
436
     * @return bool whether the query result contains any row of data.
437
     */
438 89
    public function exists($db = null)
439
    {
440 89
        if ($this->emulateExecution) {
441 6
            return false;
442
        }
443 83
        $command = $this->createCommand($db);
444 83
        $params = $command->params;
445 83
        $command->setSql($command->db->getQueryBuilder()->selectExists($command->getSql()));
446 83
        $command->bindValues($params);
447 83
        return (bool) $command->queryScalar();
448
    }
449
450
    /**
451
     * Queries a scalar value by setting [[select]] first.
452
     * Restores the value of select to make this query reusable.
453
     * @param string|ExpressionInterface $selectExpression
454
     * @param Connection|null $db the database connection used to execute the query.
455
     * @return bool|string|null
456
     * @throws \Throwable if can't create command
457
     */
458 96
    protected function queryScalar($selectExpression, $db)
459
    {
460 96
        if ($this->emulateExecution) {
461 6
            return null;
462
        }
463
464
        if (
465 96
            !$this->distinct
466 96
            && empty($this->groupBy)
467 96
            && empty($this->having)
468 96
            && empty($this->union)
469
        ) {
470 95
            $select = $this->select;
471 95
            $order = $this->orderBy;
472 95
            $limit = $this->limit;
473 95
            $offset = $this->offset;
474
475 95
            $this->select = [$selectExpression];
476 95
            $this->orderBy = null;
477 95
            $this->limit = null;
478 95
            $this->offset = null;
479
480 95
            $e = null;
481
            try {
482 95
                $command = $this->createCommand($db);
483
            } catch (\Exception $e) {
484
                // throw it later (for PHP < 7.0)
485
            } catch (\Throwable $e) {
486
                // throw it later
487
            }
488
489 95
            $this->select = $select;
490 95
            $this->orderBy = $order;
491 95
            $this->limit = $limit;
492 95
            $this->offset = $offset;
493
494 95
            if ($e !== null) {
495
                throw $e;
496
            }
497
498 95
            return $command->queryScalar();
499
        }
500
501 7
        $command = (new self())
502 7
            ->select([$selectExpression])
503 7
            ->from(['c' => $this])
504 7
            ->createCommand($db);
505 7
        $this->setCommandCache($command);
506
507 7
        return $command->queryScalar();
508
    }
509
510
    /**
511
     * Returns table names used in [[from]] indexed by aliases.
512
     * Both aliases and names are enclosed into {{ and }}.
513
     * @return string[] table names indexed by aliases
514
     * @throws \yii\base\InvalidConfigException
515
     * @since 2.0.12
516
     */
517 120
    public function getTablesUsedInFrom()
518
    {
519 120
        if (empty($this->from)) {
520
            return [];
521
        }
522
523 120
        if (is_array($this->from)) {
0 ignored issues
show
introduced by
The condition is_array($this->from) is always true.
Loading history...
524 84
            $tableNames = $this->from;
525 36
        } elseif (is_string($this->from)) {
526 24
            $tableNames = preg_split('/\s*,\s*/', trim($this->from), -1, PREG_SPLIT_NO_EMPTY);
527 12
        } elseif ($this->from instanceof Expression) {
528 6
            $tableNames = [$this->from];
529
        } else {
530 6
            throw new InvalidConfigException(gettype($this->from) . ' in $from is not supported.');
531
        }
532
533 114
        return $this->cleanUpTableNames($tableNames);
534
    }
535
536
    /**
537
     * Clean up table names and aliases
538
     * Both aliases and names are enclosed into {{ and }}.
539
     * @param array $tableNames non-empty array
540
     * @return string[] table names indexed by aliases
541
     * @since 2.0.14
542
     */
543 287
    protected function cleanUpTableNames($tableNames)
544
    {
545 287
        $cleanedUpTableNames = [];
546 287
        foreach ($tableNames as $alias => $tableName) {
547 287
            if (is_string($tableName) && !is_string($alias)) {
548 227
                $pattern = <<<PATTERN
549
~
550
^
551
\s*
552
(
553
(?:['"`\[]|{{)
554
.*?
555
(?:['"`\]]|}})
556
|
557
\(.*?\)
558
|
559
.*?
560
)
561
(?:
562
(?:
563
    \s+
564
    (?:as)?
565
    \s*
566
)
567
(
568
   (?:['"`\[]|{{)
569
    .*?
570
    (?:['"`\]]|}})
571
    |
572
    .*?
573
)
574
)?
575
\s*
576
$
577
~iux
578 227
PATTERN;
579 227
                if (preg_match($pattern, $tableName, $matches)) {
580 227
                    if (isset($matches[2])) {
581 18
                        list(, $tableName, $alias) = $matches;
582
                    } else {
583 221
                        $tableName = $alias = $matches[1];
584
                    }
585
                }
586
            }
587
588
589 287
            if ($tableName instanceof Expression) {
590 12
                if (!is_string($alias)) {
591 6
                    throw new InvalidArgumentException('To use Expression in from() method, pass it in array format with alias.');
592
                }
593 6
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $tableName;
594 275
            } elseif ($tableName instanceof self) {
595 6
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $tableName;
596
            } else {
597 269
                $cleanedUpTableNames[$this->ensureNameQuoted($alias)] = $this->ensureNameQuoted($tableName);
598
            }
599
        }
600
601 281
        return $cleanedUpTableNames;
602
    }
603
604
    /**
605
     * Ensures name is wrapped with {{ and }}
606
     * @param string $name
607
     * @return string
608
     */
609 281
    private function ensureNameQuoted($name)
610
    {
611 281
        $name = str_replace(["'", '"', '`', '[', ']'], '', $name);
612 281
        if ($name && !preg_match('/^{{.*}}$/', $name)) {
613 269
            return '{{' . $name . '}}';
614
        }
615
616 30
        return $name;
617
    }
618
619
    /**
620
     * Sets the SELECT part of the query.
621
     * @param string|array|ExpressionInterface $columns the columns to be selected.
622
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
623
     * Columns can be prefixed with table names (e.g. "user.id") and/or contain column aliases (e.g. "user.id AS user_id").
624
     * The method will automatically quote the column names unless a column contains some parenthesis
625
     * (which means the column contains a DB expression). A DB expression may also be passed in form of
626
     * an [[ExpressionInterface]] object.
627
     *
628
     * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should
629
     * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts.
630
     *
631
     * When the columns are specified as an array, you may also use array keys as the column aliases (if a column
632
     * does not need alias, do not use a string key).
633
     *
634
     * Starting from version 2.0.1, you may also select sub-queries as columns by specifying each such column
635
     * as a `Query` instance representing the sub-query.
636
     *
637
     * @param string|null $option additional option that should be appended to the 'SELECT' keyword. For example,
638
     * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
639
     * @return $this the query object itself
640
     */
641 433
    public function select($columns, $option = null)
642
    {
643 433
        $this->select = $this->normalizeSelect($columns);
644 433
        $this->selectOption = $option;
645 433
        return $this;
646
    }
647
648
    /**
649
     * Add more columns to the SELECT part of the query.
650
     *
651
     * Note, that if [[select]] has not been specified before, you should include `*` explicitly
652
     * if you want to select all remaining columns too:
653
     *
654
     * ```php
655
     * $query->addSelect(["*", "CONCAT(first_name, ' ', last_name) AS full_name"])->one();
656
     * ```
657
     *
658
     * @param string|array|ExpressionInterface $columns the columns to add to the select. See [[select()]] for more
659
     * details about the format of this parameter.
660
     * @return $this the query object itself
661
     * @see select()
662
     */
663 9
    public function addSelect($columns)
664
    {
665 9
        if ($this->select === null) {
666 3
            return $this->select($columns);
667
        }
668 9
        if (!is_array($this->select)) {
0 ignored issues
show
introduced by
The condition is_array($this->select) is always true.
Loading history...
669
            $this->select = $this->normalizeSelect($this->select);
670
        }
671 9
        $this->select = array_merge($this->select, $this->normalizeSelect($columns));
672
673 9
        return $this;
674
    }
675
676
    /**
677
     * Normalizes the SELECT columns passed to [[select()]] or [[addSelect()]].
678
     *
679
     * @param string|array|ExpressionInterface $columns
680
     * @return array
681
     * @since 2.0.21
682
     */
683 433
    protected function normalizeSelect($columns)
684
    {
685 433
        if ($columns instanceof ExpressionInterface) {
686 3
            $columns = [$columns];
687 433
        } elseif (!is_array($columns)) {
688 121
            $columns = preg_split('/\s*,\s*/', trim((string)$columns), -1, PREG_SPLIT_NO_EMPTY);
689
        }
690 433
        $select = [];
691 433
        foreach ($columns as $columnAlias => $columnDefinition) {
692 430
            if (is_string($columnAlias)) {
693
                // Already in the normalized format, good for them
694 56
                $select[$columnAlias] = $columnDefinition;
695 56
                continue;
696
            }
697 425
            if (is_string($columnDefinition)) {
698
                if (
699 422
                    preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $columnDefinition, $matches) &&
700 422
                    !preg_match('/^\d+$/', $matches[2]) &&
701 422
                    strpos($matches[2], '.') === false
702
                ) {
703
                    // Using "columnName as alias" or "columnName alias" syntax
704 21
                    $select[$matches[2]] = $matches[1];
705 21
                    continue;
706
                }
707 416
                if (strpos($columnDefinition, '(') === false) {
708
                    // Normal column name, just alias it to itself to ensure it's not selected twice
709 406
                    $select[$columnDefinition] = $columnDefinition;
710 406
                    continue;
711
                }
712
            }
713
            // Either a string calling a function, DB expression, or sub-query
714 36
            $select[] = $columnDefinition;
715
        }
716 433
        return $select;
717
    }
718
719
    /**
720
     * Returns unique column names excluding duplicates.
721
     * Columns to be removed:
722
     * - if column definition already present in SELECT part with same alias
723
     * - if column definition without alias already present in SELECT part without alias too
724
     * @param array $columns the columns to be merged to the select.
725
     * @since 2.0.14
726
     * @deprecated in 2.0.21
727
     */
728
    protected function getUniqueColumns($columns)
729
    {
730
        $unaliasedColumns = $this->getUnaliasedColumnsFromSelect();
0 ignored issues
show
Deprecated Code introduced by
The function yii\db\Query::getUnaliasedColumnsFromSelect() has been deprecated: in 2.0.21 ( Ignorable by Annotation )

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

730
        $unaliasedColumns = /** @scrutinizer ignore-deprecated */ $this->getUnaliasedColumnsFromSelect();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
731
732
        $result = [];
733
        foreach ($columns as $columnAlias => $columnDefinition) {
734
            if (!$columnDefinition instanceof Query) {
735
                if (is_string($columnAlias)) {
736
                    $existsInSelect = isset($this->select[$columnAlias]) && $this->select[$columnAlias] === $columnDefinition;
737
                    if ($existsInSelect) {
738
                        continue;
739
                    }
740
                } elseif (is_int($columnAlias)) {
741
                    $existsInSelect = in_array($columnDefinition, $unaliasedColumns, true);
742
                    $existsInResultSet = in_array($columnDefinition, $result, true);
743
                    if ($existsInSelect || $existsInResultSet) {
744
                        continue;
745
                    }
746
                }
747
            }
748
749
            $result[$columnAlias] = $columnDefinition;
750
        }
751
        return $result;
752
    }
753
754
    /**
755
     * @return array List of columns without aliases from SELECT statement.
756
     * @since 2.0.14
757
     * @deprecated in 2.0.21
758
     */
759
    protected function getUnaliasedColumnsFromSelect()
760
    {
761
        $result = [];
762
        if (is_array($this->select)) {
763
            foreach ($this->select as $name => $value) {
764
                if (is_int($name)) {
765
                    $result[] = $value;
766
                }
767
            }
768
        }
769
        return array_unique($result);
770
    }
771
772
    /**
773
     * Sets the value indicating whether to SELECT DISTINCT or not.
774
     * @param bool $value whether to SELECT DISTINCT or not.
775
     * @return $this the query object itself
776
     */
777 9
    public function distinct($value = true)
778
    {
779 9
        $this->distinct = $value;
780 9
        return $this;
781
    }
782
783
    /**
784
     * Sets the FROM part of the query.
785
     * @param string|array|ExpressionInterface $tables the table(s) to be selected from. This can be either a string (e.g. `'user'`)
786
     * or an array (e.g. `['user', 'profile']`) specifying one or several table names.
787
     * Table names can contain schema prefixes (e.g. `'public.user'`) and/or table aliases (e.g. `'user u'`).
788
     * The method will automatically quote the table names unless it contains some parenthesis
789
     * (which means the table is given as a sub-query or DB expression).
790
     *
791
     * When the tables are specified as an array, you may also use the array keys as the table aliases
792
     * (if a table does not need alias, do not use a string key).
793
     *
794
     * Use a Query object to represent a sub-query. In this case, the corresponding array key will be used
795
     * as the alias for the sub-query.
796
     *
797
     * To specify the `FROM` part in plain SQL, you may pass an instance of [[ExpressionInterface]].
798
     *
799
     * Here are some examples:
800
     *
801
     * ```php
802
     * // SELECT * FROM  `user` `u`, `profile`;
803
     * $query = (new \yii\db\Query)->from(['u' => 'user', 'profile']);
804
     *
805
     * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`;
806
     * $subquery = (new \yii\db\Query)->from('user')->where(['active' => true])
807
     * $query = (new \yii\db\Query)->from(['activeusers' => $subquery]);
808
     *
809
     * // subquery can also be a string with plain SQL wrapped in parenthesis
810
     * // SELECT * FROM (SELECT * FROM `user` WHERE `active` = 1) `activeusers`;
811
     * $subquery = "(SELECT * FROM `user` WHERE `active` = 1)";
812
     * $query = (new \yii\db\Query)->from(['activeusers' => $subquery]);
813
     * ```
814
     *
815
     * @return $this the query object itself
816
     */
817 491
    public function from($tables)
818
    {
819 491
        if ($tables instanceof ExpressionInterface) {
820 6
            $tables = [$tables];
821
        }
822 491
        if (is_string($tables)) {
823 435
            $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
824
        }
825 491
        $this->from = $tables;
0 ignored issues
show
Documentation Bug introduced by
It seems like $tables can also be of type string. However, the property $from is declared as type array|null. 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...
826 491
        return $this;
827
    }
828
829
    /**
830
     * Sets the WHERE part of the query.
831
     *
832
     * The method requires a `$condition` parameter, and optionally a `$params` parameter
833
     * specifying the values to be bound to the query.
834
     *
835
     * The `$condition` parameter should be either a string (e.g. `'id=1'`) or an array.
836
     *
837
     * {@inheritdoc}
838
     *
839
     * @param string|array|ExpressionInterface $condition the conditions that should be put in the WHERE part.
840
     * @param array $params the parameters (name => value) to be bound to the query.
841
     * @return $this the query object itself
842
     * @see andWhere()
843
     * @see orWhere()
844
     * @see QueryInterface::where()
845
     */
846 882
    public function where($condition, $params = [])
847
    {
848 882
        $this->where = $condition;
849 882
        $this->addParams($params);
850 882
        return $this;
851
    }
852
853
    /**
854
     * Adds an additional WHERE condition to the existing one.
855
     * The new condition and the existing one will be joined using the `AND` operator.
856
     * @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
857
     * on how to specify this parameter.
858
     * @param array $params the parameters (name => value) to be bound to the query.
859
     * @return $this the query object itself
860
     * @see where()
861
     * @see orWhere()
862
     */
863 459
    public function andWhere($condition, $params = [])
864
    {
865 459
        if ($this->where === null) {
866 396
            $this->where = $condition;
867 135
        } elseif (is_array($this->where) && isset($this->where[0]) && strcasecmp($this->where[0], 'and') === 0) {
868 43
            $this->where[] = $condition;
869
        } else {
870 135
            $this->where = ['and', $this->where, $condition];
871
        }
872 459
        $this->addParams($params);
873 459
        return $this;
874
    }
875
876
    /**
877
     * Adds an additional WHERE condition to the existing one.
878
     * The new condition and the existing one will be joined using the `OR` operator.
879
     * @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
880
     * on how to specify this parameter.
881
     * @param array $params the parameters (name => value) to be bound to the query.
882
     * @return $this the query object itself
883
     * @see where()
884
     * @see andWhere()
885
     */
886 8
    public function orWhere($condition, $params = [])
887
    {
888 8
        if ($this->where === null) {
889
            $this->where = $condition;
890
        } else {
891 8
            $this->where = ['or', $this->where, $condition];
892
        }
893 8
        $this->addParams($params);
894 8
        return $this;
895
    }
896
897
    /**
898
     * Adds a filtering condition for a specific column and allow the user to choose a filter operator.
899
     *
900
     * It adds an additional WHERE condition for the given field and determines the comparison operator
901
     * based on the first few characters of the given value.
902
     * The condition is added in the same way as in [[andFilterWhere]] so [[isEmpty()|empty values]] are ignored.
903
     * The new condition and the existing one will be joined using the `AND` operator.
904
     *
905
     * The comparison operator is intelligently determined based on the first few characters in the given value.
906
     * In particular, it recognizes the following operators if they appear as the leading characters in the given value:
907
     *
908
     * - `<`: the column must be less than the given value.
909
     * - `>`: the column must be greater than the given value.
910
     * - `<=`: the column must be less than or equal to the given value.
911
     * - `>=`: the column must be greater than or equal to the given value.
912
     * - `<>`: the column must not be the same as the given value.
913
     * - `=`: the column must be equal to the given value.
914
     * - If none of the above operators is detected, the `$defaultOperator` will be used.
915
     *
916
     * @param string $name the column name.
917
     * @param string $value the column value optionally prepended with the comparison operator.
918
     * @param string $defaultOperator The operator to use, when no operator is given in `$value`.
919
     * Defaults to `=`, performing an exact match.
920
     * @return $this The query object itself
921
     * @since 2.0.8
922
     */
923 3
    public function andFilterCompare($name, $value, $defaultOperator = '=')
924
    {
925 3
        if (preg_match('/^(<>|>=|>|<=|<|=)/', (string)$value, $matches)) {
926 3
            $operator = $matches[1];
927 3
            $value = substr($value, strlen($operator));
928
        } else {
929 3
            $operator = $defaultOperator;
930
        }
931
932 3
        return $this->andFilterWhere([$operator, $name, $value]);
933
    }
934
935
    /**
936
     * Appends a JOIN part to the query.
937
     * The first parameter specifies what type of join it is.
938
     * @param string $type the type of join, such as INNER JOIN, LEFT JOIN.
939
     * @param string|array $table the table or sub-query to be joined.
940
     *
941
     * Use a string to represent the name of the table to be joined.
942
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
943
     * The method will automatically quote the table name unless it contains some parenthesis
944
     * (which means the table is given as a sub-query or DB expression).
945
     *
946
     * You may also specify the table as an array with one element, using the array key as the table alias
947
     * (e.g. ['u' => 'user']).
948
     *
949
     * To join a sub-query, use an array with one element, with the value set to a [[Query]] object
950
     * representing the sub-query, and the corresponding key representing the alias.
951
     *
952
     * @param string|array $on the join condition that should appear in the ON part.
953
     * Please refer to [[where()]] on how to specify this parameter.
954
     *
955
     * Note that the array format of [[where()]] is designed to match columns to values instead of columns to columns, so
956
     * the following would **not** work as expected: `['post.author_id' => 'user.id']`, it would
957
     * match the `post.author_id` column value against the string `'user.id'`.
958
     * It is recommended to use the string syntax here which is more suited for a join:
959
     *
960
     * ```php
961
     * 'post.author_id = user.id'
962
     * ```
963
     *
964
     * @param array $params the parameters (name => value) to be bound to the query.
965
     * @return $this the query object itself
966
     */
967 90
    public function join($type, $table, $on = '', $params = [])
968
    {
969 90
        $this->join[] = [$type, $table, $on];
970 90
        return $this->addParams($params);
971
    }
972
973
    /**
974
     * Appends an INNER JOIN part to the query.
975
     * @param string|array $table the table or sub-query to be joined.
976
     *
977
     * Use a string to represent the name of the table to be joined.
978
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
979
     * The method will automatically quote the table name unless it contains some parenthesis
980
     * (which means the table is given as a sub-query or DB expression).
981
     *
982
     * You may also specify the table as an array with one element, using the array key as the table alias
983
     * (e.g. ['u' => 'user']).
984
     *
985
     * To join a sub-query, use an array with one element, with the value set to a [[Query]] object
986
     * representing the sub-query, and the corresponding key representing the alias.
987
     *
988
     * @param string|array $on the join condition that should appear in the ON part.
989
     * Please refer to [[join()]] on how to specify this parameter.
990
     * @param array $params the parameters (name => value) to be bound to the query.
991
     * @return $this the query object itself
992
     */
993 6
    public function innerJoin($table, $on = '', $params = [])
994
    {
995 6
        $this->join[] = ['INNER JOIN', $table, $on];
996 6
        return $this->addParams($params);
997
    }
998
999
    /**
1000
     * Appends a LEFT OUTER JOIN part to the query.
1001
     * @param string|array $table the table or sub-query to be joined.
1002
     *
1003
     * Use a string to represent the name of the table to be joined.
1004
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
1005
     * The method will automatically quote the table name unless it contains some parenthesis
1006
     * (which means the table is given as a sub-query or DB expression).
1007
     *
1008
     * You may also specify the table as an array with one element, using the array key as the table alias
1009
     * (e.g. ['u' => 'user']).
1010
     *
1011
     * To join a sub-query, use an array with one element, with the value set to a [[Query]] object
1012
     * representing the sub-query, and the corresponding key representing the alias.
1013
     *
1014
     * @param string|array $on the join condition that should appear in the ON part.
1015
     * Please refer to [[join()]] on how to specify this parameter.
1016
     * @param array $params the parameters (name => value) to be bound to the query
1017
     * @return $this the query object itself
1018
     */
1019 3
    public function leftJoin($table, $on = '', $params = [])
1020
    {
1021 3
        $this->join[] = ['LEFT JOIN', $table, $on];
1022 3
        return $this->addParams($params);
1023
    }
1024
1025
    /**
1026
     * Appends a RIGHT OUTER JOIN part to the query.
1027
     * @param string|array $table the table or sub-query to be joined.
1028
     *
1029
     * Use a string to represent the name of the table to be joined.
1030
     * The table name can contain a schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u').
1031
     * The method will automatically quote the table name unless it contains some parenthesis
1032
     * (which means the table is given as a sub-query or DB expression).
1033
     *
1034
     * You may also specify the table as an array with one element, using the array key as the table alias
1035
     * (e.g. ['u' => 'user']).
1036
     *
1037
     * To join a sub-query, use an array with one element, with the value set to a [[Query]] object
1038
     * representing the sub-query, and the corresponding key representing the alias.
1039
     *
1040
     * @param string|array $on the join condition that should appear in the ON part.
1041
     * Please refer to [[join()]] on how to specify this parameter.
1042
     * @param array $params the parameters (name => value) to be bound to the query
1043
     * @return $this the query object itself
1044
     */
1045
    public function rightJoin($table, $on = '', $params = [])
1046
    {
1047
        $this->join[] = ['RIGHT JOIN', $table, $on];
1048
        return $this->addParams($params);
1049
    }
1050
1051
    /**
1052
     * Sets the GROUP BY part of the query.
1053
     * @param string|array|ExpressionInterface|null $columns the columns to be grouped by.
1054
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
1055
     * The method will automatically quote the column names unless a column contains some parenthesis
1056
     * (which means the column contains a DB expression).
1057
     *
1058
     * Note that if your group-by is an expression containing commas, you should always use an array
1059
     * to represent the group-by information. Otherwise, the method will not be able to correctly determine
1060
     * the group-by columns.
1061
     *
1062
     * Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
1063
     * Since version 2.0.14, an [[ExpressionInterface]] object can be passed as well.
1064
     * @return $this the query object itself
1065
     * @see addGroupBy()
1066
     */
1067 30
    public function groupBy($columns)
1068
    {
1069 30
        if ($columns instanceof ExpressionInterface) {
1070 3
            $columns = [$columns];
1071 30
        } elseif (!is_array($columns) && !is_null($columns)) {
1072 30
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
1073
        }
1074 30
        $this->groupBy = $columns;
1075 30
        return $this;
1076
    }
1077
1078
    /**
1079
     * Adds additional group-by columns to the existing ones.
1080
     * @param string|array|ExpressionInterface $columns additional columns to be grouped by.
1081
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
1082
     * The method will automatically quote the column names unless a column contains some parenthesis
1083
     * (which means the column contains a DB expression).
1084
     *
1085
     * Note that if your group-by is an expression containing commas, you should always use an array
1086
     * to represent the group-by information. Otherwise, the method will not be able to correctly determine
1087
     * the group-by columns.
1088
     *
1089
     * Since version 2.0.7, an [[Expression]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
1090
     * Since version 2.0.14, an [[ExpressionInterface]] object can be passed as well.
1091
     * @return $this the query object itself
1092
     * @see groupBy()
1093
     */
1094 3
    public function addGroupBy($columns)
1095
    {
1096 3
        if ($columns instanceof ExpressionInterface) {
1097
            $columns = [$columns];
1098 3
        } elseif (!is_array($columns)) {
1099 3
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
1100
        }
1101 3
        if ($this->groupBy === null) {
1102
            $this->groupBy = $columns;
1103
        } else {
1104 3
            $this->groupBy = array_merge($this->groupBy, $columns);
1105
        }
1106
1107 3
        return $this;
1108
    }
1109
1110
    /**
1111
     * Sets the HAVING part of the query.
1112
     * @param string|array|ExpressionInterface $condition the conditions to be put after HAVING.
1113
     * Please refer to [[where()]] on how to specify this parameter.
1114
     * @param array $params the parameters (name => value) to be bound to the query.
1115
     * @return $this the query object itself
1116
     * @see andHaving()
1117
     * @see orHaving()
1118
     */
1119 13
    public function having($condition, $params = [])
1120
    {
1121 13
        $this->having = $condition;
1122 13
        $this->addParams($params);
1123 13
        return $this;
1124
    }
1125
1126
    /**
1127
     * Adds an additional HAVING condition to the existing one.
1128
     * The new condition and the existing one will be joined using the `AND` operator.
1129
     * @param string|array|ExpressionInterface $condition the new HAVING condition. Please refer to [[where()]]
1130
     * on how to specify this parameter.
1131
     * @param array $params the parameters (name => value) to be bound to the query.
1132
     * @return $this the query object itself
1133
     * @see having()
1134
     * @see orHaving()
1135
     */
1136 3
    public function andHaving($condition, $params = [])
1137
    {
1138 3
        if ($this->having === null) {
1139
            $this->having = $condition;
1140
        } else {
1141 3
            $this->having = ['and', $this->having, $condition];
1142
        }
1143 3
        $this->addParams($params);
1144 3
        return $this;
1145
    }
1146
1147
    /**
1148
     * Adds an additional HAVING condition to the existing one.
1149
     * The new condition and the existing one will be joined using the `OR` operator.
1150
     * @param string|array|ExpressionInterface $condition the new HAVING condition. Please refer to [[where()]]
1151
     * on how to specify this parameter.
1152
     * @param array $params the parameters (name => value) to be bound to the query.
1153
     * @return $this the query object itself
1154
     * @see having()
1155
     * @see andHaving()
1156
     */
1157 3
    public function orHaving($condition, $params = [])
1158
    {
1159 3
        if ($this->having === null) {
1160
            $this->having = $condition;
1161
        } else {
1162 3
            $this->having = ['or', $this->having, $condition];
1163
        }
1164 3
        $this->addParams($params);
1165 3
        return $this;
1166
    }
1167
1168
    /**
1169
     * Sets the HAVING part of the query but ignores [[isEmpty()|empty operands]].
1170
     *
1171
     * This method is similar to [[having()]]. The main difference is that this method will
1172
     * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
1173
     * for building query conditions based on filter values entered by users.
1174
     *
1175
     * The following code shows the difference between this method and [[having()]]:
1176
     *
1177
     * ```php
1178
     * // HAVING `age`=:age
1179
     * $query->filterHaving(['name' => null, 'age' => 20]);
1180
     * // HAVING `age`=:age
1181
     * $query->having(['age' => 20]);
1182
     * // HAVING `name` IS NULL AND `age`=:age
1183
     * $query->having(['name' => null, 'age' => 20]);
1184
     * ```
1185
     *
1186
     * Note that unlike [[having()]], you cannot pass binding parameters to this method.
1187
     *
1188
     * @param array $condition the conditions that should be put in the HAVING part.
1189
     * See [[having()]] on how to specify this parameter.
1190
     * @return $this the query object itself
1191
     * @see having()
1192
     * @see andFilterHaving()
1193
     * @see orFilterHaving()
1194
     * @since 2.0.11
1195
     */
1196 6
    public function filterHaving(array $condition)
1197
    {
1198 6
        $condition = $this->filterCondition($condition);
1199 6
        if ($condition !== []) {
1200 6
            $this->having($condition);
1201
        }
1202
1203 6
        return $this;
1204
    }
1205
1206
    /**
1207
     * Adds an additional HAVING condition to the existing one but ignores [[isEmpty()|empty operands]].
1208
     * The new condition and the existing one will be joined using the `AND` operator.
1209
     *
1210
     * This method is similar to [[andHaving()]]. The main difference is that this method will
1211
     * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
1212
     * for building query conditions based on filter values entered by users.
1213
     *
1214
     * @param array $condition the new HAVING condition. Please refer to [[having()]]
1215
     * on how to specify this parameter.
1216
     * @return $this the query object itself
1217
     * @see filterHaving()
1218
     * @see orFilterHaving()
1219
     * @since 2.0.11
1220
     */
1221 6
    public function andFilterHaving(array $condition)
1222
    {
1223 6
        $condition = $this->filterCondition($condition);
1224 6
        if ($condition !== []) {
1225
            $this->andHaving($condition);
1226
        }
1227
1228 6
        return $this;
1229
    }
1230
1231
    /**
1232
     * Adds an additional HAVING condition to the existing one but ignores [[isEmpty()|empty operands]].
1233
     * The new condition and the existing one will be joined using the `OR` operator.
1234
     *
1235
     * This method is similar to [[orHaving()]]. The main difference is that this method will
1236
     * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
1237
     * for building query conditions based on filter values entered by users.
1238
     *
1239
     * @param array $condition the new HAVING condition. Please refer to [[having()]]
1240
     * on how to specify this parameter.
1241
     * @return $this the query object itself
1242
     * @see filterHaving()
1243
     * @see andFilterHaving()
1244
     * @since 2.0.11
1245
     */
1246 6
    public function orFilterHaving(array $condition)
1247
    {
1248 6
        $condition = $this->filterCondition($condition);
1249 6
        if ($condition !== []) {
1250
            $this->orHaving($condition);
1251
        }
1252
1253 6
        return $this;
1254
    }
1255
1256
    /**
1257
     * Appends a SQL statement using UNION operator.
1258
     * @param string|Query $sql the SQL statement to be appended using UNION
1259
     * @param bool $all TRUE if using UNION ALL and FALSE if using UNION
1260
     * @return $this the query object itself
1261
     */
1262 16
    public function union($sql, $all = false)
1263
    {
1264 16
        $this->union[] = ['query' => $sql, 'all' => $all];
1265 16
        return $this;
1266
    }
1267
1268
    /**
1269
     * Prepends a SQL statement using WITH syntax.
1270
     * @param string|Query $query the SQL statement to be prepended using WITH
1271
     * @param string $alias query alias in WITH construction
1272
     * @param bool $recursive TRUE if using WITH RECURSIVE and FALSE if using WITH
1273
     * @return $this the query object itself
1274
     * @since 2.0.35
1275
     */
1276 9
    public function withQuery($query, $alias, $recursive = false)
1277
    {
1278 9
        $this->withQueries[] = ['query' => $query, 'alias' => $alias, 'recursive' => $recursive];
1279 9
        return $this;
1280
    }
1281
1282
    /**
1283
     * Sets the parameters to be bound to the query.
1284
     * @param array $params list of query parameter values indexed by parameter placeholders.
1285
     * For example, `[':name' => 'Dan', ':age' => 31]`.
1286
     * @return $this the query object itself
1287
     * @see addParams()
1288
     */
1289 6
    public function params($params)
1290
    {
1291 6
        $this->params = $params;
1292 6
        return $this;
1293
    }
1294
1295
    /**
1296
     * Adds additional parameters to be bound to the query.
1297
     * @param array $params list of query parameter values indexed by parameter placeholders.
1298
     * For example, `[':name' => 'Dan', ':age' => 31]`.
1299
     * @return $this the query object itself
1300
     * @see params()
1301
     */
1302 1230
    public function addParams($params)
1303
    {
1304 1230
        if (!empty($params)) {
1305 90
            if (empty($this->params)) {
1306 90
                $this->params = $params;
1307
            } else {
1308 6
                foreach ($params as $name => $value) {
1309 6
                    if (is_int($name)) {
1310
                        $this->params[] = $value;
1311
                    } else {
1312 6
                        $this->params[$name] = $value;
1313
                    }
1314
                }
1315
            }
1316
        }
1317
1318 1230
        return $this;
1319
    }
1320
1321
    /**
1322
     * Enables query cache for this Query.
1323
     * @param int|true $duration the number of seconds that query results can remain valid in cache.
1324
     * Use 0 to indicate that the cached data will never expire.
1325
     * Use a negative number to indicate that query cache should not be used.
1326
     * Use boolean `true` to indicate that [[Connection::queryCacheDuration]] should be used.
1327
     * Defaults to `true`.
1328
     * @param \yii\caching\Dependency|null $dependency the cache dependency associated with the cached result.
1329
     * @return $this the Query object itself
1330
     * @since 2.0.14
1331
     */
1332 3
    public function cache($duration = true, $dependency = null)
1333
    {
1334 3
        $this->queryCacheDuration = $duration;
1335 3
        $this->queryCacheDependency = $dependency;
1336 3
        return $this;
1337
    }
1338
1339
    /**
1340
     * Disables query cache for this Query.
1341
     * @return $this the Query object itself
1342
     * @since 2.0.14
1343
     */
1344 32
    public function noCache()
1345
    {
1346 32
        $this->queryCacheDuration = -1;
1347 32
        return $this;
1348
    }
1349
1350
    /**
1351
     * Sets $command cache, if this query has enabled caching.
1352
     *
1353
     * @param Command $command
1354
     * @return Command
1355
     * @since 2.0.14
1356
     */
1357 874
    protected function setCommandCache($command)
1358
    {
1359 874
        if ($this->queryCacheDuration !== null || $this->queryCacheDependency !== null) {
1360 32
            $duration = $this->queryCacheDuration === true ? null : $this->queryCacheDuration;
1361 32
            $command->cache($duration, $this->queryCacheDependency);
0 ignored issues
show
Bug introduced by
It seems like $duration can also be of type false; however, parameter $duration of yii\db\Command::cache() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

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

1361
            $command->cache(/** @scrutinizer ignore-type */ $duration, $this->queryCacheDependency);
Loading history...
1362
        }
1363
1364 874
        return $command;
1365
    }
1366
1367
    /**
1368
     * Creates a new Query object and copies its property values from an existing one.
1369
     * The properties being copies are the ones to be used by query builders.
1370
     * @param Query $from the source query object
1371
     * @return Query the new Query object
1372
     */
1373 518
    public static function create($from)
1374
    {
1375 518
        return new self([
1376 518
            'where' => $from->where,
1377 518
            'limit' => $from->limit,
1378 518
            'offset' => $from->offset,
1379 518
            'orderBy' => $from->orderBy,
1380 518
            'indexBy' => $from->indexBy,
1381 518
            'select' => $from->select,
1382 518
            'selectOption' => $from->selectOption,
1383 518
            'distinct' => $from->distinct,
1384 518
            'from' => $from->from,
1385 518
            'groupBy' => $from->groupBy,
1386 518
            'join' => $from->join,
1387 518
            'having' => $from->having,
1388 518
            'union' => $from->union,
1389 518
            'params' => $from->params,
1390 518
            'withQueries' => $from->withQueries,
1391 518
        ]);
1392
    }
1393
1394
    /**
1395
     * Returns the SQL representation of Query
1396
     * @return string
1397
     */
1398
    public function __toString()
1399
    {
1400
        return serialize($this);
1401
    }
1402
}
1403