AndRule::toString()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 22

Duplication

Lines 22
Ratio 100 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
nc 9
nop 1
dl 22
loc 22
ccs 10
cts 10
cp 1
crap 5
rs 9.2568
c 0
b 0
f 0
1
<?php
2
namespace JClaveau\LogicalFilter\Rule;
3
4
use       JClaveau\VisibilityViolator\VisibilityViolator;
5
6
/**
7
 * Logical conjunction:
8
 * @see https://en.wikipedia.org/wiki/Logical_conjunction
9
 */
10
class AndRule extends AbstractOperationRule
11
{
12
    /** @var string operator */
13
    const operator = 'and';
14
15
    /**
16
     * Replace all the OrRules of the RuleTree by one OrRule at its root.
17
     *
18
     * @todo rename as RootifyDisjunjctions?
19
     * @todo return $this (implements a Rule monad?)
20
     *
21
     * @param  array $simplification_options
22
     * @return AndRule|OrRule The copied operands with one OR at its root
23
     */
24 151
    public function rootifyDisjunctions(array $simplification_options)
25
    {
26 151
        if ( ! $this->isNormalizationAllowed($simplification_options)) {
27
            return $this;
28
        }
29
30 151
        $this->moveSimplificationStepForward(self::rootify_disjunctions, $simplification_options);
31
32 151
        $upLiftedOperands = [];
33 151
        foreach ($this->getOperands() as $operand) {
34 148
            $operand = $operand->copy();
35 148
            if ($operand instanceof AbstractOperationRule) {
36 89
                $operand = $operand->rootifyDisjunctions($simplification_options);
37 89
            }
38
39 148
            $upLiftedOperands[] = $operand;
40 151
        }
41
42
        // If the AndRule doesn't contain any OrRule , there is nothing to uplift
43
        if ( ! array_filter($upLiftedOperands, function($operand) {
44 148
            return $operand instanceof OrRule;
45 151
        })) {
46 125
            return new AndRule($upLiftedOperands);
47
        }
48
49 72
        $firstAndOperand = new AndRule();
50
51
        // This OrRule should contain only AndRules during its generation
52 72
        $upLiftedOr = new OrRule([
53 72
            $firstAndOperand,
54 72
        ]);
55
56
        // var_dump($upLiftedOperands);
57
        // $this->dump(true);
58
59 72
        foreach ($upLiftedOperands as $i => $operand) {
60 72
            if ($operand instanceof NotRule) {
61 6
                if (    ($operand instanceof NotEqualRule || $operand instanceof NotInRule)
62 6
                    && ! $operand->isNormalizationAllowed($simplification_options)
63 6
                ) {
64 6
                    foreach ($upLiftedOr->getOperands() as $upLifdtedOperand) {
65 6
                        $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...
66 6
                    }
67 6
                }
68
                else {
69
                    throw new \LogicException(
70
                        "Rootifying disjunctions MUST be done after negations removal instead of '".$operand."' \n"
71
                        .$operand
72 1
                    );
73
                }
74 6
            }
75 72
            elseif ($operand instanceof OrRule && $operand->isNormalizationAllowed($simplification_options)) {
76
77
                // If an operand is an Or, me transform the current
78
                // (A' || A") && (B')       <=> (A' && B') || (A" && B');
79
                // (A' || A") && (B' || B") <=> (A' && B') || (A' && B") || (A" && B') || (A" && B");
80
                // (A' || A") && (B' || B") && (C' || C") <=>
81
                //    (A' && B' && C') || (A' && B' && C") || (A' && B" && C') || (A' && B" && C")
82
                // || (A" && B' && C') || (A" && B' && C") || (A" && B" && C') || (A" && B" && C");
83 46
                $newUpLiftedOr = new OrRule;
84 46
                foreach ($operand->getOperands() as $subOperand) {
85 44
                    foreach ($upLiftedOr->getOperands() as $upLiftedOrSubOperand) {
86 44
                        $newUpLiftedOrSubOperand = $upLiftedOrSubOperand->copy();
87 44
                        $newUpLiftedOrSubOperand->addOperand( $subOperand->copy() );
88 44
                        if ($newUpLiftedOrSubOperand->simplify($simplification_options)->hasSolution($simplification_options)) {
89 43
                            $newUpLiftedOr->addOperand( $newUpLiftedOrSubOperand );
90 43
                        }
91 44
                    }
92 46
                }
93
94 46
                $upLiftedOr = $newUpLiftedOr;
95 46
            }
96
            else {
97
                // append the operand to all the operands of the $upLiftedOr
98 52
                foreach ($upLiftedOr->getOperands() as $upLifdtedOperand) {
99 52
                    if ( ! $upLifdtedOperand instanceof AndRule) {
100
                        throw new \LogicException(
101
                             "Operands of the uplifted OrRule MUST be AndRules during"
102
                            ."the combination."
103
                        );
104
                    }
105
106 52
                    $upLifdtedOperand->addOperand( $operand->copy() );
107 52
                }
108
            }
109 72
        }
110
111 72
        return $upLiftedOr;
112
    }
113
114
    /**
115
     * @param array $options   + show_instance=false Display the operator of the rule or its instance id
116
     *
117
     * @return array
118
     *
119
     * @todo same as OrRule
120
     */
121 183 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...
122
    {
123
        $default_options = [
124 183
            'show_instance' => false,
125 183
            'sort_operands' => false,
126 183
            'semantic'      => false,
127 183
        ];
128 183
        foreach ($default_options as $default_option => &$default_value) {
129 183
            if ( ! isset($options[ $default_option ])) {
130 183
                $options[ $default_option ] = $default_value;
131 183
            }
132 183
        }
133
134 183
        if ( ! $options['show_instance'] && ! empty($this->cache['array'])) {
135 21
            return $this->cache['array'];
136
        }
137
138
        $operands_as_array = [
139 183
            $options['show_instance'] ? $this->getInstanceId() : self::operator,
140 183
        ];
141
142 183
        $operands = $this->operands;
143 183
        if ($options['semantic']) {
144
            // Semantic array: ['operator', 'semantic_id_of_operand1', 'semantic_id_of_operand2', ...]
145
            // with sorted semantic ids
146 179
            $operands_semantic_ids = array_keys($operands);
147 179
            sort($operands_semantic_ids);
148 179
            return array_merge(
149 179
                [self::operator],
150
                $operands_semantic_ids
151 179
            );
152
        }
153
        else {
154 92
            foreach ($operands as $operand) {
155 82
                $operands_as_array[] = $operand->toArray($options);
156 92
            }
157
158 92
            if ( ! $options['show_instance']) {
159 92
                return $this->cache['array'] = $operands_as_array;
160
            }
161
            else {
162
                return $operands_as_array;
163
            }
164
        }
165
    }
166
167
    /**
168
     * Generates a string description of the rule.
169 4
     *
170
     * @param  array  $options indent_unit
171 4
     * @return string The rule description
172 4
     */
173 1 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...
174
    {
175
        $operator = self::operator;
176 3
        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...
177 3
            return $this->cache['string'] = "['{$operator}']";
178
        }
179 3
180
        $indent_unit = isset($options['indent_unit']) ? $options['indent_unit'] : '';
181 3
        $line_break  = $indent_unit ? "\n" : '';
182
183 3
        $out = "['{$operator}',$line_break";
184 3
185 3
        foreach ($this->operands as $operand) {
186
            $out .= implode("\n", array_map(function($line) use (&$indent_unit) {
187 3
                return $indent_unit.$line;
188
            }, explode("\n", $operand->toString($options)) )) . ",$line_break";
189 3
        }
190
191
        $out .= ']';
192
193
        return $this->cache['string'] = $out;
194
    }
195 152
196
    /**
197 152
     * Remove AndRules operands of AndRules
198 152
     */
199 152 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...
200
    {
201
        foreach ($this->operands as $i => $operand) {
202 89
            if ( ! is_a($operand, AndRule::class)) {
203 1
                continue;
204
            }
205
206
            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...
207 89
                continue;
208 89
            }
209 89
210 89
            // Id AND is an operand on AND they can be merge (and the same with OR)
211
            foreach ($operands as $sub_operand) {
212
                $this->addOperand( $sub_operand->copy() );
213 89
            }
214 152
            unset($this->operands[$i]);
215
216 152
            // possibility of mono-operand or dupicates
217
            $has_been_changed = true;
218
        }
219
220
        return ! empty($has_been_changed);
221
    }
222
223
    /**
224
     * Removes rule branches that cannot produce result like:
225 141
     * A = 1 || (B < 2 && B > 3) <=> A = 1
226
     *
227 141
     * @param  array   $simplification_options Contextual options of the simplification
228
     * @return AndRule $this
229
     */
230
    public function removeInvalidBranches(array $simplification_options)
231 141
    {
232
        if ( ! $this->isNormalizationAllowed($simplification_options)) {
233 141
            return $this;
234
        }
235 137
236 1
        $this->moveSimplificationStepForward(self::remove_invalid_branches, $simplification_options);
237 1
238 1
        foreach ($this->operands as $i => $operand) {
239 1
            // if ($operand instanceof AndRule || $operand instanceof OrRule ) {
240
            if ( in_array( get_class($operand), [AndRule::class, OrRule::class]) ) {
241
                $this->operands[$i] = $operand->removeInvalidBranches($simplification_options);
242 141
                if ( ! $this->operands[$i]->hasSolution()) {
243
                    $this->operands = [];
244 141
                    return $this;
245
                }
246
            }
247
        }
248 141
249 136
        $operandsByFields = $this->groupOperandsByFieldAndOperator();
250 81
251
        // $this->dump(true);
252 81
253
        foreach ($operandsByFields as $field => $operandsByOperator) {
254
            if ( ! empty($operandsByOperator[ EqualRule::operator ])) {
255
                foreach ($operandsByOperator[ EqualRule::operator ] as $equalRule) {
256 81
                    // Multiple equal rules without the same value is invalid
257 81
                    if (isset($previousEqualRule) && $previousEqualRule->getValue() != $equalRule->getValue()) {
258 81
                        $this->operands = [];
259
                        return $this;
260 81
                    }
261
                    $previousEqualRule = $equalRule;
262 81
                }
263 81
                unset($previousEqualRule);
264 81
265 1
                $equalRule = reset($operandsByOperator[ EqualRule::operator ]);
266 1
267 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
                    && null === $equalRule->getValue()
269 81
                ) {
270 81
                    $this->operands = [];
271 81
                    return $this;
272 7
                }
273 7
274 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...
275
                    && $equalRule->getValue() >= reset($operandsByOperator[ BelowRule::operator ])->getUpperLimit()
276 81
                ) {
277 81
                    $this->operands = [];
278 81
                    return $this;
279 1
                }
280 1
281 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
                    && null === $equalRule->getValue()
283 80
                ) {
284 80
                    $this->operands = [];
285 80
                    return $this;
286 8
                }
287 8
288 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...
289
                    && $equalRule->getValue() <= reset($operandsByOperator[ AboveRule::operator ])->getLowerLimit()
290 76
                ) {
291 76
                    $this->operands = [];
292 76
                    return $this;
293 2
                }
294 2
295 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...
296
                    && $equalRule->getValue() == reset($operandsByOperator[ NotEqualRule::operator ])->getValue()
297 75
                ) {
298 75
                    $this->operands = [];
299 75
                    return $this;
300 75
                }
301
302
                if (   ! empty($operandsByOperator[ NotEqualRule::operator ])
303
                    && null === $equalRule->getValue()
304 75
                    && null === reset($operandsByOperator[ NotEqualRule::operator ])->getValue()
305 97
                ) {
306 97
                    $this->operands = [];
307 21
                    return $this;
308 21
                }
309
            }
310 21
            elseif (   ! empty($operandsByOperator[ BelowRule::operator ])
311 13
                    && ! empty($operandsByOperator[ AboveRule::operator ])) {
312 13
                $aboveRule = reset($operandsByOperator[ AboveRule::operator ]);
313
                $belowRule = reset($operandsByOperator[ BelowRule::operator ]);
314 13
315 135
                if ($belowRule->getUpperLimit() <= $aboveRule->getLowerLimit()) {
316
                    $this->operands = [];
317 135
                    return $this;
318
                }
319
            }
320
        }
321
322
        return $this;
323
    }
324
325
    /**
326
     * Checks if a simplified AndRule has incompatible operands like:
327
     * + a = 3 && a > 4
328 41
     * + a = 3 && a < 2
329
     * + a > 3 && a < 2
330 41
     *
331
     * @param  array $contextual_options Contextual options to pass to the
332
     *               simplification
333
     * @return bool If the AndRule can have a solution or not
334
     */
335
    public function hasSolution(array $contextual_options=[])
336
    {
337
        $operands = $this->getOperands();
338 41 View Code Duplication
        if (    (count($operands) == 1 && ! reset($operands)->hasSolution()) // skip simplification case after call of addMinimalCase() (which seems to have unwanted side effect)
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...
339 34
            && ! $this->simplicationStepReached(self::simplified)) {
340
            throw new \LogicException(
341
                "hasSolution has no sens if the rule is not simplified instead of being at: "
342 41
                .var_export($this->current_simplification_step, true)
343
            );
344 41
        }
345
        
346
        // atomic rules
347
        foreach ($operands as $operand) {
348
            if (method_exists($operand, 'hasSolution') && ! $operand->hasSolution()) {
349
                return false;
350
            }
351 151
        }
352
353
        return ! empty($operands);
354 151
    }
355 151
356 151
    /**
357
     * if A > 2 && A > 1 <=> A > 2
358
     * if A < 2 && A < 1 <=> A < 1
359 151
     *
360
     * @param  array  $operandsByFields Operands indexed by their field
361 9
     * @return array                    The operands indexed by field simplified
362
     */
363
    protected static function simplifySameOperands(array $operandsByFields)
364
    {
365 9
        // unifying same operands
366 1
        foreach ($operandsByFields as $field => $operandsByOperator) {
367
            foreach ($operandsByOperator as $operator => $operands) {
368
                unset($previous_operand);
369 9
370 9
                try {
371
                    if (AboveRule::operator == $operator) {
372
                        usort($operands, function( AboveRule $a, AboveRule $b ) {
373 1
                            if (null === $a->getLowerLimit()) {
374 60
                                return 1;
375 60
                            }
376 60
377 139
                            if (null === $b->getLowerLimit()) {
378
                                return -1;
379 7
                            }
380 1
381
                            if ($a->getLowerLimit() > $b->getLowerLimit()) {
382
                                return -1;
383 7
                            }
384
385
                            return 1;
386
                        });
387 7
                        $operands = [reset($operands)];
388 2
                    }
389 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...
390
                        usort($operands, function( BelowRule $a, BelowRule $b ) {
391 6
                            if (null === $a->getUpperLimit()) {
392 48
                                return 1;
393 48
                            }
394 48
395 132
                            if (null === $b->getUpperLimit()) {
396
                                return -1;
397 86
                            }
398 86
399 86
                            if ($a->getUpperLimit() < $b->getUpperLimit()) {
400 86
                                return -1;
401
                            }
402
403 5
                            return 1;
404 1
                        });
405 1
                        $operands = [reset($operands)];
406
                    }
407
                    elseif (EqualRule::operator == $operator) {
408
                        // TODO add an option for the support strict comparison
409
                        foreach ($operands as $i => $operand) {
410
                            if ( ! isset($previous_operand)) {
411
                                $previous_operand = $operand;
412 4
                                continue;
413
                            }
414 83
415 83
                            if ($previous_operand == $operand) {
416 101
                                unset($operands[$i]);
417 33
                                continue;
418
                            }
419 33
                            else {
420 33
                                // Same field expected to be two differents
421 33
                                // values at the same time has no sens so
422
                                // we remove all the operands of the current
423
                                // AndRule (TODO FalseRule)
424 6
                                return [];
425 6
                            }
426 6
                        }
427 6
                    }
428 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...
429 6
                        $first_in = reset($operands);
430 33
431
                        foreach ($operands as $i => $next_in) {
432
                            if ($first_in === $next_in) {
433 33
                                continue;
434
                            }
435
436 33
                            $first_in->setPossibilities( array_intersect(
437 79
                                $first_in->getPossibilities(),
438 14
                                $next_in->getPossibilities()
439
                            ) );
440 14
441 14
                            unset($operands[$i]);
442 14
                        }
443
444
                        // [field in []] <=> false
445 2
                        if ( ! $first_in->getPossibilities()) {
446 2
                            return [];
447 2
                        }
448 2
                    }
449 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...
450 2
                        $first_not_in = reset($operands);
451 14
452 14
                        foreach ($operands as $i => $next_not_in) {
453
                            if ($first_not_in === $next_not_in) {
454 148
                                continue;
455
                            }
456
457
                            $first_not_in->setPossibilities( array_merge(
458
                                $first_not_in->getPossibilities(),
459
                                $next_not_in->getPossibilities()
460
                            ) );
461
462
                            unset($operands[$i]);
463
                        }
464
                    }
465 148
                }
466 148
                catch (\Exception $e) {
467 151
                    VisibilityViolator::setHiddenProperty($e, 'message', $e->getMessage() . "\n" . var_export([
468
                            'operands' => $operands,
469 151
                            // 'this'     => $this,
470
                        ], true)
471
                    );
472
473
                    // \Debug::dumpJson($this->toArray(), true);
474 151
                    throw $e;
475
                }
476 151
477
                $operandsByFields[ $field ][ $operator ] = $operands;
478 148
            }
479 148
        }
480 148
481 148
        return $operandsByFields;
482 148
    }
483 148
484 148
    /**
485
     */
486
    protected static function simplifyDifferentOperands(array $operandsByFields)
487 148
    {
488 148
        foreach ($operandsByFields as $field => &$operandsByOperator) {
489 133
            foreach ([
490
                    EqualRule::operator,
491
                    AboveRule::operator,
492
                    AboveRule::operator,
493
                    InRule::operator,
494
                    NotInRule::operator,
495
                    BelowOrEqualRule::operator,
496
                    AboveOrEqualRule::operator,
497
                ]
498
                as $unifyable_operator
499 133
            ) {
500 148
                if ( ! empty($operandsByOperator[ $unifyable_operator ])) {
501
                    if (1 != count($operandsByOperator[ $unifyable_operator ])) {
502 148
                        throw new \LogicException(
503
                            __METHOD__ . " MUST be called after unifyAtomicOperands() "
504
                            ."to have only one '$unifyable_operator' predicate istead of:\n"
505
                            ."[\n".implode( ",\n", array_map(function ($rule) {
506 148
                                return $rule->toString();
507 2
                            }, $operandsByOperator[ $unifyable_operator ])
508
                            )."\n]"
509 151
                        );
510
                    }
511 151
                }
512
            }
513
514
            $operandsByOperator = self::simplifyDifferentOperandsForField($field, $operandsByOperator);
515
            // If tyhere is no more operands for a given field it means there
516
            // is no possible solutions for it so all the current and_case
517
            // is invalidated.
518 148
            if ( ! $operandsByOperator) {
519
                return [];
520
            }
521 148
        }
522 83
523
        return $operandsByFields;
524 83
    }
525 4
526 4
    /**
527 3
     * + if A = 2 && A > 1 <=> A = 2
528 2
     * + if A = 2 && A < 4 <=> A = 2
529 2
     */
530 1
    protected static function simplifyDifferentOperandsForField($field, array $operandsByOperator)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

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

Loading history...
531
    {
532
        // EqualRule comparisons
533 3
        if ( ! empty($operandsByOperator[ EqualRule::operator ])) {
534 1
            $equalRule = reset( $operandsByOperator[ EqualRule::operator ] );
535 1
536
            if ( ! empty($operandsByOperator[ NotEqualRule::operator ])) {
537
                foreach ($operandsByOperator[ NotEqualRule::operator ] as $i => $not_equal_rule) {
538
                    if (null !== $equalRule->getValue()) {
539 1
                        if (null === $not_equal_rule->getValue()) { // means if exists <=> equals something
540 4
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
541 4
                        }
542
                        elseif ($not_equal_rule->getValue() != $equalRule->getValue()) {
543 83
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
544 12
                        }
545 12
                    }
546 3
                    elseif (null === $equalRule->getValue() ) {
547 3
                        if (null !== $not_equal_rule->getValue()) {
548 12
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
549
                        }
550 83
                        // else we let the "equal null" and the "not equal null" for the romeInvalidBranches step
551 11
                    }
552 11
                }
553 3
            }
554 3
555 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...
556
                $aboveRule = reset($operandsByOperator[ AboveRule::operator ]);
557 83
                if (null !== $equalRule->getValue() && $aboveRule->getLowerLimit() < $equalRule->getValue()) {
558 3
                    unset($operandsByOperator[ AboveRule::operator ]);
559
                }
560 3
            }
561 3
562 3 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...
563
                $belowRule = reset($operandsByOperator[ BelowRule::operator ]);
564
                if (null !== $equalRule->getValue() && $belowRule->getUpperLimit() > $equalRule->getValue()) {
565
                    unset($operandsByOperator[ BelowRule::operator ]);
566 1
                }
567
            }
568 1
569
            if ( ! empty($operandsByOperator[ InRule::operator ])) {
570 3
                $possibilities = reset($operandsByOperator[ InRule::operator ])->getPossibilities();
571
572 83
                if (in_array($equalRule->getValue(), $possibilities)) {
573 2
                    unset($operandsByOperator[ InRule::operator ]);
574 2
                }
575
                else {
576 2
                    // We flush possibilities of the InRule
577
                    // TODO Replace it by a FalseRule
578
                    $operandsByOperator[ InRule::operator ][0]->setPossibilities([]);
579 1
                    // and also remove the equal rule to shorten the reste of the simplification process
580
                    unset($operandsByOperator[ EqualRule::operator ]);
581
                }
582 1
            }
583
584 83
            if ( ! empty($operandsByOperator[ NotInRule::operator ])) {
585 1
                $notInRule = reset($operandsByOperator[ NotInRule::operator ]);
586 1
                if (in_array($equalRule->getValue(), $notInRule->getPossibilities())) {
587 1
                    // ['field', '=', 4] && ['field', '!in', [4]...] <=> false
588 1
                    return [];
589
                }
590
                else {
591
                    unset($operandsByOperator[ NotInRule::operator ]);
592
                }
593 1
                // $notInRule->dump(true);
594
            }
595 83
596 1 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...
597 1
                $belowOrEqualRule = reset($operandsByOperator[ BelowOrEqualRule::operator ]);
598 1
                if ($equalRule->getValue() <= $belowOrEqualRule->getMaximum()) {
599 1
                    unset($operandsByOperator[ BelowOrEqualRule::operator ]);
600
                }
601
                else {
602
                    // ['field', '=', 4] && ['field', '<=', [3]...] <=> false
603
                    return [];
604 1
                }
605 83
            }
606
607 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...
608 148
                $aboveOrEqualRule = reset($operandsByOperator[ AboveOrEqualRule::operator ]);
609 22
                if ($equalRule->getValue() >= $aboveOrEqualRule->getMinimum()) {
610 22
                    unset($operandsByOperator[ AboveOrEqualRule::operator ]);
611 22
                }
612 9
                else {
613 2
                    // ['field', '=', 4] && ['field', '<=', [3]...] <=> false
614 2
                    return [];
615
                }
616 9
            }
617 2
        }
618 2
619
        // NotEqualRule null comparisons
620 9
        if ( ! empty($operandsByOperator[ NotEqualRule::operator ])) {
621 1
            if ( ! empty($operandsByOperator[ NotEqualRule::operator ])) {
622
                foreach ($operandsByOperator[ NotEqualRule::operator ] as $i => $notEqualRule) {
623
                    if (null === $notEqualRule->getValue()) {
624 1
                        if ( ! empty($operandsByOperator[ AboveRule::operator ])) {
625 9
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
626
                        }
627 13
628 2
                        if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
629 2
                            unset($operandsByOperator[ NotEqualRule::operator ][$i]);
630 2
                        }
631 2
632
                        if ( ! empty($operandsByOperator[ EqualRule::operator ])) {
633 13
                            if (null !== reset($operandsByOperator[ EqualRule::operator ])->getValue()) {
634
                                unset($operandsByOperator[ NotEqualRule::operator ][$i]);
635
                            }
636
                        }
637
                    }
638
                    else {
639 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...
640 22
                            if ($operandsByOperator[ AboveRule::operator ][0]->getLowerLimit() >= $notEqualRule->getValue()) {
641 1
                                unset($operandsByOperator[ NotEqualRule::operator ][$i]);
642 1
                            }
643
                        }
644 1
645 1 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...
646 1
                            if ($operandsByOperator[ BelowRule::operator ][0]->getUpperLimit() <= $notEqualRule->getValue()) {
647 1
                                unset($operandsByOperator[ NotEqualRule::operator ][$i]);
648
                            }
649 1
                        }
650 1
                    }
651
652 22
                    if ( ! empty($operandsByOperator[ NotInRule::operator ])) {
653
                        $notInRule = reset($operandsByOperator[ NotInRule::operator ]);
654
                        if ( ! in_array($notEqualRule->getValue(), $notInRule->getPossibilities())) {
655
                            // TODO Replace it by a FalseRule
656
                            $operandsByOperator[ NotInRule::operator ][0]->setPossibilities(
657
                                array_merge($notInRule->getPossibilities(), [$notEqualRule->getValue()])
658
                            );
659 22
                        }
660 22
661 22
                        unset($operandsByOperator[ NotEqualRule::operator ][$i]);
662
                    }
663
664
                    if ( ! empty($operandsByOperator[ InRule::operator ])) {
665 148
                        $inRule = reset($operandsByOperator[ InRule::operator ]);
666 32
667
                        $operandsByOperator[ InRule::operator ][0]->setPossibilities(
668 32
                            array_diff($inRule->getPossibilities(), [$notEqualRule->getValue()])
669 2
                        );
670 2
                    }
671 2
                }
672 2
            }
673 2
        }
674 2
675
        // Comparison between InRules and NotInRules
676 32
        // This is an optimization to avoid NotIn explosion
677 2
        if ( ! empty($operandsByOperator[ InRule::operator ])) {
678
            $inRule = $operandsByOperator[ InRule::operator ][0];
679 2
680 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...
681 2
                $notInRule = reset($operandsByOperator[ NotInRule::operator ]);
682 2
                $operandsByOperator[ InRule::operator ][0]->setPossibilities(
683 2
                    array_diff( $inRule->getPossibilities(), $notInRule->getPossibilities())
684
                );
685 2
                unset($operandsByOperator[ NotInRule::operator ]);
686 2
            }
687
688 32 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...
689 1
                $upper_limit = reset($operandsByOperator[ BelowRule::operator ])->getUpperLimit();
690
691 1
                $operandsByOperator[ InRule::operator ][0]->setPossibilities(
692
                    array_filter( $inRule->getPossibilities(), function ($possibility) use ($upper_limit) {
693 1
                        return $possibility < $upper_limit;
694 1
                    } )
695 1
                );
696
697 1
                unset($operandsByOperator[ BelowRule::operator ]);
698 1
            }
699 32
700 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...
701
                $lower_limit = reset($operandsByOperator[ AboveRule::operator ])->getLowerLimit();
702 148
703 11
                $operandsByOperator[ InRule::operator ][0]->setPossibilities(
704
                    array_filter( $inRule->getPossibilities(), function ($possibility) use ($lower_limit) {
705 11
                        return $possibility > $lower_limit;
706 1
                    } )
707
                );
708 1
709
                unset($operandsByOperator[ AboveRule::operator ]);
710 1
            }
711 1
        }
712 1
713 1
        // Comparison between NotInRules and > or <
714
        if ( ! empty($operandsByOperator[ NotInRule::operator ])) {
715 11
            $notInRule = $operandsByOperator[ NotInRule::operator ][0];
716
717 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...
718
                $upper_limit = reset($operandsByOperator[ BelowRule::operator ])->getUpperLimit();
719
720
                $operandsByOperator[ NotInRule::operator ][0]->setPossibilities(
721
                    array_filter( $notInRule->getPossibilities(), function ($possibility) use ($upper_limit) {
722
                        return $possibility < $upper_limit;
723
                    } )
724 11
                );
725
            }
726
727 148 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...
728 13
                $lower_limit = reset($operandsByOperator[ AboveRule::operator ])->getLowerLimit();
729
730 13
                $operandsByOperator[ NotInRule::operator ][0]->setPossibilities(
731
                    array_filter( $notInRule->getPossibilities(), function ($possibility) use ($lower_limit) {
732
                        return $possibility > $lower_limit;
733
                    } )
734
                );
735
            }
736
        }
737
738
        // Comparison between <= and > or <
739
        if ( ! empty($operandsByOperator[ BelowOrEqualRule::operator ])) {
740
            $belowOrEqualRule = $operandsByOperator[ BelowOrEqualRule::operator ][0];
741
742
            if ( ! empty($operandsByOperator[ BelowRule::operator ])) {
743
                $upper_limit = reset($operandsByOperator[ BelowRule::operator ])->getUpperLimit();
744 13
745 3
                if ($belowOrEqualRule->getMaximum() >= $upper_limit) {
746
                    // [field < 3] && [field <= 3]
747 3
                    // [field < 3] && [field <= 4]
748
                    unset($operandsByOperator[ BelowOrEqualRule::operator ][0]);
749
                }
750
                else {
751 3
                    // [field < 3] && [field <= 2]
752
                    unset($operandsByOperator[ BelowRule::operator ][0]);
753 13
                }
754 5
            }
755
756 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...
757
                $lower_limit = reset($operandsByOperator[ AboveRule::operator ])->getLowerLimit();
758
759
                if ($belowOrEqualRule->getMaximum() <= $lower_limit) {
760 5
                    // [field > 3] && [field <= 2] <=> false
761
                    return [];
762 1
                }
763 1
            }
764 1
765
            if ( ! empty($operandsByOperator[ AboveOrEqualRule::operator ])) {
766 1
                $minimum = reset($operandsByOperator[ AboveOrEqualRule::operator ])->getMinimum();
767
768
                if ($belowOrEqualRule->getMaximum() < $minimum) {
769 1
                    // [field <= 3] && [field >= 4] <=> false
770 5
                    return [];
771 13
                }
772
                elseif ($belowOrEqualRule->getMaximum() == $minimum) {
773 148
                    // [field <= 3] && [field >= 3] <=> [field = 3]
774
                    unset($operandsByOperator[ BelowOrEqualRule::operator ]);
775
                    unset($operandsByOperator[ AboveOrEqualRule::operator ]);
776
                    $operandsByOperator[ EqualRule::operator ][] = new EqualRule($field, $minimum);
777
778
                    if (count($operandsByOperator[ EqualRule::operator ]) > 1) {
779
                        $operandsByOperator = self::simplifyDifferentOperandsForField($field, $operandsByOperator);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $operandsByOperator. This often makes code more readable.
Loading history...
780
                    }
781
                }
782 152
            }
783
        }
784
785 152
        return $operandsByOperator;
786
    }
787
788
    /**
789
     * This method is meant to be used during simplification that would
790
     * need to change the class of the current instance by a normal one.
791
     *
792
     * @param  array $new_operands [description]
793
     * @return AndRule The current instance (of or or subclass) or a new AndRule
794
     */
795
    public function setOperandsOrReplaceByOperation(array $new_operands)
796
    {
797
        try {
798
            return $this->setOperands( $new_operands );
799
        }
800
        catch (\LogicException $e) {
801
            return new AndRule( $new_operands );
802
        }
803
    }
804
805
    /**/
806
}
807