Completed
Push — develop ( 11971b...45f15f )
by Nate
06:59
created

QueryHelper   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 2
dl 0
loc 290
ccs 0
cts 155
cp 0
rs 8.5599
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A conditionToCriteria() 0 15 3
A configure() 0 21 5
A parseBaseParam() 0 18 3
B findParamValue() 0 25 6
A assembleParamValue() 0 16 5
A prepParamValue() 0 25 5
A getJoinType() 0 17 4
A findIdFromObjectArray() 0 8 2
B prependOperator() 0 23 8
A normalizeEmptyValue() 0 10 3
A parseParamOperator() 0 24 4

How to fix   Complexity   

Complex Class

Complex classes like QueryHelper 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 QueryHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://github.com/flipboxfactory/craft-ember/blob/master/LICENSE
6
 * @link       https://github.com/flipboxfactory/craft-ember/
7
 */
8
9
namespace flipbox\craft\ember\helpers;
10
11
use craft\helpers\ArrayHelper;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, flipbox\craft\ember\helpers\ArrayHelper.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
12
use craft\helpers\StringHelper;
13
use yii\db\Query;
14
use yii\db\QueryInterface;
15
16
/**
17
 * @author Flipbox Factory <[email protected]>
18
 * @since 2.0.0
19
 */
20
class QueryHelper
21
{
22
    /**
23
     * @var array
24
     */
25
    protected static $operators = ['not ', '!=', '<=', '>=', '<', '>', '='];
26
27
    /**
28
     * @param $condition
29
     * @return array
30
     */
31
    public static function conditionToCriteria($condition)
32
    {
33
        if (empty($condition)) {
34
            return $condition;
35
        }
36
37
        // Assume it's an id
38
        if (!is_array($condition)) {
39
            $condition = [
40
                'id' => $condition
41
            ];
42
        }
43
44
        return ['where' => ['and', $condition]];
45
    }
46
47
    /**
48
     * @param QueryInterface|Query $query
49
     * @param array $config
50
     * @return QueryInterface
51
     */
52
    public static function configure(QueryInterface $query, $config = []): QueryInterface
53
    {
54
        // Halt
55
        if (empty($config)) {
56
            return $query;
57
        }
58
59
        // Force array
60
        if (!is_array($config)) {
61
            $config = ArrayHelper::toArray($config, [], false);
62
        }
63
64
        // Populate query attributes
65
        foreach ($config as $name => $value) {
66
            if ($query->canSetProperty($name)) {
67
                $query->$name = $value;
68
            }
69
        }
70
71
        return $query;
72
    }
73
74
    /**
75
     * Standard param parsing.
76
     *
77
     * @param $value
78
     * @param $join
79
     * @return bool
80
     */
81
    public static function parseBaseParam(&$value, &$join): bool
82
    {
83
        // Force array
84
        if (!is_array($value)) {
85
            $value = [$value];
86
        }
87
88
        // Get join type ('and' , 'or')
89
        $join = self::getJoinType($value, $join);
90
91
        // Check for object array (via 'id' key)
92
        if ($id = self::findIdFromObjectArray($value)) {
93
            $value = [$id];
94
            return true;
95
        }
96
97
        return false;
98
    }
99
100
    /**
101
     * Attempt to resolve a param value by the value.
102
     * Return false if a 'handle' or other string identifier is detected.
103
     *
104
     * @param $value
105
     * @param $operator
106
     * @return bool
107
     */
108
    public static function findParamValue(&$value, &$operator): bool
109
    {
110
        if (is_array($value) || is_object($value)) {
111
            $value = static::assembleParamValue($value, $operator);
112
        } else {
113
            self::normalizeEmptyValue($value);
114
115
            $operator = self::parseParamOperator($value);
116
117
            if (is_numeric($value)) {
118
                $value = self::prependOperator($value, $operator);
119
            } else {
120
                $value = StringHelper::toLowerCase($value);
121
122
                if ($value !== ':empty:' || $value !== 'not :empty:') {
123
                    // Trim any whitespace from the value
124
                    $value = StringHelper::trim($value);
125
126
                    return false;
127
                }
128
            }
129
        }
130
131
        return true;
132
    }
133
134
    /**
135
     * Format the param value so that we return a string w/ a prepended operator.
136
     *
137
     * @param $value
138
     * @param $operator
139
     * @param string|int|mixed $defaultValue
140
     * @return array|string
141
     */
142
    public static function assembleParamValue($value, $operator, $defaultValue = ':default:')
143
    {
144
        if (is_array($value) || is_object($value)) {
145
            $id = self::findIdFromObjectArray($value, $operator);
146
147
            if ($id !== null) {
148
                return self::prependOperator($id, $operator);
149
            }
150
151
            if (is_object($value)) {
152
                return $defaultValue;
153
            }
154
        }
155
156
        return self::prependOperator($value, $operator);
157
    }
158
159
    /**
160
     * Attempt to resolve a param value by the value.
161
     * Return false if a 'handle' or other string identifier is detected.
162
     *
163
     * @param $value
164
     * @param $operator
165
     * @return bool
166
     */
167
    public static function prepParamValue(&$value, &$operator): bool
168
    {
169
170
        if (is_array($value)) {
171
            return true;
172
        } else {
173
            self::normalizeEmptyValue($value);
174
            $operator = self::parseParamOperator($value);
175
176
            if (is_numeric($value)) {
177
                return true;
178
            } else {
179
                $value = StringHelper::toLowerCase($value);
180
181
                if ($value !== ':empty:' || $value !== 'not :empty:') {
182
                    // Trim any whitespace from the value
183
                    $value = StringHelper::trim($value);
184
185
                    return false;
186
                }
187
            }
188
        }
189
190
        return true;
191
    }
192
193
    /**
194
     * @param $value
195
     * @param string $default
196
     * @return mixed|string
197
     */
198
    private static function getJoinType(&$value, $default = 'or')
199
    {
200
201
        // Get first value in array
202
        $joinType = ArrayHelper::firstValue($value);
203
204
        // Make sure first value is a string
205
        $firstVal = is_string($joinType) ? StringHelper::toLowerCase($joinType) : '';
206
207
        if ($firstVal == 'and' || $firstVal == 'or') {
208
            $join = array_shift($value);
209
        } else {
210
            $join = $default;
211
        }
212
213
        return $join;
214
    }
215
216
    /**
217
     * Attempt to get a numeric value from an object array.
218
     * @param $value
219
     * @param null $operator
220
     * @return mixed|string
221
     */
222
    private static function findIdFromObjectArray($value, $operator = null)
223
    {
224
        if ($id = ArrayHelper::getValue($value, 'id')) {
225
            return self::prependOperator($id, $operator);
226
        }
227
228
        return $id;
229
    }
230
231
    /**
232
     * Prepend the operator to a value
233
     *
234
     * @param $value
235
     * @param null $operator
236
     * @return string|array
237
     */
238
    private static function prependOperator($value, $operator = null)
239
    {
240
241
        if ($operator) {
242
            $operator = StringHelper::toLowerCase($operator);
243
244
            if (in_array($operator, static::$operators) || $operator === 'not') {
245
                if (is_array($value)) {
246
                    $values = [];
247
248
                    foreach ($value as $v) {
249
                        $values[] = $operator . ($operator === 'not' ? ' ' : '') . $v;
250
                    }
251
252
                    return $values;
253
                }
254
255
                return $operator . ($operator === 'not' ? ' ' : '') . $value;
256
            }
257
        }
258
259
        return $value;
260
    }
261
262
    /**
263
     * Normalizes “empty” values.
264
     *
265
     * @param string &$value The param value.
266
     */
267
    private static function normalizeEmptyValue(&$value)
268
    {
269
        if ($value === null) {
270
            $value = ':empty:';
271
        } else {
272
            if (StringHelper::toLowerCase($value) == ':notempty:') {
273
                $value = 'not :empty:';
274
            }
275
        }
276
    }
277
278
    /**
279
     * Extracts the operator from a DB param and returns it.
280
     *
281
     * @param string &$value Te param value.
282
     *
283
     * @return string The operator.
284
     */
285
    private static function parseParamOperator(&$value)
286
    {
287
        foreach (static::$operators as $testOperator) {
288
            // Does the value start with this operator?
289
            $operatorLength = strlen($testOperator);
290
291
            if (strncmp(
292
                StringHelper::toLowerCase($value),
293
                $testOperator,
294
                $operatorLength
295
            ) == 0
296
            ) {
297
                $value = mb_substr($value, $operatorLength);
298
299
                if ($testOperator == 'not ') {
300
                    return 'not';
301
                } else {
302
                    return $testOperator;
303
                }
304
            }
305
        }
306
307
        return '';
308
    }
309
}
310