Completed
Push — group-order-expression ( 5a462d )
by Carsten
13:40
created

QueryTrait   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 371
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 0

Test Coverage

Coverage 80.18%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 47
c 2
b 0
f 1
lcom 5
cbo 0
dl 0
loc 371
ccs 85
cts 106
cp 0.8018
rs 8.439

14 Methods

Rating   Name   Duplication   Size   Complexity  
A indexBy() 0 5 1
A where() 0 5 1
A andWhere() 0 9 2
A orWhere() 0 9 2
A filterWhere() 0 8 2
A andFilterWhere() 0 8 2
A orFilterWhere() 0 8 2
C filterCondition() 0 55 19
B isEmpty() 0 4 5
A orderBy() 0 5 1
A addOrderBy() 0 10 2
B normalizeOrderBy() 0 19 6
A limit() 0 5 1
A offset() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like QueryTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use QueryTrait, and based on these observations, apply Extract Interface, too.

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