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

AndRule::simplifySameOperands()   F

Complexity

Conditions 23
Paths 19

Size

Total Lines 120

Duplication

Lines 55
Ratio 45.83 %

Code Coverage

Tests 65
CRAP Score 23.6964

Importance

Changes 0
Metric Value
cc 23
nc 19
nop 1
dl 55
loc 120
ccs 65
cts 73
cp 0.8904
crap 23.6964
rs 3.3333
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
namespace JClaveau\LogicalFilter\Rule;
3
use       JClaveau\VisibilityViolator\VisibilityViolator;
4
5
/**
6
 * Logical conjunction:
7
 * @see https://en.wikipedia.org/wiki/Logical_conjunction
8
 */
9
class AndRule extends AbstractOperationRule
10
{
11
    /** @var string operator */
12
    const operator = 'and';
13
14
    /**
15
     * Replace all the OrRules of the RuleTree by one OrRule at its root.
16
     *
17
     * @todo rename as RootifyDisjunjctions?
18
     * @todo return $this (implements a Rule monad?)
19
     *
20
     * @return OrRule copied operands with one OR at its root
21
     */
22 98
    public function rootifyDisjunctions(array $simplification_options)
23
    {
24 98
        if ( ! $this->isNormalizationAllowed($simplification_options)) {
25
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (JClaveau\LogicalFilter\Rule\AndRule) is incompatible with the return type documented by JClaveau\LogicalFilter\R...le::rootifyDisjunctions of type JClaveau\LogicalFilter\Rule\OrRule.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
26
        }
27
28 98
        $this->moveSimplificationStepForward( self::rootify_disjunctions, $simplification_options );
29
30 98
        $upLiftedOperands = [];
31 98
        foreach ($this->getOperands() as $operand) {
32 95
            $operand = $operand->copy();
33 95
            if ($operand instanceof AbstractOperationRule) {
34 56
                $operand = $operand->rootifyDisjunctions($simplification_options);
35 56
            }
36
37 95
            $upLiftedOperands[] = $operand;
38 98
        }
39
40
        // If the AndRule doesn't contain any OrRule , there is nothing to uplift
41
        if ( ! array_filter($upLiftedOperands, function($operand) {
42 95
            return $operand instanceof OrRule;
43 98
        })) {
44 87
            return new AndRule($upLiftedOperands);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \JClaveau\Log...ule($upLiftedOperands); (JClaveau\LogicalFilter\Rule\AndRule) is incompatible with the return type documented by JClaveau\LogicalFilter\R...le::rootifyDisjunctions of type JClaveau\LogicalFilter\Rule\OrRule.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
45
        }
46
47 47
        $firstAndOperand = new AndRule();
48
49
        // This OrRule should contain only AndRules during its generation
50 47
        $upLiftedOr = new OrRule([
51 47
            $firstAndOperand,
52 47
        ]);
53
54
        // var_dump($upLiftedOperands);
55
        // $this->dump(true);
56
57 47
        foreach ($upLiftedOperands as $i => $operand) {
58 47
            if ($operand instanceof NotRule) {
59 8
                if (    ($operand instanceof NotEqualRule || $operand instanceof NotInRule)
60 8
                    && ! $operand->isNormalizationAllowed($simplification_options)
61 8
                ) {
62 8
                    foreach ($upLiftedOr->getOperands() as $upLifdtedOperand) {
63 8
                        $upLifdtedOperand->addOperand( $operand->copy() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JClaveau\LogicalFilter\Rule\AbstractRule as the method addOperand() does only exist in the following sub-classes of JClaveau\LogicalFilter\Rule\AbstractRule: JClaveau\LogicalFilter\Rule\AboveOrEqualRule, JClaveau\LogicalFilter\Rule\AbstractOperationRule, JClaveau\LogicalFilter\Rule\AndRule, JClaveau\LogicalFilter\Rule\BelowOrEqualRule, JClaveau\LogicalFilter\Rule\BetweenOrEqualBothRule, JClaveau\LogicalFilter\R...BetweenOrEqualLowerRule, JClaveau\LogicalFilter\R...BetweenOrEqualUpperRule, JClaveau\LogicalFilter\Rule\BetweenRule, JClaveau\LogicalFilter\Rule\InRule, JClaveau\LogicalFilter\Rule\NotEqualRule, JClaveau\LogicalFilter\Rule\NotInRule, JClaveau\LogicalFilter\Rule\NotRule, JClaveau\LogicalFilter\Rule\OrRule. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
64 8
                    }
65 8
                }
66
                else {
67
                    throw new \LogicException(
68
                        "Rootifying disjunctions MUST be done after negations removal instead of '".$operand."' \n"
69
                        .$operand
70
                    );
71
                }
72 9
            }
73 47
            elseif ($operand instanceof OrRule && $operand->isNormalizationAllowed($simplification_options)) {
74
75
                // If an operand is an Or, me transform the current
76
                // (A' || A") && (B')       <=> (A' && B') || (A" && B');
77
                // (A' || A") && (B' || B") <=> (A' && B') || (A' && B") || (A" && B') || (A" && B");
78
                // (A' || A") && (B' || B") && (C' || C") <=>
79
                //    (A' && B' && C') || (A' && B' && C") || (A' && B" && C') || (A' && B" && C")
80
                // || (A" && B' && C') || (A" && B' && C") || (A" && B" && C') || (A" && B" && C");
81 40
                $newUpLiftedOr = new OrRule;
82 40
                foreach ($operand->getOperands() as $subOperand) {
83 38
                    foreach ($upLiftedOr->getOperands() as $upLiftedOrSubOperand) {
84 38
                        $newUpLiftedOrSubOperand = $upLiftedOrSubOperand->copy();
85 38
                        $newUpLiftedOrSubOperand->addOperand( $subOperand->copy() );
86 38
                        if ($newUpLiftedOrSubOperand->simplify($simplification_options)->hasSolution($simplification_options)) {
87 37
                            $newUpLiftedOr->addOperand( $newUpLiftedOrSubOperand );
88 37
                        }
89 38
                    }
90 40
                }
91
92 40
                $upLiftedOr = $newUpLiftedOr;
93 40
            }
94
            else {
95
                // append the operand to all the operands of the $upLiftedOr
96 28
                foreach ($upLiftedOr->getOperands() as $upLifdtedOperand) {
97 28
                    if ( ! $upLifdtedOperand instanceof AndRule) {
98
                        throw new \LogicException(
99
                             "Operands of the uplifted OrRule MUST be AndRules during"
100
                            ."the combination."
101
                        );
102
                    }
103
104 28
                    $upLifdtedOperand->addOperand( $operand->copy() );
105 28
                }
106
            }
107 47
        }
108
109 47
        return $upLiftedOr;
110
    }
111
112
    /**
113
     * @param array $options   + show_instance=false Display the operator of the rule or its instance id
114
     *
115
     * @return array
116
     *
117
     * @todo same as OrRule
118
     */
119 127 View Code Duplication
    public function toArray(array $options=[])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
120
    {
121
        $default_options = [
122 127
            'show_instance' => false,
123 127
            'sort_operands' => false,
124 127
            'semantic'      => false,
125 127
        ];
126 127
        foreach ($default_options as $default_option => &$default_value) {
127 127
            if ( ! isset($options[ $default_option ])) {
128 127
                $options[ $default_option ] = $default_value;
129 127
            }
130 127
        }
131
132 127
        if ( ! $options['show_instance'] && ! empty($this->cache['array'])) {
133 5
            return $this->cache['array'];
134
        }
135
136
        $operands_as_array = [
137 127
            $options['show_instance'] ? $this->getInstanceId() : self::operator,
138 127
        ];
139
140 127
        $operands = $this->operands;
141 127
        if ($options['semantic']) {
142
            // Semantic array: ['operator', 'semantic_id_of_operand1', 'semantic_id_of_operand2', ...]
143
            // with sorted semantic ids
144 122
            $operands_semantic_ids = array_keys($operands);
145 122
            sort($operands_semantic_ids);
146 122
            return array_merge(
147 122
                [self::operator],
148
                $operands_semantic_ids
149 122
            );
150
        }
151
        else {
152 68
            foreach ($operands as $operand) {
153 60
                $operands_as_array[] = $operand->toArray($options);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JClaveau\LogicalFilter\Rule\AbstractRule as the method toArray() does only exist in the following sub-classes of JClaveau\LogicalFilter\Rule\AbstractRule: JClaveau\LogicalFilter\Rule\AboveOrEqualRule, JClaveau\LogicalFilter\Rule\AboveRule, JClaveau\LogicalFilter\Rule\AbstractAtomicRule, JClaveau\LogicalFilter\Rule\AndRule, JClaveau\LogicalFilter\Rule\BelowOrEqualRule, JClaveau\LogicalFilter\Rule\BelowRule, JClaveau\LogicalFilter\Rule\BetweenOrEqualBothRule, JClaveau\LogicalFilter\R...BetweenOrEqualLowerRule, JClaveau\LogicalFilter\R...BetweenOrEqualUpperRule, JClaveau\LogicalFilter\Rule\BetweenRule, JClaveau\LogicalFilter\Rule\EqualRule, JClaveau\LogicalFilter\Rule\InRule, JClaveau\LogicalFilter\Rule\NotEqualRule, JClaveau\LogicalFilter\Rule\NotInRule, JClaveau\LogicalFilter\Rule\NotRule, JClaveau\LogicalFilter\Rule\OrRule, JClaveau\LogicalFilter\Rule\RegexpRule. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
154 68
            }
155
156 68
            if ( ! $options['show_instance']) {
157 68
                return $this->cache['array'] = $operands_as_array;
158
            }
159
            else {
160
                return $operands_as_array;
161
            }
162
        }
163
    }
164
165
    /**
166
     */
167 4 View Code Duplication
    public function toString(array $options=[])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
168
    {
169 4
        $operator = self::operator;
170 4
        if ( ! $this->operands) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->operands of type JClaveau\LogicalFilter\Rule\AbstractRule[] 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...
171 1
            return $this->cache['string'] = "['{$operator}']";
172
        }
173
174 3
        $indent_unit = isset($options['indent_unit']) ? $options['indent_unit'] : '';
175 3
        $line_break  = $indent_unit ? "\n" : '';
176
177 3
        $out = "['{$operator}',$line_break";
178
179 3
        foreach ($this->operands as $operand) {
180
            $out .= implode("\n", array_map(function($line) use (&$indent_unit) {
181 3
                return $indent_unit.$line;
182 3
            }, explode("\n", $operand->toString($options)) )) . ",$line_break";
0 ignored issues
show
Bug introduced by
The method toString() does not exist on JClaveau\LogicalFilter\Rule\AbstractRule. Did you maybe mean __toString()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
183 3
        }
184
185 3
        $out .= ']';
186
187 3
        return $this->cache['string'] = $out;
188
    }
189
190
    /**
191
     * Remove AndRules operands of AndRules
192
     */
193 99 View Code Duplication
    public function removeSameOperationOperands()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
194
    {
195 99
        foreach ($this->operands as $i => $operand) {
196 99
            if ( ! is_a($operand, AndRule::class)) {
197 99
                continue;
198
            }
199
200 58
            if ( ! $operands = $operand->getOperands()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JClaveau\LogicalFilter\Rule\AbstractRule as the method getOperands() does only exist in the following sub-classes of JClaveau\LogicalFilter\Rule\AbstractRule: JClaveau\LogicalFilter\Rule\AboveOrEqualRule, JClaveau\LogicalFilter\Rule\AbstractOperationRule, JClaveau\LogicalFilter\Rule\AndRule, JClaveau\LogicalFilter\Rule\BelowOrEqualRule, JClaveau\LogicalFilter\Rule\BetweenOrEqualBothRule, JClaveau\LogicalFilter\R...BetweenOrEqualLowerRule, JClaveau\LogicalFilter\R...BetweenOrEqualUpperRule, JClaveau\LogicalFilter\Rule\BetweenRule, JClaveau\LogicalFilter\Rule\InRule, JClaveau\LogicalFilter\Rule\NotEqualRule, JClaveau\LogicalFilter\Rule\NotInRule, JClaveau\LogicalFilter\Rule\NotRule, JClaveau\LogicalFilter\Rule\OrRule. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
201 1
                continue;
202
            }
203
204
            // Id AND is an operand on AND they can be merge (and the same with OR)
205 58
            foreach ($operands as $sub_operand) {
206 58
                $this->addOperand( $sub_operand->copy() );
207 58
            }
208 58
            unset($this->operands[$i]);
209
210
            // possibility of mono-operand or dupicates
211 58
            $has_been_changed = true;
212 99
        }
213
214 99
        return ! empty($has_been_changed);
215
    }
216
217
    /**
218
     * Removes rule branches that cannot produce result like:
219
     * A = 1 || (B < 2 && B > 3) <=> A = 1
220
     *
221
     * @return AndRule $this
222
     */
223 93
    public function removeInvalidBranches(array $simplification_options)
224
    {
225 93
        if ( ! $this->isNormalizationAllowed($simplification_options)) {
226
            return $this;
227
        }
228
229 93
        $this->moveSimplificationStepForward(self::remove_invalid_branches, $simplification_options);
230
231 93
        foreach ($this->operands as $i => $operand) {
232
            // if ($operand instanceof AndRule || $operand instanceof OrRule ) {
233 89
            if ( in_array( get_class($operand), [AndRule::class, OrRule::class]) ) {
234 1
                $this->operands[$i] = $operand->removeInvalidBranches($simplification_options);
235 1
                if ( ! $this->operands[$i]->hasSolution()) {
236 1
                    $this->operands = [];
237 1
                    return $this;
238
                }
239
            }
240 93
        }
241
242 93
        $operandsByFields = $this->groupOperandsByFieldAndOperator();
243
244
        // $this->dump(true);
245
246 93
        foreach ($operandsByFields as $field => $operandsByOperator) {
247 88
            if ( ! empty($operandsByOperator[ EqualRule::operator ])) {
248 64
                foreach ($operandsByOperator[ EqualRule::operator ] as $equalRule) {
249
                    // Multiple equal rules without the same value is invalid
250 64
                    if (isset($previousEqualRule) && $previousEqualRule->getValue() != $equalRule->getValue()) {
251
                        $this->operands = [];
252
                        return $this;
253
                    }
254 64
                    $previousEqualRule = $equalRule;
255 64
                }
256 64
                unset($previousEqualRule);
257
258 64
                $equalRule = reset($operandsByOperator[ EqualRule::operator ]);
259
260 64 View Code Duplication
                if (   ! empty($operandsByOperator[ BelowRule::operator ])
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...
261 64
                    && null === $equalRule->getValue()
262 64
                ) {
263 1
                    $this->operands = [];
264 1
                    return $this;
265
                }
266
267 64 View Code Duplication
                if (   ! empty($operandsByOperator[ BelowRule::operator ])
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...
268 64
                    && $equalRule->getValue() >= reset($operandsByOperator[ BelowRule::operator ])->getMaximum()
269 64
                ) {
270 7
                    $this->operands = [];
271 7
                    return $this;
272
                }
273
274 64 View Code Duplication
                if (   ! empty($operandsByOperator[ AboveRule::operator ])
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...
275 64
                    && null === $equalRule->getValue()
276 64
                ) {
277 1
                    $this->operands = [];
278 1
                    return $this;
279
                }
280
281 63 View Code Duplication
                if (   ! empty($operandsByOperator[ AboveRule::operator ])
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...
282 63
                    && $equalRule->getValue() <= reset($operandsByOperator[ AboveRule::operator ])->getMinimum()
283 63
                ) {
284 8
                    $this->operands = [];
285 8
                    return $this;
286
                }
287
288 58 View Code Duplication
                if (   ! empty($operandsByOperator[ NotEqualRule::operator ])
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...
289 58
                    && $equalRule->getValue() == reset($operandsByOperator[ NotEqualRule::operator ])->getValue()
290 58
                ) {
291 2
                    $this->operands = [];
292 2
                    return $this;
293
                }
294
295 57
                if (   ! empty($operandsByOperator[ NotEqualRule::operator ])
296 57
                    && null === $equalRule->getValue()
297 57
                    && null === reset($operandsByOperator[ NotEqualRule::operator ])->getValue()
298 57
                ) {
299
                    $this->operands = [];
300
                    return $this;
301
                }
302 57
            }
303 54
            elseif (   ! empty($operandsByOperator[ BelowRule::operator ])
304 54
                    && ! empty($operandsByOperator[ AboveRule::operator ])) {
305 14
                $aboveRule = reset($operandsByOperator[ AboveRule::operator ]);
306 14
                $belowRule = reset($operandsByOperator[ BelowRule::operator ]);
307
308 14
                if ($belowRule->getMaximum() <= $aboveRule->getMinimum()) {
309 12
                    $this->operands = [];
310 12
                    return $this;
311
                }
312 6
            }
313 87
        }
314
315 87
        return $this;
316
    }
317
318
    /**
319
     * Checks if a simplified AndRule has incompatible operands like:
320
     * + a = 3 && a > 4
321
     * + a = 3 && a < 2
322
     * + a > 3 && a < 2
323
     *
324
     * @return bool If the AndRule can have a solution or not
325
     */
326 19
    public function hasSolution(array $contextual_options=[])
0 ignored issues
show
Unused Code introduced by
The parameter $contextual_options 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...
327
    {
328 19 View Code Duplication
        if ( ! $this->simplicationStepReached(self::simplified)) {
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...
329
            throw new \LogicException(
330
                "hasSolution has no sens if the rule is not simplified instead of being at: "
331
                .var_export($this->current_simplification_step, true)
332
            );
333
        }
334
335
        // atomic rules
336 19
        foreach ($this->getOperands() as $operand) {
337 12
            if (method_exists($operand, 'hasSolution') && ! $operand->hasSolution()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class JClaveau\LogicalFilter\Rule\AbstractRule as the method hasSolution() does only exist in the following sub-classes of JClaveau\LogicalFilter\Rule\AbstractRule: JClaveau\LogicalFilter\Rule\AboveOrEqualRule, JClaveau\LogicalFilter\Rule\AboveRule, JClaveau\LogicalFilter\Rule\AndRule, JClaveau\LogicalFilter\Rule\BelowOrEqualRule, JClaveau\LogicalFilter\Rule\BelowRule, JClaveau\LogicalFilter\Rule\BetweenOrEqualBothRule, JClaveau\LogicalFilter\R...BetweenOrEqualLowerRule, JClaveau\LogicalFilter\R...BetweenOrEqualUpperRule, JClaveau\LogicalFilter\Rule\BetweenRule, JClaveau\LogicalFilter\Rule\EqualRule, JClaveau\LogicalFilter\Rule\InRule, JClaveau\LogicalFilter\Rule\NotEqualRule, JClaveau\LogicalFilter\Rule\NotInRule, JClaveau\LogicalFilter\Rule\NotRule, JClaveau\LogicalFilter\Rule\OrRule, JClaveau\LogicalFilter\Rule\RegexpRule. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
338
                return false;
339
            }
340 19
        }
341
342 19
        return ! empty($this->getOperands());
343
    }
344
345
346
    /**
347
     * + if A > 2 && A > 1 <=> A > 2
348
     * + if A < 2 && A < 1 <=> A < 1
349
     */
350 98
    protected static function simplifySameOperands(array $operandsByFields)
351
    {
352
        // unifying same operands
353 98
        foreach ($operandsByFields as $field => $operandsByOperator) {
354 98
            foreach ($operandsByOperator as $operator => $operands) {
355 98
                unset($previous_operand);
356
357
                try {
358 98
                    if (AboveRule::operator == $operator) {
359
                        usort($operands, function( AboveRule $a, AboveRule $b ) {
360 9
                            if (null === $a->getMinimum()) {
0 ignored issues
show
Deprecated Code introduced by
The method JClaveau\LogicalFilter\R...AboveRule::getMinimum() has been deprecated with message: getLowerLimit

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
361
                                return 1;
362
                            }
363
364 9
                            if (null === $b->getMinimum()) {
0 ignored issues
show
Deprecated Code introduced by
The method JClaveau\LogicalFilter\R...AboveRule::getMinimum() has been deprecated with message: getLowerLimit

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
365 1
                                return -1;
366
                            }
367
368 9
                            if ($a->getMinimum() > $b->getMinimum()) {
0 ignored issues
show
Deprecated Code introduced by
The method JClaveau\LogicalFilter\R...AboveRule::getMinimum() has been deprecated with message: getLowerLimit

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
369 9
                                return -1;
370
                            }
371
372 1
                            return 1;
373 44
                        });
374 44
                        $operands = [reset($operands)];
375 44
                    }
376 92 View Code Duplication
                    elseif (BelowRule::operator == $operator) {
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...
377
                        usort($operands, function( BelowRule $a, BelowRule $b ) {
378 7
                            if (null === $a->getMaximum()) {
0 ignored issues
show
Deprecated Code introduced by
The method JClaveau\LogicalFilter\R...BelowRule::getMaximum() has been deprecated with message: getUpperLimit

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
379 1
                                return 1;
380
                            }
381
382 7
                            if (null === $b->getMaximum()) {
0 ignored issues
show
Deprecated Code introduced by
The method JClaveau\LogicalFilter\R...BelowRule::getMaximum() has been deprecated with message: getUpperLimit

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
383
                                return -1;
384
                            }
385
386 7
                            if ($a->getMaximum() < $b->getMaximum()) {
0 ignored issues
show
Deprecated Code introduced by
The method JClaveau\LogicalFilter\R...BelowRule::getMaximum() has been deprecated with message: getUpperLimit

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
387 2
                                return -1;
388
                            }
389
390 6
                            return 1;
391 36
                        });
392 36
                        $operands = [reset($operands)];
393 36
                    }
394 90
                    elseif (EqualRule::operator == $operator) {
395
                        // TODO add an option for the support strict comparison
396 69
                        foreach ($operands as $i => $operand) {
397 69
                            if ( ! isset($previous_operand)) {
398 69
                                $previous_operand = $operand;
399 69
                                continue;
400
                            }
401
402 5
                            if ($previous_operand == $operand) {
403 1
                                unset($operands[$i]);
404 1
                                continue;
405
                            }
406
                            else {
407
                                // Same field expected to be two differents
408
                                // values at the same time has no sens so
409
                                // we remove all the operands of the current
410
                                // AndRule (TODO FalseRule)
411 4
                                return [];
412
                            }
413 66
                        }
414 66
                    }
415 63 View Code Duplication
                    elseif (InRule::operator == $operator) {
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...
416 19
                        $first_in = reset($operands);
417
418 19
                        foreach ($operands as $i => $next_in) {
419 19
                            if ($first_in === $next_in) {
420 19
                                continue;
421
                            }
422
423 4
                            $first_in->setPossibilities( array_intersect(
424 4
                                $first_in->getPossibilities(),
425 4
                                $next_in->getPossibilities()
426 4
                            ) );
427
428 4
                            unset($operands[$i]);
429 19
                        }
430
431
                        // [field in []] <=> false
432 19
                        if ( ! $first_in->getPossibilities()) {
433
                            return [];
434
                        }
435 19
                    }
436 50 View Code Duplication
                    elseif (NotInRule::operator == $operator) {
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...
437 14
                        $first_not_in = reset($operands);
438
439 14
                        foreach ($operands as $i => $next_not_in) {
440 14
                            if ($first_not_in === $next_not_in) {
441 14
                                continue;
442
                            }
443
444 2
                            $first_not_in->setPossibilities( array_merge(
445 2
                                $first_not_in->getPossibilities(),
446 2
                                $next_not_in->getPossibilities()
447 2
                            ) );
448
449 2
                            unset($operands[$i]);
450 14
                        }
451 14
                    }
452
                }
453 95
                catch (\Exception $e) {
454
                    VisibilityViolator::setHiddenProperty($e, 'message', $e->getMessage() . "\n" . var_export([
0 ignored issues
show
Documentation introduced by
'message' is of type string, but the function expects a object<JClaveau\VisibilityViolator\of>.

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...
Documentation introduced by
$e->getMessage() . ' ' ....s' => $operands), true) is of type string, but the function expects a object<JClaveau\VisibilityViolator\the>.

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...
455
                            'operands' => $operands,
456
                            // 'this'     => $this,
457
                        ], true)
458
                    );
459
460
                    // \Debug::dumpJson($this->toArray(), true);
461
                    throw $e;
462
                }
463
464 95
                $operandsByFields[ $field ][ $operator ] = $operands;
465 95
            }
466 98
        }
467
468 98
        return $operandsByFields;
469
    }
470
471
    /**
472
     */
473 98
    protected static function simplifyDifferentOperands(array $operandsByFields)
474
    {
475 98
        foreach ($operandsByFields as $field => &$operandsByOperator) {
476
            foreach ([
477 95
                    EqualRule::operator,
478 95
                    AboveRule::operator,
479 95
                    AboveRule::operator,
480 95
                    InRule::operator,
481 95
                    NotInRule::operator,
482 95
                    BelowOrEqualRule::operator,
483 95
                    AboveOrEqualRule::operator,
484
                ]
485
                as $unifyable_operator
486 95
            ) {
487 95
                if ( ! empty($operandsByOperator[ $unifyable_operator ])) {
488 92
                    if (1 != count($operandsByOperator[ $unifyable_operator ])) {
489
                        throw new \LogicException(
490
                            __METHOD__ . " MUST be called after unifyAtomicOperands() "
491
                            ."to have only one '$unifyable_operator' predicate istead of:\n"
492
                            ."[\n".implode( ",\n", array_map(function ($rule) {
493
                                return $rule->toString();
494
                            }, $operandsByOperator[ $unifyable_operator ])
495
                            )."\n]"
496
                        );
497
                    }
498 92
                }
499 95
            }
500
501 95
            $operandsByOperator = self::simplifyDifferentOperandsForField($field, $operandsByOperator);
502
            // If tyhere is no more operands for a given field it means there
503
            // is no possible solutions for it so all the current and_case
504
            // is invalidated.
505 95
            if ( ! $operandsByOperator) {
506 2
                return [];
507
            }
508 98
        }
509
510 98
        return $operandsByFields;
511
    }
512
513
    /**
514
     * + if A = 2 && A > 1 <=> A = 2
515
     * + if A = 2 && A < 4 <=> A = 2
516
     */
517 95
    protected static function simplifyDifferentOperandsForField($field, array $operandsByOperator)
518
    {
519
        // EqualRule comparisons
520 95
        if ( ! empty($operandsByOperator[ EqualRule::operator ])) {
521 66
            $equalRule = reset( $operandsByOperator[ EqualRule::operator ] );
522
523 66
            if ( ! empty($operandsByOperator[ NotEqualRule::operator ])) {
524 4
                foreach ($operandsByOperator[ NotEqualRule::operator ] as $i => $not_equal_rule) {
525 4
                    if (null !== $equalRule->getValue()) {
526 3
                        if (null === $not_equal_rule->getValue()) { // means if exists <=> equals something
527 2
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
528 2
                        }
529 1
                        elseif ($not_equal_rule->getValue() != $equalRule->getValue()) {
530
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
531
                        }
532 3
                    }
533 1
                    elseif (null === $equalRule->getValue() ) {
534 1
                        if (null !== $not_equal_rule->getValue()) {
535
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
536
                        }
537
                        // else we let the "equal null" and the "not equal null" for the romeInvalidBranches step
538 1
                    }
539 4
                }
540 4
            }
541
542 66 View Code Duplication
            if ( ! empty($operandsByOperator[ AboveRule::operator ])) {
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...
543 12
                $aboveRule = reset($operandsByOperator[ AboveRule::operator ]);
544 12
                if (null !== $equalRule->getValue() && $aboveRule->getMinimum() < $equalRule->getValue()) {
545 3
                    unset($operandsByOperator[ AboveRule::operator ]);
546 3
                }
547 12
            }
548
549 66 View Code Duplication
            if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
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...
550 11
                $belowRule = reset($operandsByOperator[ BelowRule::operator ]);
551 11
                if (null !== $equalRule->getValue() && $belowRule->getMaximum() > $equalRule->getValue()) {
552 3
                    unset($operandsByOperator[ BelowRule::operator ]);
553 3
                }
554 11
            }
555
556 66
            if ( ! empty($operandsByOperator[ InRule::operator ])) {
557 3
                $possibilities = reset($operandsByOperator[ InRule::operator ])->getPossibilities();
558
559 3
                if (in_array($equalRule->getValue(), $possibilities)) {
560 3
                    unset($operandsByOperator[ InRule::operator ]);
561 3
                }
562
                else {
563
                    // We flush possibilities of the InRule
564
                    // TODO Replace it by a FalseRule
565 1
                    $operandsByOperator[ InRule::operator ][0]->setPossibilities([]);
566
                    // and also remove the equal rule to shorten the reste of the simplification process
567 1
                    unset($operandsByOperator[ EqualRule::operator ]);
568
                }
569 3
            }
570
571 66
            if ( ! empty($operandsByOperator[ NotInRule::operator ])) {
572 2
                $notInRule = reset($operandsByOperator[ NotInRule::operator ]);
573 2
                if (in_array($equalRule->getValue(), $notInRule->getPossibilities())) {
574
                    // ['field', '=', 4] && ['field', '!in', [4]...] <=> false
575 2
                    return [];
576
                }
577
                else {
578 1
                    unset($operandsByOperator[ NotInRule::operator ]);
579
                }
580
                // $notInRule->dump(true);
581 1
            }
582
583 66 View Code Duplication
            if ( ! empty($operandsByOperator[ BelowOrEqualRule::operator ])) {
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...
584 1
                $belowOrEqualRule = reset($operandsByOperator[ BelowOrEqualRule::operator ]);
585 1
                if ($equalRule->getValue() <= $belowOrEqualRule->getMaximum()) {
586 1
                    unset($operandsByOperator[ BelowOrEqualRule::operator ]);
587 1
                }
588
                else {
589
                    // ['field', '=', 4] && ['field', '<=', [3]...] <=> false
590
                    return [];
591
                }
592 1
            }
593
594 66 View Code Duplication
            if ( ! empty($operandsByOperator[ AboveOrEqualRule::operator ])) {
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...
595 1
                $aboveOrEqualRule = reset($operandsByOperator[ AboveOrEqualRule::operator ]);
596 1
                if ($equalRule->getValue() >= $aboveOrEqualRule->getMinimum()) {
597 1
                    unset($operandsByOperator[ AboveOrEqualRule::operator ]);
598 1
                }
599
                else {
600
                    // ['field', '=', 4] && ['field', '<=', [3]...] <=> false
601
                    return [];
602
                }
603 1
            }
604 66
        }
605
606
        // NotEqualRule null comparisons
607 95
        if ( ! empty($operandsByOperator[ NotEqualRule::operator ])) {
608 18
            if ( ! empty($operandsByOperator[ NotEqualRule::operator ])) {
609 18
                foreach ($operandsByOperator[ NotEqualRule::operator ] as $i => $notEqualRule) {
610 18
                    if (null === $notEqualRule->getValue()) {
611 8
                        if ( ! empty($operandsByOperator[ AboveRule::operator ])) {
612 2
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
613 2
                        }
614
615 8
                        if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
616 2
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
617 2
                        }
618
619 8
                        if ( ! empty($operandsByOperator[ EqualRule::operator ])) {
620 1
                            if (null !== reset($operandsByOperator[ EqualRule::operator ])->getValue()) {
621
                                unset($operandsByOperator[ NotEqualRule::operator ][$i]);
622
                            }
623 1
                        }
624 8
                    }
625
                    else {
626 10 View Code Duplication
                        if ( ! empty($operandsByOperator[ AboveRule::operator ])) {
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...
627 2
                            if ($operandsByOperator[ AboveRule::operator ][0]->getMinimum() >= $notEqualRule->getValue()) {
628 2
                                unset($operandsByOperator[ NotEqualRule::operator ][$i]);
629 2
                            }
630 2
                        }
631
632 10 View Code Duplication
                        if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
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...
633
                            if ($operandsByOperator[ BelowRule::operator ][0]->getMaximum() <= $notEqualRule->getValue()) {
634
                                unset($operandsByOperator[ NotEqualRule::operator ][$i]);
635
                            }
636
                        }
637
                    }
638
639 18
                    if ( ! empty($operandsByOperator[ NotInRule::operator ])) {
640 2
                        $notInRule = reset($operandsByOperator[ NotInRule::operator ]);
641 2
                        if ( ! in_array($notEqualRule->getValue(), $notInRule->getPossibilities())) {
642
                            // TODO Replace it by a FalseRule
643 2
                            $operandsByOperator[ NotInRule::operator ][0]->setPossibilities(
644 2
                                array_merge($notInRule->getPossibilities(), [$notEqualRule->getValue()])
645 2
                            );
646 2
                        }
647
648 2
                        unset($operandsByOperator[ NotEqualRule::operator ][$i]);
649 2
                    }
650
651 18
                    if ( ! empty($operandsByOperator[ InRule::operator ])) {
652
                        $inRule = reset($operandsByOperator[ InRule::operator ]);
653
654
                        $operandsByOperator[ InRule::operator ][0]->setPossibilities(
655
                            array_diff($inRule->getPossibilities(), [$notEqualRule->getValue()])
656
                        );
657
                    }
658 18
                }
659 18
            }
660 18
        }
661
662
        // Comparison between InRules and NotInRules
663
        // This is an optimization to avoid NotIn explosion
664 95
        if ( ! empty($operandsByOperator[ InRule::operator ])) {
665 18
            $inRule = $operandsByOperator[ InRule::operator ][0];
666
667 18 View Code Duplication
            if ( ! empty($operandsByOperator[ NotInRule::operator ])) {
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...
668 2
                $notInRule = reset($operandsByOperator[ NotInRule::operator ]);
669 2
                $operandsByOperator[ InRule::operator ][0]->setPossibilities(
670 2
                    array_diff( $inRule->getPossibilities(), $notInRule->getPossibilities())
671 2
                );
672 2
                unset($operandsByOperator[ NotInRule::operator ]);
673 2
            }
674
675 18 View Code Duplication
            if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
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...
676 2
                $upper_limit = reset($operandsByOperator[ BelowRule::operator ])->getMaximum();
677
678 2
                $operandsByOperator[ InRule::operator ][0]->setPossibilities(
679
                    array_filter( $inRule->getPossibilities(), function ($possibility) use ($upper_limit) {
680 2
                        return $possibility < $upper_limit;
681 2
                    } )
682 2
                );
683
684 2
                unset($operandsByOperator[ BelowRule::operator ]);
685 2
            }
686
687 18 View Code Duplication
            if ( ! empty($operandsByOperator[ AboveRule::operator ])) {
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...
688 1
                $lower_limit = reset($operandsByOperator[ AboveRule::operator ])->getMinimum();
689
690 1
                $operandsByOperator[ InRule::operator ][0]->setPossibilities(
691
                    array_filter( $inRule->getPossibilities(), function ($possibility) use ($lower_limit) {
692 1
                        return $possibility > $lower_limit;
693 1
                    } )
694 1
                );
695
696 1
                unset($operandsByOperator[ AboveRule::operator ]);
697 1
            }
698 18
        }
699
700
        // Comparison between NotInRules and > or <
701 95
        if ( ! empty($operandsByOperator[ NotInRule::operator ])) {
702 11
            $notInRule = $operandsByOperator[ NotInRule::operator ][0];
703
704 11 View Code Duplication
            if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
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...
705 1
                $upper_limit = reset($operandsByOperator[ BelowRule::operator ])->getUpperLimit();
706
707 1
                $operandsByOperator[ NotInRule::operator ][0]->setPossibilities(
708
                    array_filter( $notInRule->getPossibilities(), function ($possibility) use ($upper_limit) {
709 1
                        return $possibility < $upper_limit;
710 1
                    } )
711 1
                );
712 1
            }
713
714 11 View Code Duplication
            if ( ! empty($operandsByOperator[ AboveRule::operator ])) {
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...
715
                $lower_limit = reset($operandsByOperator[ AboveRule::operator ])->getMinimum();
716
717
                $operandsByOperator[ NotInRule::operator ][0]->setPossibilities(
718
                    array_filter( $notInRule->getPossibilities(), function ($possibility) use ($lower_limit) {
719
                        return $possibility > $lower_limit;
720
                    } )
721
                );
722
            }
723 11
        }
724
725
        // Comparison between <= and > or <
726 95
        if ( ! empty($operandsByOperator[ BelowOrEqualRule::operator ])) {
727 5
            $belowOrEqualRule = $operandsByOperator[ BelowOrEqualRule::operator ][0];
728
729 5
            if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
730
                $upper_limit = reset($operandsByOperator[ BelowRule::operator ])->getUpperLimit();
731
732
                if ($belowOrEqualRule->getMaximum() >= $upper_limit) {
733
                    // [field < 3] && [field <= 3]
734
                    // [field < 3] && [field <= 4]
735
                    unset($operandsByOperator[ BelowOrEqualRule::operator ][0]);
736
                }
737
                else {
738
                    // [field < 3] && [field <= 2]
739
                    unset($operandsByOperator[ BelowRule::operator ][0]);
740
                }
741
            }
742
743 5 View Code Duplication
            if ( ! empty($operandsByOperator[ AboveRule::operator ])) {
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...
744 1
                $lower_limit = reset($operandsByOperator[ AboveRule::operator ])->getLowerLimit();
745
746 1
                if ($belowOrEqualRule->getMaximum() <= $lower_limit) {
747
                    // [field > 3] && [field <= 2] <=> false
748
                    return [];
749
                }
750 1
            }
751
752 5
            if ( ! empty($operandsByOperator[ AboveOrEqualRule::operator ])) {
753 2
                $minimum = reset($operandsByOperator[ AboveOrEqualRule::operator ])->getMinimum();
754
755 2
                if ($belowOrEqualRule->getMaximum() < $minimum) {
756
                    // [field <= 3] && [field >= 4] <=> false
757
                    return [];
758
                }
759 2
                elseif ($belowOrEqualRule->getMaximum() == $minimum) {
760
                    // [field <= 3] && [field >= 3] <=> [field = 3]
761 1
                    unset($operandsByOperator[ BelowOrEqualRule::operator ]);
762 1
                    unset($operandsByOperator[ AboveOrEqualRule::operator ]);
763 1
                    $operandsByOperator[ EqualRule::operator ][] = new EqualRule($field, $minimum);
764
765 1
                    if (count($operandsByOperator[ EqualRule::operator ]) > 1) {
766
                        $operandsByOperator = self::simplifyDifferentOperandsForField($field, $operandsByOperator);
767
                    }
768 1
                }
769 2
            }
770 5
        }
771
772 95
        return $operandsByOperator;
773
    }
774
775
    /**
776
     * This method is meant to be used during simplification that would
777
     * need to change the class of the current instance by a normal one.
778
     *
779
     * @return AndRule The current instance (of or or subclass) or a new AndRule
780
     */
781 101
    public function setOperandsOrReplaceByOperation($new_operands)
782
    {
783
        try {
784 101
            return $this->setOperands( $new_operands );
785
        }
786
        catch (\LogicException $e) {
787
            return new AndRule( $new_operands );
788
        }
789
    }
790
791
    /**/
792
}
793