Issues (902)

framework/db/QueryTrait.php (1 issue)

Severity
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use yii\base\NotSupportedException;
11
12
/**
13
 * The BaseQuery trait represents the minimum method set of a database Query.
14
 *
15
 * It is supposed to be used in a class that implements the [[QueryInterface]].
16
 *
17
 * @author Qiang Xue <[email protected]>
18
 * @author Carsten Brandt <[email protected]>
19
 * @since 2.0
20
 */
21
trait QueryTrait
22
{
23
    /**
24
     * @var string|array|ExpressionInterface|null query condition. This refers to the WHERE clause in a SQL statement.
25
     * For example, `['age' => 31, 'team' => 1]`.
26
     * @see where() for valid syntax on specifying this value.
27
     */
28
    public $where;
29
    /**
30
     * @var int|ExpressionInterface|null maximum number of records to be returned. May be an instance of [[ExpressionInterface]].
31
     * If not set or less than 0, it means no limit.
32
     */
33
    public $limit;
34
    /**
35
     * @var int|ExpressionInterface|null zero-based offset from where the records are to be returned.
36
     * May be an instance of [[ExpressionInterface]]. If not set or less than 0, it means starting from the beginning.
37
     */
38
    public $offset;
39
    /**
40
     * @var array|null how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
41
     * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
42
     * can be either [SORT_ASC](https://www.php.net/manual/en/array.constants.php#constant.sort-asc)
43
     * or [SORT_DESC](https://www.php.net/manual/en/array.constants.php#constant.sort-desc).
44
     * The array may also contain [[ExpressionInterface]] objects. If that is the case, the expressions
45
     * will be converted into strings without any change.
46
     */
47
    public $orderBy;
48
    /**
49
     * @var string|callable|null the name of the column by which the query results should be indexed by.
50
     * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
51
     * row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]].
52
     */
53
    public $indexBy;
54
    /**
55
     * @var bool whether to emulate the actual query execution, returning empty or false results.
56
     * @see emulateExecution()
57
     * @since 2.0.11
58
     */
59
    public $emulateExecution = false;
60
61
62
    /**
63
     * Sets the [[indexBy]] property.
64
     * @param string|callable $column the name of the column by which the query results should be indexed by.
65
     * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
66
     * row data. The signature of the callable should be:
67
     *
68
     * ```php
69
     * function ($row)
70
     * {
71
     *     // return the index value corresponding to $row
72
     * }
73
     * ```
74
     *
75
     * @return $this the query object itself
76
     */
77 54
    public function indexBy($column)
78
    {
79 54
        $this->indexBy = $column;
80 54
        return $this;
81
    }
82
83
    /**
84
     * Sets the WHERE part of the query.
85
     *
86
     * See [[QueryInterface::where()]] for detailed documentation.
87
     *
88
     * @param string|array|ExpressionInterface $condition the conditions that should be put in the WHERE part.
89
     * @return $this the query object itself
90
     * @see andWhere()
91
     * @see orWhere()
92
     */
93
    public function where($condition)
94
    {
95
        $this->where = $condition;
96
        return $this;
97
    }
98
99
    /**
100
     * Adds an additional WHERE condition to the existing one.
101
     * The new condition and the existing one will be joined using the 'AND' operator.
102
     * @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
103
     * on how to specify this parameter.
104
     * @return $this the query object itself
105
     * @see where()
106
     * @see orWhere()
107
     */
108
    public function andWhere($condition)
109
    {
110
        if ($this->where === null) {
111
            $this->where = $condition;
112
        } else {
113
            $this->where = ['and', $this->where, $condition];
114
        }
115
116
        return $this;
117
    }
118
119
    /**
120
     * Adds an additional WHERE condition to the existing one.
121
     * The new condition and the existing one will be joined using the 'OR' operator.
122
     * @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
123
     * on how to specify this parameter.
124
     * @return $this the query object itself
125
     * @see where()
126
     * @see andWhere()
127
     */
128
    public function orWhere($condition)
129
    {
130
        if ($this->where === null) {
131
            $this->where = $condition;
132
        } else {
133
            $this->where = ['or', $this->where, $condition];
134
        }
135
136
        return $this;
137
    }
138
139
    /**
140
     * Sets the WHERE part of the query but ignores [[isEmpty()|empty operands]].
141
     *
142
     * This method is similar to [[where()]]. The main difference is that this method will
143
     * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
144
     * for building query conditions based on filter values entered by users.
145
     *
146
     * The following code shows the difference between this method and [[where()]]:
147
     *
148
     * ```php
149
     * // WHERE `age`=:age
150
     * $query->filterWhere(['name' => null, 'age' => 20]);
151
     * // WHERE `age`=:age
152
     * $query->where(['age' => 20]);
153
     * // WHERE `name` IS NULL AND `age`=:age
154
     * $query->where(['name' => null, 'age' => 20]);
155
     * ```
156
     *
157
     * Note that unlike [[where()]], you cannot pass binding parameters to this method.
158
     *
159
     * @param array $condition the conditions that should be put in the WHERE part.
160
     * See [[where()]] on how to specify this parameter.
161
     * @return $this the query object itself
162
     * @see where()
163
     * @see andFilterWhere()
164
     * @see orFilterWhere()
165
     */
166 75
    public function filterWhere(array $condition)
167
    {
168 75
        $condition = $this->filterCondition($condition);
169 75
        if ($condition !== []) {
170 24
            $this->where($condition);
171
        }
172
173 75
        return $this;
174
    }
175
176
    /**
177
     * Adds an additional WHERE condition to the existing one but ignores [[isEmpty()|empty operands]].
178
     * The new condition and the existing one will be joined using the 'AND' operator.
179
     *
180
     * This method is similar to [[andWhere()]]. The main difference is that this method will
181
     * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
182
     * for building query conditions based on filter values entered by users.
183
     *
184
     * @param array $condition the new WHERE condition. Please refer to [[where()]]
185
     * on how to specify this parameter.
186
     * @return $this the query object itself
187
     * @see filterWhere()
188
     * @see orFilterWhere()
189
     */
190 9
    public function andFilterWhere(array $condition)
191
    {
192 9
        $condition = $this->filterCondition($condition);
193 9
        if ($condition !== []) {
194 3
            $this->andWhere($condition);
195
        }
196
197 9
        return $this;
198
    }
199
200
    /**
201
     * Adds an additional WHERE condition to the existing one but ignores [[isEmpty()|empty operands]].
202
     * The new condition and the existing one will be joined using the 'OR' operator.
203
     *
204
     * This method is similar to [[orWhere()]]. The main difference is that this method will
205
     * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
206
     * for building query conditions based on filter values entered by users.
207
     *
208
     * @param array $condition the new WHERE condition. Please refer to [[where()]]
209
     * on how to specify this parameter.
210
     * @return $this the query object itself
211
     * @see filterWhere()
212
     * @see andFilterWhere()
213
     */
214 6
    public function orFilterWhere(array $condition)
215
    {
216 6
        $condition = $this->filterCondition($condition);
217 6
        if ($condition !== []) {
218
            $this->orWhere($condition);
219
        }
220
221 6
        return $this;
222
    }
223
224
    /**
225
     * Removes [[isEmpty()|empty operands]] from the given query condition.
226
     *
227
     * @param array $condition the original condition
228
     * @return array the condition with [[isEmpty()|empty operands]] removed.
229
     * @throws NotSupportedException if the condition operator is not supported
230
     */
231 84
    protected function filterCondition($condition)
232
    {
233 84
        if (!is_array($condition)) {
0 ignored issues
show
The condition is_array($condition) is always true.
Loading history...
234 21
            return $condition;
235
        }
236
237 84
        if (!isset($condition[0])) {
238
            // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
239 9
            foreach ($condition as $name => $value) {
240 9
                if ($this->isEmpty($value)) {
241 6
                    unset($condition[$name]);
242
                }
243
            }
244
245 9
            return $condition;
246
        }
247
248
        // operator format: operator, operand 1, operand 2, ...
249
250 78
        $operator = array_shift($condition);
251
252 78
        switch (strtoupper($operator)) {
253 78
            case 'NOT':
254 75
            case 'AND':
255 66
            case 'OR':
256 30
                foreach ($condition as $i => $operand) {
257 30
                    $subCondition = $this->filterCondition($operand);
258 30
                    if ($this->isEmpty($subCondition)) {
259 30
                        unset($condition[$i]);
260
                    } else {
261 18
                        $condition[$i] = $subCondition;
262
                    }
263
                }
264
265 30
                if (empty($condition)) {
266 15
                    return [];
267
                }
268 18
                break;
269 57
            case 'BETWEEN':
270 54
            case 'NOT BETWEEN':
271 12
                if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
272 12
                    if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) {
273 12
                        return [];
274
                    }
275
                }
276
                break;
277
            default:
278 51
                if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
279 51
                    return [];
280
                }
281
        }
282
283 27
        array_unshift($condition, $operator);
284
285 27
        return $condition;
286
    }
287
288
    /**
289
     * Returns a value indicating whether the give value is "empty".
290
     *
291
     * The value is considered "empty", if one of the following conditions is satisfied:
292
     *
293
     * - it is `null`,
294
     * - an empty string (`''`),
295
     * - a string containing only whitespace characters,
296
     * - or an empty array.
297
     *
298
     * @param mixed $value
299
     * @return bool if the value is empty
300
     */
301 84
    protected function isEmpty($value)
302
    {
303 84
        return $value === '' || $value === [] || $value === null || is_string($value) && trim($value) === '';
304
    }
305
306
    /**
307
     * Sets the ORDER BY part of the query.
308
     * @param string|array|ExpressionInterface|null $columns the columns (and the directions) to be ordered by.
309
     * Columns can be specified in either a string (e.g. `"id ASC, name DESC"`) or an array
310
     * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
311
     *
312
     * The method will automatically quote the column names unless a column contains some parenthesis
313
     * (which means the column contains a DB expression).
314
     *
315
     * Note that if your order-by is an expression containing commas, you should always use an array
316
     * to represent the order-by information. Otherwise, the method will not be able to correctly determine
317
     * the order-by columns.
318
     *
319
     * Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
320
     * @return $this the query object itself
321
     * @see addOrderBy()
322
     */
323 245
    public function orderBy($columns)
324
    {
325 245
        $this->orderBy = $this->normalizeOrderBy($columns);
326 245
        return $this;
327
    }
328
329
    /**
330
     * Adds additional ORDER BY columns to the query.
331
     * @param string|array|ExpressionInterface $columns the columns (and the directions) to be ordered by.
332
     * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
333
     * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
334
     *
335
     * The method will automatically quote the column names unless a column contains some parenthesis
336
     * (which means the column contains a DB expression).
337
     *
338
     * Note that if your order-by is an expression containing commas, you should always use an array
339
     * to represent the order-by information. Otherwise, the method will not be able to correctly determine
340
     * the order-by columns.
341
     *
342
     * Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
343
     * @return $this the query object itself
344
     * @see orderBy()
345
     */
346 54
    public function addOrderBy($columns)
347
    {
348 54
        $columns = $this->normalizeOrderBy($columns);
349 54
        if ($this->orderBy === null) {
350 24
            $this->orderBy = $columns;
351
        } else {
352 45
            $this->orderBy = array_merge($this->orderBy, $columns);
353
        }
354
355 54
        return $this;
356
    }
357
358
    /**
359
     * Normalizes format of ORDER BY data.
360
     *
361
     * @param array|string|ExpressionInterface|null $columns the columns value to normalize. See [[orderBy]] and [[addOrderBy]].
362
     * @return array
363
     */
364 245
    protected function normalizeOrderBy($columns)
365
    {
366 245
        if (empty($columns)) {
367 28
            return [];
368 238
        } elseif ($columns instanceof ExpressionInterface) {
369 6
            return [$columns];
370 238
        } elseif (is_array($columns)) {
371 115
            return $columns;
372
        }
373
374 168
        $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
375 168
        $result = [];
376 168
        foreach ($columns as $column) {
377 168
            if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
378 24
                $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
379
            } else {
380 165
                $result[$column] = SORT_ASC;
381
            }
382
        }
383
384 168
        return $result;
385
    }
386
387
    /**
388
     * Sets the LIMIT part of the query.
389
     * @param int|ExpressionInterface|null $limit the limit. Use null or negative value to disable limit.
390
     * @return $this the query object itself
391
     */
392 144
    public function limit($limit)
393
    {
394 144
        $this->limit = $limit;
395 144
        return $this;
396
    }
397
398
    /**
399
     * Sets the OFFSET part of the query.
400
     * @param int|ExpressionInterface|null $offset the offset. Use null or negative value to disable offset.
401
     * @return $this the query object itself
402
     */
403 43
    public function offset($offset)
404
    {
405 43
        $this->offset = $offset;
406 43
        return $this;
407
    }
408
409
    /**
410
     * Sets whether to emulate query execution, preventing any interaction with data storage.
411
     * After this mode is enabled, methods, returning query results like [[QueryInterface::one()]],
412
     * [[QueryInterface::all()]], [[QueryInterface::exists()]] and so on, will return empty or false values.
413
     * You should use this method in case your program logic indicates query should not return any results, like
414
     * in case you set false where condition like `0=1`.
415
     * @param bool $value whether to prevent query execution.
416
     * @return $this the query object itself.
417
     * @since 2.0.11
418
     */
419 42
    public function emulateExecution($value = true)
420
    {
421 42
        $this->emulateExecution = $value;
422 42
        return $this;
423
    }
424
}
425