Completed
Push — master ( a2b299...9fb2e9 )
by Jean
03:48
created

LogicalFilter   F

Complexity

Total Complexity 142

Size/Duplication

Total Lines 1120
Duplicated Lines 3.93 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 89.34%

Importance

Changes 0
Metric Value
dl 44
loc 1120
ccs 419
cts 469
cp 0.8934
rs 1.1198
c 0
b 0
f 0
wmc 142
lcom 1
cbo 9

39 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 24 7
A getDefaultFilterer() 0 8 2
A setDefaultOptions() 0 8 2
A getDefaultOptions() 0 4 1
A getOptions() 0 9 2
D addRules() 0 85 16
B addRule() 0 36 9
F addCompositeRule_recursion() 8 128 25
A and_() 0 5 1
A or_() 0 5 1
A matches() 0 4 1
A hasSolutionIf() 0 8 1
A getRules() 0 4 3
A simplify() 0 12 2
A addMinimalCase() 0 8 2
A hasSolution() 0 13 3
A toArray() 0 4 2
A toString() 0 4 2
A getSemanticId() 0 4 2
A jsonSerialize() 0 4 1
A __toString() 0 4 1
A __invoke() 0 4 1
A flushRules() 0 5 1
A renameFields() 0 8 2
A removeRules() 0 36 3
A filterRules() 0 16 2
A keepLeafRulesMatching() 0 38 4
A listLeafRulesMatching() 0 33 5
A onEachRule() 0 24 3
A onEachCase() 0 21 3
B getRanges() 6 58 8
A getFieldRange() 0 7 2
A copy() 0 4 1
A __clone() 0 6 2
A saveAs() 0 4 1
A saveCopyAs() 0 5 1
B dump() 6 52 8
A applyOn() 12 23 5
A validates() 12 17 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * LogicalFilter
4
 *
5
 * @package php-logical-filter
6
 * @author  Jean Claveau
7
 */
8
namespace JClaveau\LogicalFilter;
9
10
use JClaveau\LogicalFilter\Rule\AbstractRule;
11
use JClaveau\LogicalFilter\Rule\AbstractOperationRule;
12
use JClaveau\LogicalFilter\Rule\AndRule;
13
use JClaveau\LogicalFilter\Rule\OrRule;
14
use JClaveau\LogicalFilter\Rule\NotRule;
15
16
use JClaveau\LogicalFilter\Filterer\Filterer;
17
use JClaveau\LogicalFilter\Filterer\PhpFilterer;
18
use JClaveau\LogicalFilter\Filterer\CustomizableFilterer;
19
use JClaveau\LogicalFilter\Filterer\RuleFilterer;
20
21
/**
22
 * LogicalFilter describes a set of logical rules structured by
23
 * conjunctions and disjunctions (AND and OR).
24
 *
25
 * It's able to simplify them in order to find contractories branches
26
 * of the tree rule and check if there is at least one set rules having
27
 * possibilities.
28
 */
29
class LogicalFilter implements \JsonSerializable
30
{
31
    /** @var  AndRule $rules */
32
    protected $rules;
33
34
    /** @var  Filterer $default_filterer */
35
    protected $default_filterer;
36
37
    /** @var  array $options */
38
    protected $options = [];
39
40
    /** @var  array $default_options */
41
    protected static $default_options = [
42
        'in.normalization_threshold' => 0,
43
    ];
44
45
    /**
46
     * Creates a filter. You can provide a description of rules as in
47
     * addRules() as paramater.
48
     *
49
     * @param  array    $rules
50
     * @param  Filterer $default_filterer
0 ignored issues
show
Documentation introduced by
Should the type for parameter $default_filterer not be null|Filterer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
51
     *
52
     * @see self::addRules
53
     */
54 260
    public function __construct($rules=[], Filterer $default_filterer=null, array $options=[])
55
    {
56 260
        if ($rules instanceof AbstractRule) {
57 6
            $rules = $rules->copy();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $rules. This often makes code more readable.
Loading history...
58 6
        }
59 260
        elseif (! is_null($rules) && ! is_array($rules)) {
60
            throw new \InvalidArgumentException(
61
                "\$rules must be a rules description or an AbstractRule instead of"
62
                .var_export($rules, true)
63
            );
64
        }
65
66 260
        if ($default_filterer) {
67 36
            $this->default_filterer = $default_filterer;
68 36
        }
69
70 260
        if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options 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...
71 4
            $this->options = $options;
72 4
        }
73
74 260
        if ($rules) {
75 224
            $this->and_( $rules );
76 223
        }
77 259
    }
78
79
    /**
80
     */
81 9
    protected function getDefaultFilterer()
82
    {
83 9
        if (! $this->default_filterer) {
84 7
            $this->default_filterer = new PhpFilterer();
85 7
        }
86
87 9
        return $this->default_filterer;
88
    }
89
90
    /**
91
     */
92 1
    public static function setDefaultOptions(array $options)
93
    {
94 1
        foreach ($options as $name => $default_value) {
95 1
            self::$default_options[$name] = $default_value;
96 1
        }
97
98 1
        AbstractRule::flushStaticCache();
99 1
    }
100
101
    /**
102
     * @return array
103
     */
104 74
    public static function getDefaultOptions()
105
    {
106 74
        return self::$default_options;
107
    }
108
109
    /**
110
     * @return array
111
     */
112 249
    public function getOptions()
113
    {
114 249
        $options = self::$default_options;
115 249
        foreach ($this->options as $name => $value) {
116 4
            $options[$name] = $value;
117 249
        }
118
119 249
        return $options;
120
    }
121
122
    /**
123
     * This method parses different ways to define the rules of a LogicalFilter.
124
     * + You can add N already instanciated Rules.
125
     * + You can provide 3 arguments: $field, $operator, $value
126
     * + You can provide a tree of rules:
127
     * [
128
     *      'or',
129
     *      [
130
     *          'and',
131
     *          ['field_5', 'above', 'a'],
132
     *          ['field_5', 'below', 'a'],
133
     *      ],
134
     *      ['field_6', 'equal', 'b'],
135
     *  ]
136
     *
137
     * @param  string        $operation         and | or
138
     * @param  array         $rules_description Rules description
139
     * @return LogicalFilter $this
140
     */
141 256
    protected function addRules($operation, array $rules_description)
142
    {
143 256
        if ($rules_description == [null]) {
144
            // TODO this is due to the bad design of using "Null" instead of
145
            // TrueRule when a Filter "has no rule". So it's the equivalent of
146
            // "and true" or "or true".
147
            // Remove it while fixing https://github.com/jclaveau/php-logical-filter/issues/59
148 37
            if (AndRule::operator == $operation) {
149
                // A && True <=> A
150 19
                return $this;
151
            }
152 19
            elseif (OrRule::operator == $operation) {
153
                // A || True <=> True
154 19
                $this->rules = null;
155 19
                return $this;
156
            }
157
            else {
158
                throw new InvalidArgumentException(
159
                    "Unhandled operation '$operation'"
160
                );
161
            }
162
        }
163
164 256
        if (   3 == count($rules_description)
165 256
            && is_string($rules_description[0])
166 256
            && is_string($rules_description[1])
167 256
        ) {
168
            // Atomic rules
169 5
            $new_rule = AbstractRule::generateSimpleRule(
170 5
                $rules_description[0], // field
171 5
                $rules_description[1], // operator
172 5
                $rules_description[2], // value
173 5
                $this->getOptions()
174 5
            );
175
176 5
            $this->addRule($new_rule, $operation);
177 4
        }
178
        elseif (count($rules_description) == count(array_filter($rules_description, function($arg) {
179 252
            return $arg instanceof LogicalFilter;
180 252
        })) ) {
181
            // Already instanciated rules
182 2
            foreach ($rules_description as $i => $filter) {
183 2
                $rules = $filter->getRules();
184 2
                if (null !== $rules) {
185 1
                    $this->addRule( $rules, $operation);
186 1
                }
187 2
            }
188 2
        }
189
        elseif (count($rules_description) == count(array_filter($rules_description, function($arg) {
190 251
            return $arg instanceof AbstractRule;
191 251
        })) ) {
192
            // Already instanciated rules
193 8
            foreach ($rules_description as $i => $new_rule) {
194 8
                $this->addRule( $new_rule, $operation);
195 8
            }
196 8
        }
197 250
        elseif (1 == count($rules_description) && is_array($rules_description[0])) {
198
            if (count($rules_description[0]) == count(array_filter($rules_description[0], function($arg) {
199 250
                return $arg instanceof AbstractRule;
200 250
            })) ) {
201
                // Case of $filter->or_([AbstractRule, AbstractRule, AbstractRule, ...])
202 2
                foreach ($rules_description[0] as $i => $new_rule) {
203 2
                    $this->addRule( $new_rule, $operation );
204 2
                }
205 2
            }
206
            else {
207 248
                $fake_root = new AndRule;
208
209 248
                $this->addCompositeRule_recursion(
210 248
                    $rules_description[0],
211
                    $fake_root
212 248
                );
213
214 244
                $this->addRule($fake_root->getOperands()[0], $operation);
215
            }
216 246
        }
217
        else {
218 1
            throw new \InvalidArgumentException(
219
                "Bad set of arguments provided for rules addition: "
220 1
                .var_export($rules_description, true)
221 1
            );
222
        }
223
224 252
        return $this;
225
    }
226
227
    /**
228
     * Add one rule object to the filter
229
     *
230
     * @param AbstractRule $rule
231
     * @param string       $operation
232
     *
233
     * @return $this
234
     */
235 251
    protected function addRule( AbstractRule $rule, $operation=AndRule::operator )
236
    {
237 251
        if ($this->rules && in_array( get_class($this->rules), [AndRule::class, OrRule::class])
238 251
            && ! $this->rules->getOperands() ) {
239 1
            throw new \LogicException(
240
                 "You are trying to add rules to a LogicalFilter which had "
241
                ."only contradictory rules that have already been simplified: "
242 1
                .$this->rules
243 1
            );
244
        }
245
246 251
        if (null === $this->rules) {
247 251
            $this->rules = $rule;
0 ignored issues
show
Documentation Bug introduced by
$rule is of type object<JClaveau\LogicalFilter\Rule\AbstractRule>, but the property $rules was declared to be of type object<JClaveau\LogicalFilter\Rule\AndRule>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
248 251
        }
249 16
        elseif (($tmp_rules = $this->rules) // $this->rules::operator not supported in PHP 5.6
250 16
            && ($tmp_rules::operator != $operation)
251 16
        ) {
252 15
            if (AndRule::operator == $operation) {
253 10
                $this->rules = new AndRule([$this->rules, $rule]);
254 10
            }
255 6
            elseif (OrRule::operator == $operation) {
256 5
                $this->rules = new OrRule([$this->rules, $rule]);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \JClaveau\LogicalFil...y($this->rules, $rule)) of type object<JClaveau\LogicalFilter\Rule\OrRule> is incompatible with the declared type object<JClaveau\LogicalFilter\Rule\AndRule> of property $rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
257 5
            }
258
            else {
259 1
                throw new \InvalidArgumentException(
260 1
                    "\$operation must be '".AndRule::operator."' or '".OrRule::operator
261 1
                    ."' instead of: ".var_export($operation, true)
262 1
                );
263
            }
264 14
        }
265
        else {
266 6
            $this->rules->addOperand($rule);
267
        }
268
269 251
        return $this;
270
    }
271
272
    /**
273
     * Recursion auxiliary of addCompositeRule.
274
     *
275
     * @param array                 $rules_composition  The description of the
276
     *                                                  rules to add.
277
     * @param AbstractOperationRule $recursion_position The position in the
278
     *                                                  tree where rules must
279
     *                                                  be added.
280
     *
281
     * @return $this
282
     */
283 248
    protected function addCompositeRule_recursion(
284
        array $rules_composition,
285
        AbstractOperationRule $recursion_position
286
    ) {
287
        if (! array_filter($rules_composition, function ($rule_composition_part) {
288 248
            return is_string($rule_composition_part);
289 248
        })) {
290
            // at least one operator is required for operation rules
291 1
            throw new \InvalidArgumentException(
292
                "Please provide an operator for the operation: \n"
293 1
                .var_export($rules_composition, true)
294 1
            );
295
        }
296 247
        elseif ( 3 == count($rules_composition)
297 247
            && AbstractRule::isLeftOperand($rules_composition[0])
298 247
            && AbstractRule::isOperator($rules_composition[1])
299 247
        ) {
300
            // atomic or composit rules
301 244
            $operand_left  = $rules_composition[0];
302 244
            $operation     = $rules_composition[1];
303 244
            $operand_right = $rules_composition[2];
304
305 244
            $rule = AbstractRule::generateSimpleRule(
306 244
                $operand_left, $operation, $operand_right, $this->getOptions()
307 244
            );
308 244
            $recursion_position->addOperand( $rule );
309 244
        }
310
        else {
311
            // operations
312 141
            if (   NotRule::operator == $rules_composition[0]
313 141
                || $rules_composition[0] == AbstractRule::findSymbolicOperator( NotRule::operator ) ) {
314 21
                $rule = new NotRule();
315 21
            }
316 132 View Code Duplication
            elseif (in_array( AndRule::operator, $rules_composition )
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...
317 132
                || in_array( AbstractRule::findSymbolicOperator( AndRule::operator ), $rules_composition )) {
318 114
                $rule = new AndRule();
319 114
            }
320 72 View Code Duplication
            elseif (in_array( OrRule::operator, $rules_composition )
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...
321 72
                || in_array( AbstractRule::findSymbolicOperator( OrRule::operator ), $rules_composition ) ) {
322 71
                $rule = new OrRule();
323 71
            }
324
            else {
325 1
                throw new \InvalidArgumentException(
326
                    "A rule description seems to be an operation but do "
327 1
                    ."not contains a valid operator: ".var_export($rules_composition, true)
328 1
                );
329
            }
330
331 140
            $operator = $rule::operator;
332
333 140
            $operands_descriptions = array_filter(
334 140
                $rules_composition,
335
                function ($operand) use ($operator) {
336 140
                    return ! in_array($operand, [$operator, AbstractRule::findSymbolicOperator( $operator )]);
337
                }
338 140
            );
339
340 140
            $non_true_rule_descriptions = array_filter(
341 140
                $operands_descriptions,
342
                function($operand) {
343 139
                    return null !== $operand  // no rule <=> true
344 139
                        || true !== $operand
345 139
                        ;
346
                }
347 140
            );
348
349 140
            foreach ($operands_descriptions as $i => $operands_description) {
350 139
                if (false === $operands_description) {
351 1
                    $operands_descriptions[ $i ] = ['and']; // FalseRule hack
352 1
                }
353 139
                elseif (null === $operands_description || true === $operands_description) {
354 1
                    $operands_description = ['and'];
355 1
                    if (empty($non_true_rule_descriptions)) {
356
                        throw new \LogicException(
357
                            "TrueRules are not implemented. Please add "
358
                            ."them to operations having other type of rules"
359
                        );
360
                    }
361
362 1
                    unset($operands_descriptions[ $i ]);
363 1
                }
364 140
            }
365
366 140
            $remaining_operations = array_filter(
367 140
                $operands_descriptions,
368
                function($operand) {
369 139
                    return ! is_array($operand)
370 139
                        && ! $operand instanceof AbstractRule
371 139
                        && ! $operand instanceof LogicalFilter
372 139
                        ;
373
                }
374 140
            );
375
376 140
            if (! empty($remaining_operations)) {
377 1
                throw new \InvalidArgumentException(
378
                    "Mixing different operations in the same rule level not implemented: \n["
379 1
                    . implode(', ', $remaining_operations)."]\n"
380 1
                    . 'in ' . var_export($rules_composition, true)
381 1
                );
382
            }
383
384 140
            if (NotRule::operator == $operator && 1 != count($operands_descriptions)) {
385 1
                throw new \InvalidArgumentException(
386
                    "Negations can have only one operand: \n"
387 1
                    .var_export($rules_composition, true)
388 1
                );
389
            }
390
391 139
            foreach ($operands_descriptions as $operands_description) {
392 138
                if ($operands_description instanceof AbstractRule) {
393 1
                    $rule->addOperand($operands_description);
394 1
                }
395 138
                elseif ($operands_description instanceof LogicalFilter) {
396 2
                    $rule->addOperand($operands_description->getRules());
397 2
                }
398
                else {
399 137
                    $this->addCompositeRule_recursion(
400 137
                        $operands_description,
401
                        $rule
402 137
                    );
403
                }
404 139
            }
405
406 138
            $recursion_position->addOperand( $rule );
407
        }
408
409 245
        return $this;
410
    }
411
412
    /**
413
     * This method parses different ways to define the rules of a LogicalFilter
414
     * and add them as a new And part of the filter.
415
     * + You can add N already instanciated Rules.
416
     * + You can provide 3 arguments: $field, $operator, $value
417
     * + You can provide a tree of rules:
418
     * [
419
     *      'or',
420
     *      [
421
     *          'and',
422
     *          ['field_5', 'above', 'a'],
423
     *          ['field_5', 'below', 'a'],
424
     *      ],
425
     *      ['field_6', 'equal', 'b'],
426
     *  ]
427
     *
428
     * @param  mixed The descriptions of the rules to add
429
     * @return $this
430
     *
431
     * @todo remove the _ for PHP 7
432
     */
433 253
    public function and_()
434
    {
435 253
        $this->addRules( AndRule::operator, func_get_args());
436 249
        return $this;
437
    }
438
439
    /**
440
     * This method parses different ways to define the rules of a LogicalFilter
441
     * and add them as a new Or part of the filter.
442
     * + You can add N already instanciated Rules.
443
     * + You can provide 3 arguments: $field, $operator, $value
444
     * + You can provide a tree of rules:
445
     * [
446
     *      'or',
447
     *      [
448
     *          'and',
449
     *          ['field_5', 'above', 'a'],
450
     *          ['field_5', 'below', 'a'],
451
     *      ],
452
     *      ['field_6', 'equal', 'b'],
453
     *  ]
454
     *
455
     * @param  mixed The descriptions of the rules to add
456
     * @return $this
457
     *
458
     * @todo
459
     * @todo remove the _ for PHP 7
460
     */
461 24
    public function or_()
462
    {
463 24
        $this->addRules( OrRule::operator, func_get_args());
464 24
        return $this;
465
    }
466
467
    /**
468
     * @deprecated
469
     */
470 1
    public function matches($rules_to_match)
471
    {
472 1
        return $this->hasSolutionIf($rules_to_match);
473
    }
474
475
    /**
476
     * Checks that a filter matches another one.
477
     *
478
     * @param array|AbstractRule $rules_to_match
479
     *
480
     * @return bool Whether or not this combination of filters has
481
     *              potential solutions
482
     */
483 1
    public function hasSolutionIf($rules_to_match)
484
    {
485 1
        return $this
486 1
            ->copy()
487 1
            ->and_($rules_to_match)
488 1
            ->hasSolution()
489 1
            ;
490
    }
491
492
    /**
493
     * Retrieve all the rules.
494
     *
495
     * @param  bool $copy By default copy the rule tree to avoid side effects.
496
     *
497
     * @return AbstractRule The tree of rules
498
     */
499 112
    public function getRules($copy = true)
500
    {
501 112
        return $copy && $this->rules ? $this->rules->copy() : $this->rules;
502
    }
503
504
    /**
505
     * Remove any constraint being a duplicate of another one.
506
     *
507
     * @param  array $options stop_after | stop_before |
508
     * @return $this
509
     */
510 121
    public function simplify($options=[])
511
    {
512 121
        if ($this->rules) {
513
            // AndRule added to make all Operation methods available
514 119
            $this->rules = (new AndRule([$this->rules]))
515 119
                ->simplify( $options )
516
                // ->dump(true, false)
517
                ;
518 119
        }
519
520 121
        return $this;
521
    }
522
523
524
    /**
525
     * Forces the two firsts levels of the tree to be an OrRule having
526
     * only AndRules as operands:
527
     * ['field', '=', '1'] <=> ['or', ['and', ['field', '=', '1']]]
528
     * As a simplified ruleTree will alwways be reduced to this structure
529
     * with no suboperands others than atomic ones or a simpler one like:
530
     * ['or', ['field', '=', '1'], ['field2', '>', '3']]
531
     *
532
     * This helpes to ease the result of simplify()
533
     *
534
     * @return OrRule
0 ignored issues
show
Documentation introduced by
Should the return type not be LogicalFilter?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
535
     */
536 53
    public function addMinimalCase()
537
    {
538 53
        if ($this->rules) {
539 53
            $this->rules = $this->rules->addMinimalCase();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->rules->addMinimalCase() of type object<JClaveau\LogicalFilter\Rule\OrRule> is incompatible with the declared type object<JClaveau\LogicalFilter\Rule\AndRule> of property $rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
540 53
        }
541
542 53
        return $this;
543
    }
544
545
    /**
546
     * Checks if there is at least on set of conditions which is not
547
     * contradictory.
548
     *
549
     * Checking if a filter has solutions require to simplify it first.
550
     * To let the control on the balance between readability and
551
     * performances, the required simplification can be saved or not
552
     * depending on the $save_simplification parameter.
553
     *
554
     * @param  $save_simplification
555
     *
556
     * @return bool
557
     */
558 61
    public function hasSolution($save_simplification=true)
559
    {
560 61
        if (! $this->rules) {
561 2
            return true;
562
        }
563
564 59
        if ($save_simplification) {
565 58
            $this->simplify();
566 58
            return $this->rules->hasSolution();
567
        }
568
569 2
        return $this->copy()->simplify()->rules->hasSolution();
570
    }
571
572
    /**
573
     * Returns an array describing the rule tree of the Filter.
574
     *
575
     * @param array $options
576
     *
577
     * @return array A description of the rules.
578
     */
579 188
    public function toArray(array $options=[])
580
    {
581 188
        return $this->rules ? $this->rules->toArray($options) : $this->rules;
582
    }
583
584
    /**
585
     * Returns an array describing the rule tree of the Filter.
586
     *
587
     * @param $debug Provides a source oriented dump.
588
     *
589
     * @return array A description of the rules.
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
590
     */
591 4
    public function toString(array $options=[])
592
    {
593 4
        return $this->rules ? $this->rules->toString($options) : $this->rules;
594
    }
595
596
    /**
597
     * Returns a unique id corresponding to the set of rules of the filter
598
     *
599
     * @return string The unique semantic id
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
600
     */
601 1
    public function getSemanticId()
602
    {
603 1
        return $this->rules ? $this->rules->getSemanticId() : null;
604
    }
605
606
    /**
607
     * For implementing JsonSerializable interface.
608
     *
609
     * @see https://secure.php.net/manual/en/jsonserializable.jsonserialize.php
610
     */
611 1
    public function jsonSerialize()
612
    {
613 1
        return $this->toArray();
614
    }
615
616
    /**
617
     * @return string
618
     */
619 4
    public function __toString()
620
    {
621 4
        return $this->toString();
622
    }
623
624
    /**
625
     * @see    https://secure.php.net/manual/en/language.oop5.magic.php#object.invoke
626
     * @param  mixed $row
627
     * @return bool
628
     */
629 3
    public function __invoke($row, $key=null)
630
    {
631 3
        return $this->validates($row, $key);
632
    }
633
634
    /**
635
     * Removes all the defined rules.
636
     *
637
     * @return $this
638
     */
639 2
    public function flushRules()
640
    {
641 2
        $this->rules = null;
642 2
        return $this;
643
    }
644
645
    /**
646
     * @param  array|callable Associative array of renamings or callable
647
     *                        that would rename the fields.
648
     *
649
     * @return LogicalFilter  $this
650
     */
651 1
    public function renameFields($renamings)
652
    {
653 1
        if ($this->rules) {
654 1
            $this->rules->renameFields($renamings);
655 1
        }
656
657 1
        return $this;
658
    }
659
660
    /**
661
     * @param  array|callable Associative array of renamings or callable
662
     *                        that would rename the fields.
663
     *
664
     * @return string $this
0 ignored issues
show
Documentation introduced by
Should the return type not be LogicalFilter?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
665
     */
666 10
    public function removeRules($filter)
667
    {
668 10
        $cache_flush_required = false;
669
670 10
        $this->rules = (new RuleFilterer)->apply(
671 10
            new LogicalFilter($filter),
672 10
            $this->rules,
673
            [
674
                Filterer::on_row_matches => function($rule, $key, &$rows, $matching_case) use (&$cache_flush_required) {
0 ignored issues
show
Unused Code introduced by
The parameter $matching_case is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
675
                    // $rule->dump();
676 8
                    unset( $rows[$key] );
677 8
                    if (! $rows ) {
678 1
                        throw new \Exception(
679 1
                             "Removing the only rule $rule from the filter $this "
680
                            ."produces a case which has no possible solution due to missing "
681 1
                            ."implementation of TrueRule.\n"
682 1
                            ."Please see: https://github.com/jclaveau/php-logical-filter/issues/59"
683 1
                        );
684
                    }
685
686
                    // $matching_case->dump(true);
687 7
                    $cache_flush_required = true;
688 10
                },
689
                // Filterer::on_row_mismatches => function($rule, $key, &$rows, $matching_case) {
690
                    // $rule->dump();
691
                    // $matching_case && $matching_case->dump(true);
692
                // }
693
            ]
694 10
        );
695
696 7
        if ($cache_flush_required) {
697 7
            $this->rules->flushCache();
698 7
        }
699
700 7
        return $this;
701
    }
702
703
    /**
704
     * Apply a "RuleFilter" on the rules of the current instance.
705
     *
706
     * @param  array|LogicalFilter  $rule_filter
707
     * @param  array|callable       $options
708
     *
709
     * @return array The rules matching the filter
0 ignored issues
show
Documentation introduced by
Should the return type not be LogicalFilter?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
710
     */
711 20
    public function filterRules($rule_filter=[], array $options=[])
712
    {
713 20
        $filter = (new LogicalFilter($rule_filter, new RuleFilterer))
0 ignored issues
show
Bug introduced by
It seems like $rule_filter defined by parameter $rule_filter on line 711 can also be of type object<JClaveau\LogicalFilter\LogicalFilter>; however, JClaveau\LogicalFilter\L...alFilter::__construct() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
714
        // ->dump()
715 20
        ;
716
717 20
        $this->rules = (new RuleFilterer)->apply($filter, $this->rules, $options);
718
        // $this->rules->dump(true);
719
720
        // TODO replace it by a FalseRule
721 19
        if (false === $this->rules) {
722 1
            $this->rules = new AndRule;
723 1
        }
724
725 19
        return $this;
726
    }
727
728
    /**
729
     * @param  array|callable Associative array of renamings or callable
730
     *                        that would rename the fields.
731
     *
732
     * @return array The rules matching the filter
733
     * @return array $options debug | leaves_only | clean_empty_branches
0 ignored issues
show
Documentation introduced by
Should the return type not be LogicalFilter?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
734
     */
735 4
    public function keepLeafRulesMatching($filter=[], array $options=[])
736
    {
737 4
        $clean_empty_branches = ! isset($options['clean_empty_branches']) || $options['clean_empty_branches'];
738
739 4
        $filter = (new LogicalFilter($filter, new RuleFilterer))
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filter. This often makes code more readable.
Loading history...
740
        // ->dump()
741 4
        ;
742
743 4
        $options[ Filterer::leaves_only ] = true;
744
745 4
        $this->rules = (new RuleFilterer)->apply($filter, $this->rules, $options);
746
        // $this->rules->dump(true);
747
748
        // clean the remaining branches
749 4
        if ($clean_empty_branches) {
750 4
            $this->rules = (new RuleFilterer)->apply(
751 4
                new LogicalFilter(['and',
752 4
                    ['operator', 'in', ['or', 'and', 'not', '!in']],
753 4
                    ['children', '=', 0],
754 4
                ]),
755 4
                $this->rules,
756
                [
757
                    Filterer::on_row_matches => function($rule, $key, &$rows) {
758 2
                        unset($rows[$key]);
759 4
                    },
760
                    Filterer::on_row_mismatches => function($rule, $key, &$rows) {
0 ignored issues
show
Unused Code introduced by
The parameter $rule is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $rows is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
761 4
                    },
762
                ]
763 4
            );
764
765
            // TODO replace it by a FalseRule
766 4
            if (false === $this->rules) {
767 1
                $this->rules = new AndRule;
768 1
            }
769 4
        }
770
771 4
        return $this;
772
    }
773
774
    /**
775
     * @param  array|callable Associative array of renamings or callable
776
     *                        that would rename the fields.
777
     *
778
     * @return array The rules matching the filter
779
     *
780
     *
781
     * @todo Merge with rules
782
     */
783 3
    public function listLeafRulesMatching($filter=[])
784
    {
785 3
        $filter = (new LogicalFilter($filter, new RuleFilterer))
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filter. This often makes code more readable.
Loading history...
786
        // ->dump()
787 3
        ;
788
789 3
        if (! $this->rules) {
790 1
            return [];
791
        }
792
793 2
        $out = [];
794 2
        (new RuleFilterer)->apply(
795 2
            $filter,
796 2
            $this->rules,
797
            [
798
                Filterer::on_row_matches => function(
799
                    AbstractRule $matching_rule,
800
                    $key,
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
801
                    array $siblings
0 ignored issues
show
Unused Code introduced by
The parameter $siblings is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
802
                ) use (&$out) {
803
                    if (   ! $matching_rule instanceof AndRule
804 2
                        && ! $matching_rule instanceof OrRule
805 2
                        && ! $matching_rule instanceof NotRule
806 2
                    ) {
807 2
                        $out[] = $matching_rule;
808 2
                    }
809 2
                },
810 2
                Filterer::leaves_only => true,
811
            ]
812 2
        );
813
814 2
        return $out;
815
    }
816
817
    /**
818
     * $filter->onEachRule(
819
     *      ['field', 'in', [...]],
820
     *      function ($rule, $key, array &$rules) {
821
     *          // ...
822
     * })
823
     *
824
     * $filter->onEachRule(
825
     *      ['field', 'in', [...]],
826
     *      [
827
     *          Filterer::on_row_matches => function ($rule, $key, array &$rules) {
828
     *              // ...
829
     *          },
830
     *          Filterer::on_row_mismatches => function ($rule, $key, array &$rules) {
831
     *              // ...
832
     *          },
833
     *      ]
834
     * )
835
     *
836
     * @todo Make it available on AbstractRule also
837
     *
838
     * @param  array|LogicalFilter
839
     * @param  array|callable Associative array of renamings or callable
840
     *                        that would rename the fields.
841
     *
842
     * @return array          The rules matching the filter
0 ignored issues
show
Documentation introduced by
Should the return type not be array|LogicalFilter?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
843
     */
844 7
    public function onEachRule($filter=[], $options)
845
    {
846 7
        $filter = (new LogicalFilter($filter, new RuleFilterer))
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filter. This often makes code more readable.
Loading history...
847
        // ->dump()
848 7
        ;
849
850 7
        if (! $this->rules) {
851
            return [];
852
        }
853
854 7
        if (is_callable($options)) {
855
            $options = [
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $options. This often makes code more readable.
Loading history...
856 7
                Filterer::on_row_matches => $options,
857 7
            ];
858 7
        }
859
860 7
        (new RuleFilterer)->apply(
861 7
            $filter,
862 7
            $this->rules,
863
            $options
864 7
        );
865
866 7
        return $this;
867
    }
868
869
    /**
870
     * $filter->onEachCase(function (AndRule $case, $key, array &$caseRules) {
871
     *      // do whatever you want on the current case...
872
     * })
873
     *
874
     * @param  array|callable $action Callback to apply on each case.
875
     * @return LogicalFilter  $this
876
     *
877
     * @todo Make it available on AbstractRule also
878
     */
879 6
    public function onEachCase(callable $action)
880
    {
881 6
        $this->simplify()->addMinimalCase();
882
883 6
        if (! $this->rules) {
884
            return $this;
885
        }
886
887 6
        $operands = $this->rules->getOperands();
888
889 6
        foreach ($operands as $i => &$and_case) {
890
            $arguments = [
891 6
                &$and_case,
892 6
            ];
893 6
            call_user_func_array($action, $arguments);
894 6
        }
895
896 6
        $this->rules = new OrRule($operands);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \JClaveau\LogicalFilter\Rule\OrRule($operands) of type object<JClaveau\LogicalFilter\Rule\OrRule> is incompatible with the declared type object<JClaveau\LogicalFilter\Rule\AndRule> of property $rules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
897
898 6
        return $this;
899
    }
900
901
    /**
902
     * Retrieves the minimum possibility and the maximum possibility for
903
     * each field of the rules matching the filter.
904
     *
905
     * @param  array|LogicalFilter|AbstractRule $ruleFilter
0 ignored issues
show
Documentation introduced by
Should the type for parameter $ruleFilter not be array|LogicalFilter|AbstractRule|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
906
     *
907
     * @return array The bounds of the range and a nullable property for each field
908
     */
909 5
    public function getRanges($ruleFilter=null)
910
    {
911 5
        $ranges = [];
912
913
        $this->onEachCase(function (AndRule $and_rule) use (&$ranges, $ruleFilter) {
914 5
            (new self($and_rule))->onEachRule(
0 ignored issues
show
Documentation introduced by
$and_rule is of type object<JClaveau\LogicalFilter\Rule\AndRule>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
915 5
                ['and',
916 5
                    $ruleFilter,
917 5
                    ['operator', 'in', [
918 5
                        '=', '>', '<', '>=', '<=',
919 5
                        '><', '><=', '=><=', '=><',
920 5
                    ]],
921 5
                ],
922 4
                function ($rule) use (&$ranges) {
923
924 4
                    $field = $rule->getField();
925
926 4
                    $range = isset($ranges[ $field ])
927 4
                           ? $ranges[ $field ]
928 4
                           : ['min' => [], 'max' => [], 'nullable' => false];
929
930 4
                    if ($rule::operator == '=') {
931 3
                        if (null === $rule->getValues()) {
932 1
                            $range['nullable'] = true;
933 1
                        }
934
                        else {
935 2
                            $range['min'][] = $rule->getValues();
936 2
                            $range['max'][] = $rule->getValues();
937
                        }
938 3
                    }
939 4 View Code Duplication
                    elseif (in_array($rule::operator, ['<', '<='])) {
940 4
                        $range['max'][] = $rule->getValues();
941 4
                    }
942 4 View Code Duplication
                    elseif (in_array($rule::operator, ['>', '>='])) {
943 4
                        $range['min'][] = $rule->getValues();
944 4
                    }
945
                    elseif (in_array($rule::operator, ['><', '><=', '=><=', '=><'])) {
946
                        $range['min'][] = $rule->getValues()[0];
947
                        $range['max'][] = $rule->getValues()[1];
948
                    }
949
                    else {
950
                        throw new \LogicException(
951
                            "Buggy case: ".$rule::operator
952
                        );
953
                    }
954
955 4
                    $ranges[ $field ] = $range;
956 4
                }
957 5
            );
958 5
        });
959
960 5
        foreach ($ranges as &$range) {
961 4
            $range['min'] = min($range['min']);
962 4
            $range['max'] = max($range['max']);
963 5
        }
964
965 5
        return $ranges;
966
    }
967
968
    /**
969
     * Retrieves the minimum possibility and the maximum possibility for
970
     * the given field.
971
     *
972
     * @param  mixed $field
973
     * @return array The bounds of the range and a nullable property for the given field
974
     */
975 5
    public function getFieldRange($field)
976
    {
977 5
        $range = $this->getRanges(['field', '=', $field]);
978 5
        return isset($range[$field])
979 5
            ? $range[$field]
980 5
            : ['min' => null, 'max' => null, 'nullable' => false];
981
    }
982
983
    /**
984
     * Clone the current object and its rules.
985
     *
986
     * @return LogicalFilter A copy of the current instance with a copied ruletree
987
     */
988 7
    public function copy()
989
    {
990 7
        return clone $this;
991
    }
992
993
    /**
994
     * Make a deep copy of the rules
995
     */
996 7
    public function __clone()
997
    {
998 7
        if ($this->rules) {
999 7
            $this->rules = $this->rules->copy();
1000 7
        }
1001 7
    }
1002
1003
    /**
1004
     * Copy the current instance into the variable given as parameter
1005
     * and returns the copy.
1006
     *
1007
     * @return LogicalFilter
1008
     */
1009 3
    public function saveAs( &$variable)
1010
    {
1011 3
        return $variable = $this;
1012
    }
1013
1014
    /**
1015
     * Copy the current instance into the variable given as parameter
1016
     * and returns the copied instance.
1017
     *
1018
     * @return LogicalFilter
1019
     */
1020 1
    public function saveCopyAs( &$copied_variable)
1021
    {
1022 1
        $copied_variable = $this->copy();
1023 1
        return $this;
1024
    }
1025
1026
    /**
1027
     * @param bool  $exit=false
0 ignored issues
show
Bug introduced by
There is no parameter named $exit=false. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
1028
     * @param array $options    + callstack_depth=2 The level of the caller to dump
1029
     *                          + mode='string' in 'export' | 'dump' | 'string'
1030
     *
1031
     * @return $this
0 ignored issues
show
Documentation introduced by
Should the return type not be LogicalFilter|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1032
     */
1033 4
    public function dump($exit=false, array $options=[])
1034
    {
1035
        $default_options = [
1036 4
            'callstack_depth' => 3,
1037 4
            'mode'            => 'string',
1038 4
        ];
1039 4
        foreach ($default_options as $default_option => &$default_value) {
1040 4
            if (! isset($options[ $default_option ])) {
1041 4
                $options[ $default_option ] = $default_value;
1042 4
            }
1043 4
        }
1044 4
        extract($options);
1045
1046 4
        if ($this->rules) {
1047 4
            $this->rules->dump($exit, $options);
1048 4
        }
1049
        else {
1050
            // TODO dump a TrueRule
1051
            $bt     = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $callstack_depth);
1052
            $caller = $bt[ $callstack_depth - 2 ];
1053
1054
            // get line and file from the previous level of the caller
1055
            // TODO go deeper if this case exist?
1056 View Code Duplication
            if (! isset($caller['file'])) {
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...
1057
                $caller['file'] = $bt[ $callstack_depth - 3 ]['file'];
1058
            }
1059
1060 View Code Duplication
            if (! isset($caller['line'])) {
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...
1061
                $caller['line'] = $bt[ $callstack_depth - 3 ]['line'];
1062
            }
1063
1064
            try {
1065
                echo "\n" . $caller['file'] . ':' . $caller['line'] . "\n";
1066
                var_export($this->toArray($options));
1067
            }
1068
            catch (\Exception $e) {
1069
                echo "\nError while dumping: " . $e->getMessage() . "\n";
1070
                var_export($caller);
1071
                echo "\n\n";
1072
                var_export($bt);
1073
                echo "\n\n";
1074
                var_export($this->toArray($options));
1075
            }
1076
            echo "\n\n";
1077
1078
            if ($exit) {
1079
                exit;
1080
            }
1081
        }
1082
1083 4
        return $this;
1084
    }
1085
1086
    /**
1087
     * Applies the current instance to a set of data.
1088
     *
1089
     * @param  mixed                  $data_to_filter
1090
     * @param  Filterer|callable|null $filterer
1091
     *
1092
     * @return mixed The filtered data
1093
     */
1094 5
    public function applyOn($data_to_filter, $action_on_matches=null, $filterer=null)
0 ignored issues
show
Unused Code introduced by
The parameter $action_on_matches is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1095
    {
1096 5 View Code Duplication
        if (! $filterer) {
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...
1097 5
            $filterer = $this->getDefaultFilterer();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filterer. This often makes code more readable.
Loading history...
1098 5
        }
1099
        elseif (is_callable($filterer)) {
1100
            $filterer = new CustomizableFilterer($filterer);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filterer. This often makes code more readable.
Loading history...
1101
        }
1102
        elseif (! $filterer instanceof Filterer) {
1103
            throw new \InvalidArgumentException(
1104
                 "The given \$filterer must be null or a callable or a instance "
1105
                ."of Filterer instead of: ".var_export($filterer, true)
1106
            );
1107
        }
1108
1109 5
        if ($data_to_filter instanceof LogicalFilter) {
1110 2
            $filtered_rules = $filterer->apply( $this, $data_to_filter->getRules() );
0 ignored issues
show
Documentation introduced by
$data_to_filter->getRules() is of type object<JClaveau\LogicalF...\AbstractOperationRule>, but the function expects a object<JClaveau\LogicalFilter\Filterer\Iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1111 2
            return $data_to_filter->flushRules()->addRule( $filtered_rules );
1112
        }
1113
        else {
1114 3
            return $filterer->apply($this, $data_to_filter);
1115
        }
1116
    }
1117
1118
    /**
1119
     * Applies the current instance to a value (and its index optionnally).
1120
     *
1121
     * @param  mixed                  $value_to_check
1122
     * @param  scalar                 $index
0 ignored issues
show
Bug introduced by
There is no parameter named $index. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

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

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

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

Loading history...
1123
     * @param  Filterer|callable|null $filterer
1124
     *
1125
     * @return AbstractRule|false|true + False if the filter doesn't validates
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1126
     *                                 + Null if the target has no sens (operation filtered by field for example)
1127
     *                                 + A rule tree containing the first matching case if there is one.
1128
     */
1129 4
    public function validates($value_to_check, $key_to_check=null, $filterer=null)
1130
    {
1131 4 View Code Duplication
        if (! $filterer) {
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...
1132 4
            $filterer = $this->getDefaultFilterer();
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filterer. This often makes code more readable.
Loading history...
1133 4
        }
1134
        elseif (is_callable($filterer)) {
1135
            $filterer = new CustomizableFilterer($filterer);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filterer. This often makes code more readable.
Loading history...
1136
        }
1137
        elseif (! $filterer instanceof Filterer) {
1138
            throw new \InvalidArgumentException(
1139
                 "The given \$filterer must be null or a callable or a instance "
1140
                ."of Filterer instead of: ".var_export($filterer, true)
1141
            );
1142
        }
1143
1144 4
        return $filterer->hasMatchingCase($this, $value_to_check, $key_to_check);
1145
    }
1146
1147
    /**/
1148
}
1149