Passed
Push — master ( 053de8...66b8f9 )
by Jean
04:17
created

Filterer::applyOnRow()   C

Complexity

Conditions 14
Paths 14

Size

Total Lines 82

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 15.1777

Importance

Changes 0
Metric Value
cc 14
nc 14
nop 4
dl 0
loc 82
ccs 45
cts 55
cp 0.8182
crap 15.1777
rs 5.406
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Filterer
4
 *
5
 * @package php-logical-filter
6
 * @author  Jean Claveau
7
 */
8
namespace JClaveau\LogicalFilter\Filterer;
9
10
use       JClaveau\LogicalFilter\Filterer\FiltererInterface;
11
use       JClaveau\LogicalFilter\LogicalFilter;
12
13
use       JClaveau\LogicalFilter\Rule\InRule;
14
use       JClaveau\LogicalFilter\Rule\NotInRule;
15
use       JClaveau\LogicalFilter\Rule\EqualRule;
16
use       JClaveau\LogicalFilter\Rule\BelowRule;
17
use       JClaveau\LogicalFilter\Rule\AboveRule;
18
use       JClaveau\LogicalFilter\Rule\NotEqualRule;
19
use       JClaveau\LogicalFilter\Rule\AbstractAtomicRule;
20
use       JClaveau\LogicalFilter\Rule\AbstractOperationRule;
21
22
/**
23
 * This filterer provides the tools and API to apply a LogicalFilter once it has
24
 * been simplified.
25
 */
26
abstract class Filterer implements FiltererInterface
27
{
28
    const leaves_only       = 'leaves_only';
29
    const on_row_matches    = 'on_row_matches';
30
    const on_row_mismatches = 'on_row_mismatches';
31
32
    /** @var array $custom_actions */
33
    protected $custom_actions = [
34
        // self::on_row_matches    => null,
35
        // self::on_row_mismatches => null,
36
    ];
37
38
    /**
39
     */
40
    public function setCustomActions(array $custom_actions)
41
    {
42
        $this->custom_actions = $custom_actions;
43
        return $this;
44
    }
45
46
    /**
47
     */
48 23
    public function onRowMatches(&$row, $key, &$rows, $matching_case, $options)
49
    {
50 23 View Code Duplication
        if (isset($options[ self::on_row_matches ])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
51 14
            $callback = $options[ self::on_row_matches ];
52 14
        }
53 10
        elseif (isset($this->custom_actions[ self::on_row_matches ])) {
54
            $callback = $this->custom_actions[ self::on_row_matches ];
55
        }
56
        else {
57 10
            return;
58
        }
59
60
        $args = [
61
            // &$row,
62 14
            $row,
63 14
            $key,
64 14
            &$rows,
65 14
            $matching_case,
66 14
            $options,
67 14
        ];
68
69 14
        call_user_func_array($callback, $args);
70 13
    }
71
72
    /**
73
     */
74 21
    public function onRowMismatches(&$row, $key, &$rows, $matching_case, $options)
75
    {
76 21
        if (   ! $this->custom_actions
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->custom_actions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
77 21
            && ! isset($options[self::on_row_mismatches])
78 21
            && ! isset($options[self::on_row_matches])
79 21
        ) {
80
            // Unset by default ONLY if NO custom action defined
81 10
            unset($rows[$key]);
82 10
            return;
83
        }
84
85 12 View Code Duplication
        if (isset($options[ self::on_row_mismatches ])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
86 2
            $callback = $options[ self::on_row_mismatches ];
87 2
        }
88 10
        elseif (isset($this->custom_actions[ self::on_row_mismatches ])) {
89
            $callback = $this->custom_actions[ self::on_row_mismatches ];
90
        }
91
        else {
92 10
            return;
93
        }
94
95
        $args = [
96
            // &$row,
97 2
            $row,
98 2
            $key,
99 2
            &$rows,
100 2
            $matching_case,
101 2
            $options,
102 2
        ];
103
104 2
        call_user_func_array($callback, $args);
105 2
    }
106
107
    /**
108
     * @return array
109
     */
110 5
    public function getChildren($row)
111
    {
112 5
        return [];
113
    }
114
115
    /**
116
     */
117
    public function setChildren(&$row, $filtered_children)
118
    {
119
    }
120
121
    /**
122
     * @param LogicalFilter   $filter
123
     * @param Iterable        $tree_to_filter
124
     * @param array           $options
125
     */
126 25
    public function apply( LogicalFilter $filter, $tree_to_filter, $options=[] )
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
127
    {
128
        $root_OrRule = $filter
129 25
            ->simplify(['force_logical_core' => true])
130 25
            ->getRules()
131
            // ->dump(true)
132 25
            ;
133
134 25 View Code Duplication
        if (null !== $root_OrRule) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
135 24
            if ( ! $root_OrRule->hasSolution()) {
136
                return null;
137
            }
138
139 24
            $root_cases = $root_OrRule->getOperands();
140 24
        }
141
        else {
142 1
            $root_cases = [];
143
        }
144
145 25
        if ( ! isset($options['recurse'])) {
146 25
            $options['recurse'] = 'before';
147 25
        }
148
        elseif ( ! in_array($options['recurse'], ['before', 'after', null])) {
149
            throw new \InvalidArgumentException(
150
                "Invalid value for 'recurse' option: "
151
                .var_export($options['recurse'], true)
152
                ."\nInstead of ['before', 'after', null]"
153
            );
154
        }
155
156 25
        return $this->foreachRow(
157 25
            $root_cases,
158 25
            $tree_to_filter,
159 25
            $path=[],
160
            $options
161 25
        );
162
    }
163
164
    /**
165
     */
166 25
    protected function foreachRow(array $root_cases, $tree_to_filter, array $path, $options=[])
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
167
    {
168
        // Once the rules are prepared, we parse the data
169 25
        foreach ($tree_to_filter as $row_index => $row_to_filter) {
170 25
            array_push($path, $row_index);
171
172 25 View Code Duplication
            if ('before' == $options['recurse']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173 25
                if ($children = $this->getChildren($row_to_filter)) {
174 20
                    $filtered_children = $this->foreachRow(
175 20
                        $root_cases,
176 20
                        $children,
177 20
                        $path,
178
                        $options
179 20
                    );
180
181 17
                    $this->setChildren($row_to_filter, $filtered_children);
182 17
                }
183 25
            }
184
185 25
            $matching_case = $this->applyOnRow($root_cases, $row_to_filter, $path, $options);
186
187 23
            if ($matching_case) {
188 23
                $this->onRowMatches($row_to_filter, $row_index, $tree_to_filter, $matching_case, $options);
189 22
            }
190 21
            elseif (false === $matching_case) {
191
                // No case match the rule
192 21
                $this->onRowMismatches($row_to_filter, $row_index, $tree_to_filter, $matching_case, $options);
193 21
            }
194 7
            elseif (null === $matching_case) {
195
                // We simply avoid rules
196
                // row out of scope
197 7
            }
198
199 22 View Code Duplication
            if ('after' == $options['recurse']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
                if ($children = $this->getChildren($row_to_filter)) {
201
                    $filtered_children = $this->foreachRow(
202
                        $root_cases,
203
                        $children,
204
                        $path,
205
                        $options
206
                    );
207
208
                    $this->setChildren($row_to_filter, $filtered_children);
209
                }
210
            }
211
212 22
            array_pop($path);
213 22
        }
214
215 22
        return $tree_to_filter;
216
    }
217
218
    /**
219
     * @param LogicalFilter   $filter
220
     * @param Iterable        $tree_to_filter
0 ignored issues
show
Documentation introduced by
There is no parameter named $tree_to_filter. Did you maybe mean $filter?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
221
     * @param array           $options
222
     *
223
     * @return bool
224
     */
225 4
    public function hasMatchingCase( LogicalFilter $filter, $row_to_check, $key_to_check, $options=[] )
226
    {
227
        $root_OrRule = $filter
228 4
            ->simplify(['force_logical_core' => true])
229 4
            ->getRules()
230
            // ->dump(true)
231 4
            ;
232
233 4 View Code Duplication
        if (null !== $root_OrRule) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
234 4
            if ( ! $root_OrRule->hasSolution()) {
235
                return null;
236
            }
237
238 4
            $root_cases = $root_OrRule->getOperands();
239 4
        }
240
        else {
241
            $root_cases = [];
242
        }
243
244 4
        return $this->applyOnRow(
245 4
            $root_cases,
246 4
            $row_to_check,
247 4
            $path=[$key_to_check],
248
            $options
249 4
        );
250
    }
251
252
    /**
253
     */
254 29
    protected function applyOnRow(array $root_cases, $row_to_filter, array $path, $options=[])
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
255
    {
256 29
        $operands_validation_row_cache = [];
257
258 29
        if ( ! $root_cases) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $root_cases of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
259 1
            $matching_case = true;
260 1
        }
261
        else {
262 28
            $matching_case = null;
263 28
            foreach ($root_cases as $and_case_index => $and_case) {
264 28
                if ( ! empty($options['debug'])) {
265
                    var_dump("Case $and_case_index: ".$and_case);
266
                }
267
268 28
                $case_is_good = null;
269 28
                foreach ($and_case->getOperands() as $i => $rule) {
270 28
                    $class = get_class($rule);
271
272 28
                    if (in_array($class, [OrRule::class, AndRule::class, ])) {
273
                        $field = null;
274
                        $value = $rule->getOperands();
275
                    }
276 28
                    elseif ($rule instanceof AbstractAtomicRule || ! $rule->isNormalizationAllowed($options)) {
277 28
                        $field = $rule->getField();
278 28
                        $value = $rule->getValues();
279 28
                    }
280
                    else {
281
                        throw new \LogicException(
282
                            "Filtering with a rule which has not been simplified: $rule"
283
                        );
284
                    }
285
286 28
                    $operator = $rule::operator;
287
288 28
                    $cache_key = $and_case_index.'~|~'.$field.'~|~'.$operator;
289
290 28
                    if ( ! empty($operands_validation_row_cache[ $cache_key ])) {
291
                        $is_valid = $operands_validation_row_cache[ $cache_key ];
292
                    }
293
                    else {
294 28
                        $is_valid = $this->validateRule(
295 28
                            $field,
296 28
                            $operator,
297 28
                            $value,
298 28
                            $row_to_filter,
299 28
                            $path,
300 28
                            $root_cases,
301
                            $options
302 28
                        );
303
304 26
                        $operands_validation_row_cache[ $cache_key ] = $is_valid;
305
                    }
306
307 26
                    if (false === $is_valid) {
308
                        // one of the rules of the and_case do not validate
309
                        // so all the and_case is invalid
310 25
                        $case_is_good = false;
311 25
                        break;
312
                    }
313 26
                    elseif (true === $is_valid) {
314
                        // one of the rules of the and_case do not validate
315
                        // so all the and_case is invalid
316 26
                        $case_is_good = true;
317 26
                    }
318 26
                }
319
320 26
                if (true === $case_is_good) {
321
                    // at least one and_case works so we can stop here
322 26
                    $matching_case = $and_case;
323 26
                    break;
324
                }
325 25
                elseif (false === $case_is_good) {
326 25
                    $matching_case = false;
327 25
                }
328 8
                elseif (null === $case_is_good) {
329
                    // row out of scope
330 8
                }
331 26
            }
332
        }
333
334 27
        return $matching_case;
335
    }
336
337
    /**/
338
}
339