Completed
Push — master ( 7333d4...d6297f )
by Jean
03:34
created

AbstractRule::flushStaticCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 6
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
AbstractRule::toArray() 0 1 ?
1
<?php
2
namespace JClaveau\LogicalFilter\Rule;
3
4
abstract class AbstractRule implements \JsonSerializable
5
{
6
    use Trait_RuleWithOptions;
7
    use Trait_RuleWithCache;
8
9
    /** @var  array $ruleAliases */
10
    protected static $ruleAliases = [
11
        '!'    => 'not',
12
        '='    => 'equal',
13
        '>'    => 'above',
14
        '<'    => 'below',
15
        '><'   => 'between',
16
        '=><'  => 'between_or_equal_lower',
17
        '><='  => 'between_or_equal_upper',
18
        '=><=' => 'between_or_equal_both',
19
        '<='   => 'below_or_equal',
20
        '>='   => 'above_or_equal',
21
        '!='   => 'not_equal',
22
        'in'   => 'in',
23
        '!in'  => 'not_in',
24
    ];
25
26
    /**
27
     * @param  string $english_operator
28
     * @return string
29
     */
30 139
    public static function findSymbolicOperator($english_operator)
31
    {
32 139
        $association = array_flip( self::$ruleAliases );
33 139
        if (isset($association[ $english_operator ])) {
34 139
            return $association[ $english_operator ];
35
        }
36
37 139
        return $english_operator;
38
    }
39
40
    /**
41
     * @param  string $symbolic_operator
42
     * @return string
43
     */
44 102
    public static function findEnglishOperator($symbolic_operator)
45
    {
46 102
        $association = self::$ruleAliases;
47 102
        if (isset($association[ $symbolic_operator ])) {
48 93
            return $association[ $symbolic_operator ];
49
        }
50
51 14
        return $symbolic_operator;
52
    }
53
54
    /**
55
     *
56
     * @param  string $field
57
     * @param  string $type
58
     * @param  mixed  $values
59
     * @param  array  $options
60
     *
61
     * @return AbstractRule
62
     */
63 143
    public static function generateSimpleRule($field, $type, $values, array $options=[])
64
    {
65 143
        $cache_key = hash('md4', serialize( func_get_args()) );
66 143
        if (isset(self::$static_cache['rules_generation'][$cache_key])) {
67 98
            return self::$static_cache['rules_generation'][$cache_key]->copy();
68
        }
69
70 102
        $ruleClass = self::getRuleClass($type);
71
72 102
        return self::$static_cache['rules_generation'][$cache_key] = new $ruleClass( $field, $values, $options );
73
    }
74
75
    /**
76
     * @param  string $rule_operator
77
     *
78
     * @return string Class corresponding to the given operator
79
     */
80 102
    public static function getRuleClass($rule_operator)
81
    {
82 102
        $english_rule_operator = self::findEnglishOperator($rule_operator);
83
84
        $rule_class = __NAMESPACE__
85
            . '\\'
86 102
            . str_replace('_', '', ucwords($english_rule_operator, '_'))
87 102
            . 'Rule';
88
89 102
        if ( ! class_exists( $rule_class)) {
90
            throw new \InvalidArgumentException(
91
                "The class '$rule_class' corresponding to the  operator "
92
                ."'$rule_operator' / '$english_rule_operator' cannot be found."
93
            );
94
        }
95
96 102
        return $rule_class;
97
    }
98
99
    /**
100
     * Clones the rule with a chained syntax.
101
     *
102
     * @return AbstractRule A copy of the current instance.
103
     */
104 135
    public function copy()
105
    {
106 135
        return clone $this;
107
    }
108
109
    /**
110
     * Dumps the rule with a chained syntax.
111
     *
112
     * @param bool  $exit       Default false
113
     * @param array $options    + callstack_depth=2 The level of the caller to dump
114
     *                          + mode='string' in 'export' | 'dump' | 'string' | 'xdebug'
115
     *
116
     * @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...
117
     */
118 4
    final public function dump($exit=false, array $options=[])
119
    {
120
        $default_options = [
121 4
            'callstack_depth' => 2,
122 4
            'mode'            => 'string',
123
            // 'show_instance'   => false,
124 4
        ];
125 4
        foreach ($default_options as $default_option => &$default_value) {
126 4
            if ( ! isset($options[ $default_option ])) {
127
                $options[ $default_option ] = $default_value;
128
            }
129 4
        }
130 4
        extract($options);
131
132 4
        $bt     = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $callstack_depth);
133 4
        $caller = $bt[ $callstack_depth - 2 ];
134
135 4
        echo "\n" . $caller['file'] . ':' . $caller['line'] . "\n";
136 4
        if ('string' == $mode) {
137 1
            if ( ! isset($options['indent_unit'])) {
138
                $options['indent_unit'] = "    ";
139
            }
140
141 1
            echo($this->toString($options));
142 1
        }
143 3
        elseif ('dump' == $mode) {
144 1
            if ($xdebug_enabled = ini_get('xdebug.overload_var_dump')) {
145 1
                ini_set('xdebug.overload_var_dump', 0);
146 1
            }
147
148 1
            var_dump($this->toArray($options));
149
150 1
            if ($xdebug_enabled) {
151 1
                ini_set('xdebug.overload_var_dump', 1);
152 1
            }
153 1
        }
154 2
        elseif ('xdebug' == $mode) {
155 1
            if ( ! function_exists('xdebug_is_enabled')) {
156
                throw new \RuntimeException("Xdebug is not installed");
157
            }
158 1
            if ( ! xdebug_is_enabled()) {
159
                throw new \RuntimeException("Xdebug is disabled");
160
            }
161
162 1
            if ($xdebug_enabled = ini_get('xdebug.overload_var_dump')) {
163 1
                ini_set('xdebug.overload_var_dump', 1);
164 1
            }
165
166 1
            var_dump($this->toArray($options));
167
168 1
            if ($xdebug_enabled) {
169 1
                ini_set('xdebug.overload_var_dump', 0);
170 1
            }
171 1
        }
172 1
        elseif ('export' == $mode) {
173 1
            var_export($this->toArray($options));
174 1
        }
175
        else {
176
            throw new \InvalidArgumentException(
177
                 "'mode' option must belong to ['string', 'export', 'dump'] "
178
                ."instead of " . var_export($mode, true)
179
            );
180
        }
181 4
        echo "\n\n";
182
183 4
        if ($exit) {
184
            exit;
185
        }
186
187 4
        return $this;
188
    }
189
190
    /**
191
     * For implementing JsonSerializable interface.
192
     *
193
     * @see https://secure.php.net/manual/en/jsonserializable.jsonserialize.php
194
     */
195
    public function jsonSerialize()
196
    {
197
        return $this->toArray();
198
    }
199
200
    /**
201
     * @return string
202
     */
203 3
    public function __toString()
204
    {
205 3
        return $this->toString();
206
    }
207
208
    /**
209
     * @return string
210
     */
211
    abstract public function toString(array $options=[]);
212
213
    /**
214
     * @return array
215
     */
216
    abstract public function toArray(array $options=[]);
217
218
    protected $instance_id;
219
220
    /**
221
     * Returns an id describing the instance internally for debug purpose.
222
     *
223
     * @see https://secure.php.net/manual/en/function.spl-object-id.php
224
     *
225
     * @return string
226
     */
227 1
    public function getInstanceId()
228
    {
229 1
        if ($this->instance_id) {
230
            return $this->instance_id;
231
        }
232
233 1
        return $this->instance_id = get_class($this).':'.spl_object_id($this);
234
    }
235
236
    /**
237
     * Returns an id corresponding to the meaning of the rule.
238
     *
239
     * @return string
240
     */
241 161
    final public function getSemanticId()
242
    {
243 161
        if (isset($this->cache['semantic_id'])) {
244
            return $this->cache['semantic_id'];
245
        }
246
247 161
        return hash('md4', serialize( $this->toArray(['semantic' => true]) ))  // faster but longer
248
              .'-'
249 161
              .hash('md4', serialize( $this->options ))
250 161
              ;
251
    }
252
253
    /**
254
     * @deprecated addMinimalCase
255
     */
256 4
    protected function forceLogicalCore()
257
    {
258 4
        return $this->addMinimalCase();
259
    }
260
261
    /**
262
     * Forces the two firsts levels of the tree to be an OrRule having
263
     * only AndRules as operands:
264
     * ['field', '=', '1'] <=> ['or', ['and', ['field', '=', '1']]]
265
     * As a simplified ruleTree will alwways be reduced to this structure
266
     * with no suboperands others than atomic ones or a simpler one like:
267
     * ['or', ['field', '=', '1'], ['field2', '>', '3']]
268
     *
269
     * This helpes to ease the result of simplify()
270
     *
271
     * @return OrRule
272
     */
273 48
    public function addMinimalCase()
274
    {
275
        // Simplification step is required to call hasSolution() on the
276
        // returned OrRule value
277 48
        if ($this instanceof AndRule || $this instanceof OrRule) {
278 33
            $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...
279 33
        }
280 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...
281 18
            $simplification_step_to_keep = AbstractOperationRule::simplified;
282 18
        }
283
        else {
284 1
            $simplification_step_to_keep = null;
285
        }
286
287
        if (   $this instanceof AbstractAtomicRule
288 48
            || $this instanceof NotRule
289 37
            || $this instanceof InRule
290 33
            || ! $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...
291 48
        ) {
292 23
            $ruleTree = new OrRule([
293 23
                new AndRule([
294 23
                    $this,
295 23
                ]),
296 23
            ]);
297 23
        }
298 29
        elseif ($this instanceof AndRule) {
299 9
            $ruleTree = new OrRule([
300 9
                $this,
301 9
            ]);
302 9
        }
303 21
        elseif ($this instanceof OrRule) {
304 21
            foreach ($this->operands as $i => $operand) {
305 21
                if (! $operand instanceof AndRule) {
306 11
                    $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...
307 11
                }
308 21
            }
309 21
            $ruleTree = $this;
310 21
        }
311
        else {
312
            throw new \LogicException(
313
                "Unhandled type of simplified rules provided for conversion: "
314
                .$this
315
            );
316
        }
317
318 48
        if ($simplification_step_to_keep) {
319 44
            foreach ($operands = $ruleTree->getOperands() as $andOperand) {
320 44
                if (! $andOperand instanceof AndRule) {
321
                    throw new \LogicException(
322
                        "A rule is intended to be an and case: \n"
323
                        .$andOperand
324
                        ."\nof:\n"
325
                        .$ruleTree
326
                    );
327
                }
328
329 44
                $andOperand->moveSimplificationStepForward($simplification_step_to_keep, [], true);
330 44
            }
331 44
            $ruleTree->setOperands($operands);
332 44
            $ruleTree->moveSimplificationStepForward($simplification_step_to_keep, [], true);
333 44
        }
334
335
336 48
        return $ruleTree;
337
    }
338
339
    /**/
340
}
341