Passed
Branch master (2a417c)
by Jean
05:00
created

Filterer::onRowMismatches()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 32

Duplication

Lines 9
Ratio 28.13 %

Code Coverage

Tests 20
CRAP Score 6.027

Importance

Changes 0
Metric Value
cc 6
nc 4
nop 5
dl 9
loc 32
ccs 20
cts 22
cp 0.9091
crap 6.027
rs 8.7857
c 0
b 0
f 0
1
<?php
2
/**
3
 * Filterer
4
 *
5
 * @package php-logical-filter
6
 * @author  Jean Claveau
7
 */
8
namespace JClaveau\LogicalFilter\Filterer;
9
use       JClaveau\LogicalFilter\Filterer\FiltererInterface;
10
use       JClaveau\LogicalFilter\LogicalFilter;
11
12
use       JClaveau\LogicalFilter\Rule\InRule;
13
use       JClaveau\LogicalFilter\Rule\NotInRule;
14
use       JClaveau\LogicalFilter\Rule\EqualRule;
15
use       JClaveau\LogicalFilter\Rule\BelowRule;
16
use       JClaveau\LogicalFilter\Rule\AboveRule;
17
use       JClaveau\LogicalFilter\Rule\NotEqualRule;
18
use       JClaveau\LogicalFilter\Rule\AbstractAtomicRule;
19
use       JClaveau\LogicalFilter\Rule\AbstractOperationRule;
20
21
/**
22
 * This filterer provides the tools and API to apply a LogicalFilter once it has
23
 * been simplified.
24
 */
25
abstract class Filterer implements FiltererInterface
26
{
27
    const leaves_only       = 'leaves_only';
28
    const on_row_matches    = 'on_row_matches';
29
    const on_row_mismatches = 'on_row_mismatches';
30
31
    /** @var array $custom_actions */
32
    protected $custom_actions = [
33
        // self::on_row_matches    => null,
34
        // self::on_row_mismatches => null,
35
    ];
36
37
    /**
38
     */
39
    public function setCustomActions(array $custom_actions)
40
    {
41
        $this->custom_actions = $custom_actions;
42
        return $this;
43
    }
44
45
    /**
46
     */
47 23
    public function onRowMatches(&$row, $key, &$rows, $matching_case, $options)
48
    {
49 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...
50 14
            $callback = $options[ self::on_row_matches ];
51 14
        }
52 10
        elseif (isset($this->custom_actions[ self::on_row_matches ])) {
53
            $callback = $this->custom_actions[ self::on_row_matches ];
54
        }
55
        else {
56 10
            return;
57
        }
58
59
        $args = [
60
            // &$row,
61 14
            $row,
62 14
            $key,
63 14
            &$rows,
64 14
            $matching_case,
65 14
            $options,
66 14
        ];
67
68 14
        call_user_func_array($callback, $args);
69 13
    }
70
71
    /**
72
     */
73 21
    public function onRowMismatches(&$row, $key, &$rows, $matching_case, $options)
74
    {
75 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...
76 21
            && ! isset($options[self::on_row_mismatches])
77 21
            && ! isset($options[self::on_row_matches])
78 21
        ) {
79
            // Unset by default ONLY if NO custom action defined
80 10
            unset($rows[$key]);
81 10
            return;
82
        }
83
84 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...
85 2
            $callback = $options[ self::on_row_mismatches ];
86 2
        }
87 10
        elseif (isset($this->custom_actions[ self::on_row_mismatches ])) {
88
            $callback = $this->custom_actions[ self::on_row_mismatches ];
89
        }
90
        else {
91 10
            return;
92
        }
93
94
        $args = [
95
            // &$row,
96 2
            $row,
97 2
            $key,
98 2
            &$rows,
99 2
            $matching_case,
100 2
            $options,
101 2
        ];
102
103 2
        call_user_func_array($callback, $args);
104 2
    }
105
106
    /**
107
     * @return array
108
     */
109 5
    public function getChildren($row)
110
    {
111 5
        return [];
112
    }
113
114
    /**
115
     */
116
    public function setChildren(&$row, $filtered_children)
117
    {
118
    }
119
120
    /**
121
     * @param LogicalFilter   $filter
122
     * @param Iterable        $tree_to_filter
123
     * @param array           $options
124
     */
125 25
    public function apply( LogicalFilter $filter, $tree_to_filter, $options=[] )
126
    {
127
        $root_OrRule = $filter
128 25
            ->simplify(['force_logical_core' => true])
129 25
            ->getRules()
130
            // ->dump(true)
131 25
            ;
132
133 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...
134 24
            if ( ! $root_OrRule->hasSolution()) {
135
                return null;
136
            }
137
138 24
            $root_cases = $root_OrRule->getOperands();
139 24
        }
140
        else {
141 1
            $root_cases = [];
142
        }
143
144 25
        if ( ! isset($options['recurse'])) {
145 25
            $options['recurse'] = 'before';
146 25
        }
147
        elseif ( ! in_array($options['recurse'], ['before', 'after', null])) {
148
            throw new \InvalidArgumentException(
149
                "Invalid value for 'recurse' option: "
150
                .var_export($options['recurse'], true)
151
                ."\nInstead of ['before', 'after', null]"
152
            );
153
        }
154
155 25
        return $this->foreachRow(
156 25
            $root_cases,
157 25
            $tree_to_filter,
158 25
            $path=[],
159
            $options
160 25
        );
161
    }
162
163
    /**
164
     */
165 25
    protected function foreachRow(array $root_cases, $tree_to_filter, array $path, $options=[])
166
    {
167
        // Once the rules are prepared, we parse the data
168 25
        foreach ($tree_to_filter as $row_index => $row_to_filter) {
169 25
            array_push($path, $row_index);
170
171 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...
172 25
                if ($children = $this->getChildren($row_to_filter)) {
173 20
                    $filtered_children = $this->foreachRow(
174 20
                        $root_cases,
175 20
                        $children,
176 20
                        $path,
177
                        $options
178 20
                    );
179
180 17
                    $this->setChildren($row_to_filter, $filtered_children);
181 17
                }
182 25
            }
183
184 25
            $matching_case = $this->applyOnRow($root_cases, $row_to_filter, $path, $options);
185
186 23
            if ($matching_case) {
187 23
                $this->onRowMatches($row_to_filter, $row_index, $tree_to_filter, $matching_case, $options);
188 22
            }
189 21
            elseif (false === $matching_case) {
190
                // No case match the rule
191 21
                $this->onRowMismatches($row_to_filter, $row_index, $tree_to_filter, $matching_case, $options);
192 21
            }
193 7
            elseif (null === $matching_case) {
194
                // We simply avoid rules
195
                // row out of scope
196 7
            }
197
198 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...
199
                if ($children = $this->getChildren($row_to_filter)) {
200
                    $filtered_children = $this->foreachRow(
201
                        $root_cases,
202
                        $children,
203
                        $path,
204
                        $options
205
                    );
206
207
                    $this->setChildren($row_to_filter, $filtered_children);
208
                }
209
            }
210
211 22
            array_pop($path);
212 22
        }
213
214 22
        return $tree_to_filter;
215
    }
216
217
    /**
218
     * @param LogicalFilter   $filter
219
     * @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...
220
     * @param array           $options
221
     *
222
     * @return bool
223
     */
224 4
    public function hasMatchingCase( LogicalFilter $filter, $row_to_check, $key_to_check, $options=[] )
225
    {
226
        $root_OrRule = $filter
227 4
            ->simplify(['force_logical_core' => true])
228 4
            ->getRules()
229
            // ->dump(true)
230 4
            ;
231
232 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...
233 4
            if ( ! $root_OrRule->hasSolution()) {
234
                return null;
235
            }
236
237 4
            $root_cases = $root_OrRule->getOperands();
238 4
        }
239
        else {
240
            $root_cases = [];
241
        }
242
243 4
        return $this->applyOnRow(
244 4
            $root_cases,
245 4
            $row_to_check,
246 4
            $path=[$key_to_check],
247
            $options
248 4
        );
249
    }
250
251
    /**
252
     */
253 29
    protected function applyOnRow(array $root_cases, $row_to_filter, array $path, $options=[])
254
    {
255 29
        $operands_validation_row_cache = [];
256
257 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...
258 1
            $matching_case = true;
259 1
        }
260
        else {
261 28
            $matching_case = null;
262 28
            foreach ($root_cases as $and_case_index => $and_case) {
263 28
                if ( ! empty($options['debug'])) {
264
                    var_dump("Case $and_case_index: ".$and_case);
0 ignored issues
show
Security Debugging Code introduced by
var_dump("Case {$and_case_index}: " . $and_case); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
265
                }
266
267 28
                $case_is_good = null;
268 28
                foreach ($and_case->getOperands() as $i => $rule) {
269 28
                    if ($rule instanceof OrRule && $rule instanceof AndRule) {
0 ignored issues
show
Bug introduced by
The class JClaveau\LogicalFilter\Filterer\OrRule does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Bug introduced by
The class JClaveau\LogicalFilter\Filterer\AndRule does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
270
                        $field = null;
271
                        $value = $rule->getOperands();
272
                    }
273
                    elseif ($rule instanceof NotEqualRule
274 28
                        || $rule instanceof AbstractAtomicRule
275 26
                        || $rule instanceof InRule
276 7
                        || $rule instanceof NotInRule
277 28
                    ) {
278 28
                        $field = $rule->getField();
279 28
                        $value = $rule->getValues();
0 ignored issues
show
Bug introduced by
The method getValues does only exist in JClaveau\LogicalFilter\R...alFilter\Rule\NotInRule, but not in JClaveau\LogicalFilter\Rule\AbstractAtomicRule.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
280 28
                    }
281
                    else {
282
                        throw new \LogicException(
283
                            "Filtering with a rule which has not been simplified: $rule"
284
                        );
285
                    }
286
287 28
                    $operator = $rule::operator;
288
289 28
                    $cache_key = $and_case_index.'~|~'.$field.'~|~'.$operator;
290
291 28
                    if ( ! empty($operands_validation_row_cache[ $cache_key ])) {
292
                        $is_valid = $operands_validation_row_cache[ $cache_key ];
293
                    }
294
                    else {
295 28
                        $is_valid = $this->validateRule(
296 28
                            $field,
297 28
                            $operator,
298 28
                            $value,
299 28
                            $row_to_filter,
300 28
                            $path,
301 28
                            $root_cases,
302
                            $options
303 28
                        );
304
305 26
                        $operands_validation_row_cache[ $cache_key ] = $is_valid;
306
                    }
307
308 26
                    if (false === $is_valid) {
309
                        // one of the rules of the and_case do not validate
310
                        // so all the and_case is invalid
311 25
                        $case_is_good = false;
312 25
                        break;
313
                    }
314 26
                    elseif (true === $is_valid) {
315
                        // one of the rules of the and_case do not validate
316
                        // so all the and_case is invalid
317 26
                        $case_is_good = true;
318 26
                    }
319 26
                }
320
321 26
                if (true === $case_is_good) {
322
                    // at least one and_case works so we can stop here
323 26
                    $matching_case = $and_case;
324 26
                    break;
325
                }
326 25
                elseif (false === $case_is_good) {
327 25
                    $matching_case = false;
328 25
                }
329 8
                elseif (null === $case_is_good) {
330
                    // row out of scope
331 8
                }
332 26
            }
333
        }
334
335 27
        return $matching_case;
336
    }
337
338
    /**/
339
}
340