Completed
Push — master ( d05d36...1c1a74 )
by Jean
03:54
created

AbstractRule::addMinimalCase()   C

Complexity

Conditions 15
Paths 30

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 15.4989

Importance

Changes 0
Metric Value
cc 15
nc 30
nop 0
dl 0
loc 65
ccs 40
cts 46
cp 0.8696
crap 15.4989
rs 5.9166
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
4
abstract class AbstractRule implements \JsonSerializable
5
{
6
    use Trait_RuleWithOptions;
7
8
    /** @var  array $ruleAliases */
9
    protected static $ruleAliases = [
10
        '!'    => 'not',
11
        '='    => 'equal',
12
        '>'    => 'above',
13
        '<'    => 'below',
14
        '><'   => 'between',
15
        '=><'  => 'between_or_equal_lower',
16
        '><='  => 'between_or_equal_upper',
17
        '=><=' => 'between_or_equal_both',
18
        '<='   => 'below_or_equal',
19
        '>='   => 'above_or_equal',
20
        '!='   => 'not_equal',
21
        'in'   => 'in',
22
        '!in'  => 'not_in',
23
    ];
24
25
    /** @var array $cache */
26
    protected $cache = [
27
        'array'       => null,
28
        'string'      => null,
29
        'semantic_id' => null,
30
    ];
31
32
    /**
33
     * @param  string $english_operator
34
     * @return string
35
     */
36 134
    public static function findSymbolicOperator($english_operator)
37
    {
38 134
        $association = array_flip( self::$ruleAliases );
39 134
        if (isset($association[ $english_operator ])) {
40 134
            return $association[ $english_operator ];
41
        }
42
43 134
        return $english_operator;
44
    }
45
46
    /**
47
     * @param  string $symbolic_operator
48
     * @return string
49
     */
50 97
    public static function findEnglishOperator($symbolic_operator)
51
    {
52 97
        $association = self::$ruleAliases;
53 97
        if (isset($association[ $symbolic_operator ])) {
54 88
            return $association[ $symbolic_operator ];
55
        }
56
57 14
        return $symbolic_operator;
58
    }
59
60
    protected static $static_cache = [
61
        'rules_generation' => [],
62
    ];
63
64
    /**
65
     *
66
     * @param  string $field
67
     * @param  string $type
68
     * @param  mixed  $values
69
     * @param  array  $options
70
     *
71
     * @return AbstractRule
72
     */
73 138
    public static function generateSimpleRule($field, $type, $values, array $options=[])
74
    {
75 138
        $cache_key = hash('md4', serialize( func_get_args()) );
76 138
        if (isset(self::$static_cache['rules_generation'][$cache_key])) {
77 93
            return self::$static_cache['rules_generation'][$cache_key]->copy();
78
        }
79
80 97
        $ruleClass = self::getRuleClass($type);
81
82 97
        return self::$static_cache['rules_generation'][$cache_key] = new $ruleClass( $field, $values, $options );
83
    }
84
85
    /**
86
     * @param  string $rule_operator
87
     *
88
     * @return string Class corresponding to the given operator
89
     */
90 97
    public static function getRuleClass($rule_operator)
91
    {
92 97
        $english_rule_operator = self::findEnglishOperator($rule_operator);
93
94
        $rule_class = __NAMESPACE__
95
            . '\\'
96 97
            . str_replace('_', '', ucwords($english_rule_operator, '_'))
97 97
            . 'Rule';
98
99 97
        if ( ! class_exists( $rule_class)) {
100
            throw new \InvalidArgumentException(
101
                "The class '$rule_class' corresponding to the  operator "
102
                ."'$rule_operator' / '$english_rule_operator' cannot be found."
103
            );
104
        }
105
106 97
        return $rule_class;
107
    }
108
109
    /**
110
     * Clones the rule with a chained syntax.
111
     *
112
     * @return AbstractRule A copy of the current instance.
113
     */
114 130
    public function copy()
115
    {
116 130
        return clone $this;
117
    }
118
119
    /**
120
     * Dumps the rule with a chained syntax.
121
     *
122
     * @param bool  $exit       Default false
123
     * @param array $options    + callstack_depth=2 The level of the caller to dump
124
     *                          + mode='string' in 'export' | 'dump' | 'string' | 'xdebug'
125
     *
126
     * @return $this
0 ignored issues
show
Documentation introduced by
Should the return type not be AbstractRule|null?

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

Loading history...
127
     */
128 4
    final public function dump($exit=false, array $options=[])
129
    {
130
        $default_options = [
131 4
            'callstack_depth' => 2,
132 4
            'mode'            => 'string',
133
            // 'show_instance'   => false,
134 4
        ];
135 4
        foreach ($default_options as $default_option => &$default_value) {
136 4
            if ( ! isset($options[ $default_option ])) {
137
                $options[ $default_option ] = $default_value;
138
            }
139 4
        }
140 4
        extract($options);
141
142 4
        $bt     = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $callstack_depth);
143 4
        $caller = $bt[ $callstack_depth - 2 ];
144
145 4
        echo "\n" . $caller['file'] . ':' . $caller['line'] . "\n";
146 4
        if ('string' == $mode) {
147 1
            if ( ! isset($options['indent_unit'])) {
148
                $options['indent_unit'] = "    ";
149
            }
150
151 1
            echo($this->toString($options));
152 1
        }
153 3
        elseif ('dump' == $mode) {
154 1
            if ($xdebug_enabled = ini_get('xdebug.overload_var_dump')) {
155 1
                ini_set('xdebug.overload_var_dump', 0);
156 1
            }
157
158 1
            var_dump($this->toArray($options));
159
160 1
            if ($xdebug_enabled) {
161 1
                ini_set('xdebug.overload_var_dump', 1);
162 1
            }
163 1
        }
164 2
        elseif ('xdebug' == $mode) {
165 1
            if ( ! function_exists('xdebug_is_enabled')) {
166
                throw new \RuntimeException("Xdebug is not installed");
167
            }
168 1
            if ( ! xdebug_is_enabled()) {
169
                throw new \RuntimeException("Xdebug is disabled");
170
            }
171
172 1
            if ($xdebug_enabled = ini_get('xdebug.overload_var_dump')) {
173 1
                ini_set('xdebug.overload_var_dump', 1);
174 1
            }
175
176 1
            var_dump($this->toArray($options));
177
178 1
            if ($xdebug_enabled) {
179 1
                ini_set('xdebug.overload_var_dump', 0);
180 1
            }
181 1
        }
182 1
        elseif ('export' == $mode) {
183 1
            var_export($this->toArray($options));
184 1
        }
185
        else {
186
            throw new \InvalidArgumentException(
187
                 "'mode' option must belong to ['string', 'export', 'dump'] "
188
                ."instead of " . var_export($mode, true)
189
            );
190
        }
191 4
        echo "\n\n";
192
193 4
        if ($exit) {
194
            exit;
195
        }
196
197 4
        return $this;
198
    }
199
200
    /**
201
     */
202 167
    public function flushCache()
203
    {
204 167
        $this->cache = [
205 167
            'array'       => null,
206 167
            'string'      => null,
207 167
            'semantic_id' => null,
208
        ];
209
210 167
        return $this;
211
    }
212
213
    /**
214
     */
215 1
    public static function flushStaticCache()
216
    {
217 1
        self::$static_cache = [
218 1
            'rules_generation' => [],
219 1
        ];
220 1
    }
221
222
    /**
223
     * For implementing JsonSerializable interface.
224
     *
225
     * @see https://secure.php.net/manual/en/jsonserializable.jsonserialize.php
226
     */
227
    public function jsonSerialize()
228
    {
229
        return $this->toArray();
230
    }
231
232
    /**
233
     * @return string
234
     */
235 3
    public function __toString()
236
    {
237 3
        return $this->toString();
238
    }
239
240
    /**
241
     * @return string
242
     */
243
    abstract public function toString(array $options=[]);
244
245
    /**
246
     * @return array
247
     */
248
    abstract public function toArray(array $options=[]);
249
250
    protected $instance_id;
251
252
    /**
253
     * Returns an id describing the instance internally for debug purpose.
254
     *
255
     * @see https://secure.php.net/manual/en/function.spl-object-id.php
256
     *
257
     * @return string
258
     */
259 1
    public function getInstanceId()
260
    {
261 1
        if ($this->instance_id) {
262
            return $this->instance_id;
263
        }
264
265 1
        return $this->instance_id = get_class($this).':'.spl_object_id($this);
266
    }
267
268
    /**
269
     * Returns an id corresponding to the meaning of the rule.
270
     *
271
     * @return string
272
     */
273 156
    final public function getSemanticId()
274
    {
275 156
        if (isset($this->cache['semantic_id'])) {
276
            return $this->cache['semantic_id'];
277
        }
278
279 156
        return hash('md4', serialize( $this->toArray(['semantic' => true]) ))  // faster but longer
280
              .'-'
281 156
              .hash('md4', serialize( $this->options ))
282 156
              ;
283
    }
284
285
    /**
286
     * @deprecated addMinimalCase
287
     */
288 4
    protected function forceLogicalCore()
289
    {
290 4
        return $this->addMinimalCase();
291
    }
292
293
    /**
294
     * Forces the two firsts levels of the tree to be an OrRule having
295
     * only AndRules as operands:
296
     * ['field', '=', '1'] <=> ['or', ['and', ['field', '=', '1']]]
297
     * As a simplified ruleTree will alwways be reduced to this structure
298
     * with no suboperands others than atomic ones or a simpler one like:
299
     * ['or', ['field', '=', '1'], ['field2', '>', '3']]
300
     *
301
     * This helpes to ease the result of simplify()
302
     *
303
     * @return OrRule
304
     */
305 43
    public function addMinimalCase()
306
    {
307
        // Simplification step is required to call hasSolution() on the
308
        // returned OrRule value
309 43
        if ($this instanceof AndRule || $this instanceof OrRule) {
310 28
            $simplification_step_to_keep = $this->getSimplificationStep();
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 getSimplificationStep() 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...
311 28
        }
312 19
        elseif ($this->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...
313 18
            $simplification_step_to_keep = AbstractOperationRule::simplified;
314 18
        }
315
        else {
316 1
            $simplification_step_to_keep = null;
317
        }
318
319
        if (   $this instanceof AbstractAtomicRule
320 43
            || $this instanceof NotRule
321 32
            || $this instanceof InRule
322 28
            || ! $this->isNormalizationAllowed([])
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 isNormalizationAllowed() 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...
323 43
        ) {
324 23
            $ruleTree = new OrRule([
325 23
                new AndRule([
326 23
                    $this,
327 23
                ]),
328 23
            ]);
329 23
        }
330 24
        elseif ($this instanceof AndRule) {
331 9
            $ruleTree = new OrRule([
332 9
                $this,
333 9
            ]);
334 9
        }
335 16
        elseif ($this instanceof OrRule) {
336 16
            foreach ($this->operands as $i => $operand) {
337 16
                if (! $operand instanceof AndRule) {
338 6
                    $this->operands[$i] = new AndRule([$operand]);
0 ignored issues
show
Bug introduced by
The property operands does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
339 6
                }
340 16
            }
341 16
            $ruleTree = $this;
342 16
        }
343
        else {
344
            throw new \LogicException(
345
                "Unhandled type of simplified rules provided for conversion: "
346
                .$this
347
            );
348
        }
349
350 43
        if ($simplification_step_to_keep) {
351 39
            foreach ($operands = $ruleTree->getOperands() as $andOperand) {
352 39
                if (! $andOperand instanceof AndRule) {
353
                    throw new \LogicException(
354
                        "A rule is intended to be an and case: \n"
355
                        .$andOperand
356
                        ."\nof:\n"
357
                        .$ruleTree
358
                    );
359
                }
360
361 39
                $andOperand->moveSimplificationStepForward($simplification_step_to_keep, [], true);
362 39
            }
363 39
            $ruleTree->setOperands($operands);
364 39
            $ruleTree->moveSimplificationStepForward($simplification_step_to_keep, [], true);
365 39
        }
366
367
368 43
        return $ruleTree;
369
    }
370
371
    /**/
372
}
373