BaseAlgorithm   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 1
dl 0
loc 354
ccs 145
cts 145
cp 1
rs 8.48
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A callAlgorithm() 0 9 1
A evaluateLogical() 0 11 2
A packEvaluationResult() 0 8 1
A unpackEvaluationValue() 0 4 1
A unpackEvaluationObligations() 0 4 1
A unpackEvaluationAdvice() 0 4 1
A evaluateFirstApplicable() 0 20 4
A evaluateDenyUnlessPermit() 0 20 3
A evaluatePermitUnlessDeny() 0 20 3
C evaluateDenyOverrides() 0 65 15
C evaluatePermitOverrides() 0 65 15
A mergeToStorage() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like BaseAlgorithm often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BaseAlgorithm, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Auth\Authorization\PolicyDecision\Algorithms;
4
5
/**
6
 * Copyright 2015-2019 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\Auth\Contracts\Authorization\PolicyAdministration\EvaluationEnum;
22
use Limoncello\Auth\Contracts\Authorization\PolicyInformation\ContextInterface;
23
use Psr\Log\LoggerInterface;
24
use function array_key_exists;
25
use function array_merge;
26
use function call_user_func;
27
28
/**
29
 * @package Limoncello\Auth
30
 */
31
abstract class BaseAlgorithm implements BaseAlgorithmInterface
32
{
33
    /** @var callable */
34
    const METHOD = null;
35
36
    /** Evaluation result index */
37
    const EVALUATION_VALUE = 0;
38
39
    /** Evaluation result index */
40
    const EVALUATION_OBLIGATIONS = self::EVALUATION_VALUE + 1;
41
42
    /** Evaluation result index */
43
    const EVALUATION_ADVICE = self::EVALUATION_OBLIGATIONS + 1;
44
45
    /**
46
     * @param callable             $method
47
     * @param ContextInterface     $context
48
     * @param array                $optimizedTargets
49 23
     * @param array                $encodedItems
50
     * @param LoggerInterface|null $logger
51
     *
52
     * @return array
53
     */
54
    protected static function callAlgorithm(
55
        callable $method,
56 23
        ContextInterface $context,
57
        array $optimizedTargets,
58
        array $encodedItems,
59
        LoggerInterface $logger = null
60
    ): array {
61
        return call_user_func($method, $context, $optimizedTargets, $encodedItems, $logger);
62
    }
63
64
    /**
65 21
     * @param ContextInterface $context
66
     * @param array|null       $serializedLogical
67 21
     *
68 21
     * @return bool
69
     */
70
    protected static function evaluateLogical(ContextInterface $context, array $serializedLogical = null): bool
71 15
    {
72 13
        if ($serializedLogical === null) {
73
            return true;
74 13
        }
75
76
        $evaluation = call_user_func($serializedLogical, $context);
77
        $result     = $evaluation === true;
78
79
        return $result;
80
    }
81
82
    /**
83
     * @param int   $evaluation
84 33
     * @param array $obligations
85
     * @param array $advice
86
     *
87 33
     * @return array
88 33
     */
89 33
    protected static function packEvaluationResult(int $evaluation, array $obligations = [], array $advice = []): array
90
    {
91
        return [
92
            self::EVALUATION_VALUE       => $evaluation,
93
            self::EVALUATION_OBLIGATIONS => $obligations,
94
            self::EVALUATION_ADVICE      => $advice,
95
        ];
96
    }
97
98 26
    /**
99
     * @param array $packedEvaluation
100 26
     *
101
     * @return int
102
     */
103
    protected static function unpackEvaluationValue(array $packedEvaluation): int
104
    {
105
        return $packedEvaluation[self::EVALUATION_VALUE];
106
    }
107
108 21
    /**
109
     * @param array $packedEvaluation
110 21
     *
111
     * @return array
112
     */
113
    protected static function unpackEvaluationObligations(array $packedEvaluation): array
114
    {
115
        return $packedEvaluation[self::EVALUATION_OBLIGATIONS];
116
    }
117
118 21
    /**
119
     * @param array $packedEvaluation
120 21
     *
121
     * @return array
122
     */
123
    protected static function unpackEvaluationAdvice(array $packedEvaluation): array
124
    {
125
        return $packedEvaluation[self::EVALUATION_ADVICE];
126
    }
127
128
    /**
129
     * @param ContextInterface     $context
130
     * @param array                $optimizedTargets
131 7
     * @param array                $encodedItems
132
     * @param LoggerInterface|null $logger
133
     *
134
     * @return array
135
     */
136
    protected static function evaluateFirstApplicable(
137 7
        ContextInterface $context,
138 7
        array $optimizedTargets,
139 7
        array $encodedItems,
140 7
        ?LoggerInterface $logger
141 7
    ): array {
142 6
        foreach (static::evaluateTargets($context, $optimizedTargets, $logger) as $match => $itemId) {
143 6
            $encodedItem      = $encodedItems[$itemId];
144
            $packedEvaluation = static::evaluateItem($context, $match, $encodedItem, $logger);
145 7
            $evaluation       = static::unpackEvaluationValue($packedEvaluation);
146
            if ($evaluation === EvaluationEnum::PERMIT || $evaluation === EvaluationEnum::DENY) {
147
                $obligations = static::unpackEvaluationObligations($packedEvaluation);
148
                $advice      = static::unpackEvaluationAdvice($packedEvaluation);
149 2
150
                return static::packEvaluationResult($evaluation, $obligations, $advice);
151
            }
152
        }
153
154
        return static::packEvaluationResult(EvaluationEnum::NOT_APPLICABLE);
155
    }
156
157
    /**
158
     * @param ContextInterface     $context
159
     * @param array                $optimizedTargets
160 9
     * @param array                $encodedItems
161
     * @param LoggerInterface|null $logger
162
     *
163
     * @return array
164
     */
165
    protected static function evaluateDenyUnlessPermit(
166 9
        ContextInterface $context,
167 9
        array $optimizedTargets,
168 9
        array $encodedItems,
169 9
        ?LoggerInterface $logger
170 9
    ): array {
171 5
        foreach (static::evaluateTargets($context, $optimizedTargets, $logger) as $match => $itemId) {
172 5
            $encodedItem      = $encodedItems[$itemId];
173
            $packedEvaluation = static::evaluateItem($context, $match, $encodedItem, $logger);
174 9
            $evaluation       = static::unpackEvaluationValue($packedEvaluation);
175
            if ($evaluation === EvaluationEnum::PERMIT) {
176
                $obligations = static::unpackEvaluationObligations($packedEvaluation);
177
                $advice      = static::unpackEvaluationAdvice($packedEvaluation);
178 5
179
                return static::packEvaluationResult($evaluation, $obligations, $advice);
180
            }
181
        }
182
183
        return static::packEvaluationResult(EvaluationEnum::DENY);
184
    }
185
186
    /**
187
     * @param ContextInterface     $context
188
     * @param array                $optimizedTargets
189 2
     * @param array                $encodedItems
190
     * @param LoggerInterface|null $logger
191
     *
192
     * @return array
193
     */
194
    protected static function evaluatePermitUnlessDeny(
195 2
        ContextInterface $context,
196 2
        array $optimizedTargets,
197 2
        array $encodedItems,
198 2
        ?LoggerInterface $logger
199 2
    ): array {
200 2
        foreach (static::evaluateTargets($context, $optimizedTargets, $logger) as $match => $itemId) {
201 2
            $encodedItem      = $encodedItems[$itemId];
202
            $packedEvaluation = static::evaluateItem($context, $match, $encodedItem, $logger);
203 2
            $evaluation       = static::unpackEvaluationValue($packedEvaluation);
204
            if ($evaluation === EvaluationEnum::DENY) {
205
                $obligations = static::unpackEvaluationObligations($packedEvaluation);
206
                $advice      = static::unpackEvaluationAdvice($packedEvaluation);
207 2
208
                return static::packEvaluationResult($evaluation, $obligations, $advice);
209
            }
210
        }
211
212
        return static::packEvaluationResult(EvaluationEnum::PERMIT);
213
    }
214
215
    /**
216
     * @param ContextInterface     $context
217
     * @param array                $optimizedTargets
218
     * @param array                $encodedItems
219
     * @param LoggerInterface|null $logger
220
     *
221
     * @return array
222 9
     *
223
     * @SuppressWarnings(PHPMD.StaticAccess)
224
     * @SuppressWarnings(PHPMD.ElseExpression)
225
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
226
     */
227
    protected static function evaluateDenyOverrides(
228 9
        ContextInterface $context,
229 9
        array $optimizedTargets,
230 9
        array $encodedItems,
231 9
        ?LoggerInterface $logger
232 9
    ): array {
233
        $foundDeny          = false;
234 9
        $foundPermit        = false;
235 9
        $foundIntDeny       = false;
236
        $foundIntPermit     = false;
237 9
        $foundIntDenyPermit = false;
238 9
239 9
        $obligationsCollector = [];
240 9
        $adviceCollector      = [];
241
242 9
        foreach (static::evaluateTargets($context, $optimizedTargets, $logger) as $match => $itemId) {
243 1
            $encodedItem      = $encodedItems[$itemId];
244 1
            $packedEvaluation = static::evaluateItem($context, $match, $encodedItem, $logger);
245 9
            $evaluation       = static::unpackEvaluationValue($packedEvaluation);
246 5
            switch ($evaluation) {
247 5
                case EvaluationEnum::DENY:
248 9
                    $foundDeny = true;
249 1
                    break;
250 1
                case EvaluationEnum::PERMIT:
251 9
                    $foundPermit = true;
252 1
                    break;
253 1
                case EvaluationEnum::INDETERMINATE_DENY:
254 9
                    $foundIntDeny = true;
255 1
                    break;
256 1
                case EvaluationEnum::INDETERMINATE_PERMIT:
257
                    $foundIntPermit = true;
258
                    break;
259 9
                case EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT:
260 9
                    $foundIntDenyPermit = true;
261
                    break;
262 9
            }
263 9
264
            $itemObligations = static::unpackEvaluationObligations($packedEvaluation);
265
            $itemAdvice      = static::unpackEvaluationAdvice($packedEvaluation);
266 9
267 1
            $obligationsCollector = static::mergeToStorage($obligationsCollector, $evaluation, $itemObligations);
0 ignored issues
show
Bug introduced by
Since mergeToStorage() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeToStorage() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
268 8
            $adviceCollector      = static::mergeToStorage($adviceCollector, $evaluation, $itemAdvice);
0 ignored issues
show
Bug introduced by
Since mergeToStorage() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeToStorage() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
269 1
        }
270 8
271 1
        if ($foundDeny === true) {
272 8
            $evaluation = EvaluationEnum::DENY;
273 1
        } elseif ($foundIntDenyPermit === true) {
274 8
            $evaluation = EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT;
275 4
        } elseif ($foundIntDeny === true && ($foundIntPermit === true || $foundPermit === true)) {
276 5
            $evaluation = EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT;
277 1
        } elseif ($foundIntDeny === true) {
278
            $evaluation = EvaluationEnum::INDETERMINATE_DENY;
279 4
        } elseif ($foundPermit === true) {
280
            $evaluation = EvaluationEnum::PERMIT;
281
        } elseif ($foundIntPermit === true) {
282 9
            $evaluation = EvaluationEnum::INDETERMINATE_PERMIT;
283 9
        } else {
284
            $evaluation = EvaluationEnum::NOT_APPLICABLE;
285 9
        }
286
287
        $obligations = Encoder::getFulfillObligations($evaluation, $obligationsCollector);
288
        $advice      = Encoder::getAppliedAdvice($evaluation, $adviceCollector);
289
290
        return static::packEvaluationResult($evaluation, $obligations, $advice);
291
    }
292
293
    /**
294
     * @param ContextInterface     $context
295
     * @param array                $optimizedTargets
296
     * @param array                $encodedItems
297
     * @param LoggerInterface|null $logger
298
     *
299
     * @return array
300 7
     *
301
     * @SuppressWarnings(PHPMD.StaticAccess)
302
     * @SuppressWarnings(PHPMD.ElseExpression)
303
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
304
     */
305
    protected static function evaluatePermitOverrides(
306 7
        ContextInterface $context,
307 7
        array $optimizedTargets,
308 7
        array $encodedItems,
309 7
        ?LoggerInterface $logger
310 7
    ): array {
311
        $foundDeny          = false;
312 7
        $foundPermit        = false;
313 7
        $foundIntDeny       = false;
314
        $foundIntPermit     = false;
315 7
        $foundIntDenyPermit = false;
316 7
317 7
        $obligationsCollector = [];
318 7
        $adviceCollector      = [];
319
320 7
        foreach (static::evaluateTargets($context, $optimizedTargets, $logger) as $match => $itemId) {
321 2
            $encodedItem      = $encodedItems[$itemId];
322 2
            $packedEvaluation = static::evaluateItem($context, $match, $encodedItem, $logger);
323 6
            $evaluation       = static::unpackEvaluationValue($packedEvaluation);
324 4
            switch ($evaluation) {
325 4
                case EvaluationEnum::DENY:
326 5
                    $foundDeny = true;
327 1
                    break;
328 1
                case EvaluationEnum::PERMIT:
329 5
                    $foundPermit = true;
330 1
                    break;
331 1
                case EvaluationEnum::INDETERMINATE_DENY:
332 5
                    $foundIntDeny = true;
333 1
                    break;
334 1
                case EvaluationEnum::INDETERMINATE_PERMIT:
335
                    $foundIntPermit = true;
336
                    break;
337 7
                case EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT:
338 7
                    $foundIntDenyPermit = true;
339
                    break;
340 7
            }
341 7
342
            $itemObligations = static::unpackEvaluationObligations($packedEvaluation);
343
            $itemAdvice      = static::unpackEvaluationAdvice($packedEvaluation);
344 7
345 4
            $obligationsCollector = static::mergeToStorage($obligationsCollector, $evaluation, $itemObligations);
0 ignored issues
show
Bug introduced by
Since mergeToStorage() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeToStorage() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
346 4
            $adviceCollector      = static::mergeToStorage($adviceCollector, $evaluation, $itemAdvice);
0 ignored issues
show
Bug introduced by
Since mergeToStorage() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of mergeToStorage() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
347 1
        }
348 4
349 1
        if ($foundPermit === true) {
350 4
            $evaluation = EvaluationEnum::PERMIT;
351 1
        } elseif ($foundIntDenyPermit === true) {
352 4
            $evaluation = EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT;
353 1
        } elseif ($foundIntPermit === true && ($foundIntDeny === true || $foundDeny === true)) {
354 3
            $evaluation = EvaluationEnum::INDETERMINATE_DENY_OR_PERMIT;
355 1
        } elseif ($foundIntPermit === true) {
356
            $evaluation = EvaluationEnum::INDETERMINATE_PERMIT;
357 2
        } elseif ($foundDeny === true) {
358
            $evaluation = EvaluationEnum::DENY;
359
        } elseif ($foundIntDeny === true) {
360 7
            $evaluation = EvaluationEnum::INDETERMINATE_DENY;
361 7
        } else {
362
            $evaluation = EvaluationEnum::NOT_APPLICABLE;
363 7
        }
364
365
        $obligations = Encoder::getFulfillObligations($evaluation, $obligationsCollector);
366
        $advice      = Encoder::getAppliedAdvice($evaluation, $adviceCollector);
367
368
        return static::packEvaluationResult($evaluation, $obligations, $advice);
369
    }
370
371
    /**
372
     * @param array      $storage
373 12
     * @param string|int $key
374
     * @param array      $list
375 12
     *
376
     * @return array
377 12
     */
378
    private static function mergeToStorage(array $storage, $key, array $list): array
379
    {
380
        $storage[$key] = array_key_exists($key, $storage) === true ? array_merge($storage[$key], $list) : $list;
381
382
        return $storage;
383
    }
384
}
385