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
introduced
by
![]() |
|||
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 |