Passed
Push — master ( 941b97...25fd80 )
by Mauro
02:54
created

QueryBuilder   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 338
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 42
eloc 89
c 2
b 0
f 0
dl 0
loc 338
rs 9.0399

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A sortedBy() 0 13 2
A limit() 0 24 5
A getCount() 0 3 1
B applyCriteriaFilter() 0 30 7
A addElement() 0 7 2
A addCriterion() 0 14 2
A getLastResult() 0 5 2
A getResults() 0 9 2
A isAValidSortingOperator() 0 3 1
A create() 0 3 1
A applyLimitFilter() 0 3 1
A getShuffledResults() 0 11 2
A applyJoinFilter() 0 3 1
A setArray() 0 11 3
A applySortingFilter() 0 3 1
A removeElement() 0 7 2
A getFirstResult() 0 3 2
A join() 0 10 1
A isAValidCriterionOperator() 0 3 1
A getResult() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like QueryBuilder 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.

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 QueryBuilder, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of the ArrayQuery package.
4
 *
5
 * (c) Mauro Cassani<https://github.com/mauretto78>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace ArrayQuery;
12
13
use ArrayQuery\Exceptions\NotConsistentDataException;
14
use ArrayQuery\Exceptions\NotExistingElementException;
15
use ArrayQuery\Exceptions\NotValidCriterionOperatorException;
16
use ArrayQuery\Exceptions\NotValidLimitsOfArrayException;
17
use ArrayQuery\Exceptions\NotValidSortingOperatorException;
18
use ArrayQuery\Filters\CriterionFilter;
19
use ArrayQuery\Filters\JoinFilter;
20
use ArrayQuery\Filters\LimitFilter;
21
use ArrayQuery\Filters\SortingFilter;
22
use ArrayQuery\Helpers\ArrayHelper;
23
use ArrayQuery\Helpers\ConsistencyChecker;
24
25
class QueryBuilder
26
{
27
    /**
28
     * @var array
29
     */
30
    private $criteria;
31
32
    /**
33
     * @var array
34
     */
35
    private $sortedBy;
36
37
    /**
38
     * @var array
39
     */
40
    private $limit;
41
42
    /**
43
     * @var array
44
     */
45
    private $join;
46
47
    /**
48
     * @var array
49
     */
50
    private $array;
51
52
    /**
53
     * QueryBuilder constructor.
54
     *
55
     * @param array $array
56
     *
57
     * @throws NotConsistentDataException
58
     */
59
    public function __construct(array $array)
60
    {
61
        $this->setArray($array);
62
    }
63
64
    /**
65
     * @param array $array
66
     *
67
     * @return static
68
     * @throws NotConsistentDataException
69
     */
70
    public static function create(array $array)
71
    {
72
        return new static($array);
73
    }
74
75
    /**
76
     * @param array $array
77
     *
78
     * @return array
79
     * @throws NotConsistentDataException
80
     */
81
    private function setArray(array $array)
82
    {
83
        if (empty($array)) {
84
            return [];
85
        }
86
87
        if (false === ConsistencyChecker::isValid($array)) {
88
            throw new NotConsistentDataException('Array provided has no consistent data.');
89
        }
90
91
        $this->array = $array;
92
    }
93
94
    /**
95
     * @param $element
96
     * @param null $key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
97
     * @throws NotConsistentDataException
98
     */
99
    public function addElement($element, $key = null)
100
    {
101
        if (false === ConsistencyChecker::isElementValid($element, $this->array)) {
102
            throw new NotConsistentDataException('Element provided has no consistent data.');
103
        }
104
105
        $this->array[$key] = $element;
106
    }
107
108
    /**
109
     * @param $key
110
     * @throws NotExistingElementException
111
     */
112
    public function removeElement($key)
113
    {
114
        if(!isset($this->array[$key])){
115
            throw new NotExistingElementException(sprintf('Element with key %s does not exists.', $key));
116
        }
117
118
        unset($this->array[$key]);
119
    }
120
121
    /**
122
     * @param $key
123
     * @param $value
124
     * @param string $operator
125
     * @param null $dateFormat
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $dateFormat is correct as it would always require null to be passed?
Loading history...
126
     * @return $this
127
     * @throws NotValidCriterionOperatorException
128
     */
129
    public function addCriterion($key, $value, $operator = '=', $dateFormat = null)
130
    {
131
        if (!$this->isAValidCriterionOperator($operator)) {
132
            throw new NotValidCriterionOperatorException($operator.' is not a valid operator.');
133
        }
134
135
        $this->criteria[] = [
136
            'key' => $key,
137
            'value' => $value,
138
            'operator' => $operator,
139
            'date_format' => $dateFormat
140
        ];
141
142
        return $this;
143
    }
144
145
    /**
146
     * @param $operator
147
     * @return bool
148
     */
149
    private function isAValidCriterionOperator($operator)
150
    {
151
        return in_array($operator, array_keys(CriterionFilter::$operatorsMap));
152
    }
153
154
    /**
155
     * @param $key
156
     * @param string $operator
157
     *
158
     * @return $this
159
     *
160
     * @throws NotValidSortingOperatorException
161
     */
162
    public function sortedBy($key, $operator = 'ASC', $format = null)
163
    {
164
        if (!$this->isAValidSortingOperator($operator)) {
165
            throw new NotValidSortingOperatorException($operator.' is not a valid sorting operator.');
166
        }
167
168
        $this->sortedBy = [
169
            'key' => $key,
170
            'order' => $operator,
171
            'format' => $format
172
        ];
173
174
        return $this;
175
    }
176
177
    /**
178
     * @param $operator
179
     * @return bool
180
     */
181
    private function isAValidSortingOperator($operator)
182
    {
183
        return in_array($operator, SortingFilter::$operatorsMap);
184
    }
185
186
    /**
187
     * @param $offset
188
     * @param $length
189
     * @return $this
190
     * @throws NotValidLimitsOfArrayException
191
     */
192
    public function limit($offset, $length)
193
    {
194
        if (!is_integer($offset)) {
195
            throw new NotValidLimitsOfArrayException($offset.' must be an integer.');
196
        }
197
198
        if (!is_integer($length)) {
199
            throw new NotValidLimitsOfArrayException($length.' must be an integer.');
200
        }
201
202
        if ($offset > $length) {
203
            throw new NotValidLimitsOfArrayException($offset.' must be an < than '.$length.'.');
204
        }
205
206
        if ($length > count($this->array)) {
207
            throw new NotValidLimitsOfArrayException($length.' must be an > than array count.');
208
        }
209
210
        $this->limit = [
211
            'offset' => $offset,
212
            'lenght' => $length,
213
        ];
214
215
        return $this;
216
    }
217
218
    /**
219
     * @param $array
220
     * @param $arrayName
221
     * @param $parentKey
222
     * @param $foreignKey
223
     *
224
     * @return $this
225
     */
226
    public function join($array, $arrayName, $parentKey, $foreignKey)
227
    {
228
        $this->join[] = [
229
            'array' => $array,
230
            'arrayName' => $arrayName,
231
            'parentKey' => $parentKey,
232
            'foreignKey' => $foreignKey,
233
        ];
234
235
        return $this;
236
    }
237
238
    /**
239
     * @return array
240
     */
241
    public function getResults()
242
    {
243
        if(empty($this->array)){
244
            return [];
245
        }
246
247
        $results = $this->applySortingFilter($this->applyLimitFilter($this->applyCriteriaFilter($this->applyJoinFilter())));
248
249
        return array_map([ArrayHelper::class, 'convertToPlainArray'], $results);
250
    }
251
252
    /**
253
     * @return array
254
     */
255
    public function getResult($n)
256
    {
257
        return $this->getResults()[$n-1] ?: [];
258
    }
259
260
    /**
261
     * @return array
262
     */
263
    public function getFirstResult()
264
    {
265
        return $this->getResults()[0] ?: [];
266
    }
267
268
    /**
269
     * @return array
270
     */
271
    public function getLastResult()
272
    {
273
        $count = count($this->getResults());
274
275
        return $this->getResults()[$count-1] ?: [];
276
    }
277
278
    /**
279
     * @return array
280
     */
281
    public function getShuffledResults()
282
    {
283
        $shuffledArray = [];
284
        $keys = array_keys($this->getResults());
285
        shuffle($keys);
286
287
        foreach ($keys as $key) {
288
            $shuffledArray[$key] = $this->getResults()[$key];
289
        }
290
291
        return $shuffledArray;
292
    }
293
294
    /**
295
     * @param array $array
296
     *
297
     * @return array
298
     */
299
    private function applySortingFilter(array $array)
300
    {
301
        return SortingFilter::filter($array, $this->sortedBy);
302
    }
303
304
    /**
305
     * @param array $array
306
     *
307
     * @return array
308
     */
309
    private function applyLimitFilter(array $array)
310
    {
311
        return LimitFilter::filter($array, $this->limit);
312
    }
313
314
    /**
315
     * @return array
316
     */
317
    private function applyJoinFilter()
318
    {
319
        return JoinFilter::filter($this->array, $this->join);
320
    }
321
322
    /**
323
     * @return array
324
     */
325
    private function applyCriteriaFilter(array $array)
326
    {
327
        if (empty($this->criteria) or count($this->criteria) === 0) {
328
            return $array;
329
        }
330
331
        foreach ($this->criteria as $criterion) {
332
            $results = array_filter(
333
                (isset($results)) ? $results : $array, function ($element) use ($criterion) {
334
                    return CriterionFilter::filter($criterion, $element);
335
                }
336
            );
337
338
            $results = array_map(function ($result) use ($criterion) {
339
                $key = explode(Constants::ALIAS_DELIMITER, $criterion['key']);
340
                if (count($key) > 1) {
341
                    $oldkey = explode(Constants::ARRAY_SEPARATOR, $key[0]);
342
                    $newkey = $key[1];
343
344
                    $result = ArrayHelper::convertToPlainArray($result);
345
                    $oldResult = $result[$oldkey[0]];
346
347
                    $result[$newkey] = (count($oldkey) > 1) ? ArrayHelper::getValueFromNestedArray($oldkey, $oldResult) : $oldResult;
348
                }
349
350
                return $result;
351
            }, $results);
352
        }
353
354
        return $results;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $results seems to be defined by a foreach iteration on line 331. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
355
    }
356
357
    /**
358
     * @return int
359
     */
360
    public function getCount()
361
    {
362
        return count($this->getResults());
363
    }
364
}
365