Passed
Pull Request — master (#1141)
by Abdul Malik
15:30 queued 03:41
created

RuleManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 0
c 0
b 0
f 0
dl 0
loc 3
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Security;
6
7
use Psr\Container\ContainerInterface;
8
use Spiral\Core\Attribute\Singleton;
9
use Spiral\Security\Exception\RuleException;
10
use Spiral\Security\Rule\CallableRule;
11
12
/**
13
 * Provides ability to request permissions rules based on it's name. Rules are being fetched from container.
14
 */
15
#[Singleton]
16
final class RuleManager implements RulesInterface
17
{
18
    private array $rules = [];
19
20 363
    public function __construct(
21
        private readonly ContainerInterface $container
22
    ) {
23 363
    }
24
25 2
    public function set(string $name, mixed $rule = null): RuleManager
26
    {
27 2
        if (empty($rule)) {
28 1
            $rule = $name;
29
        }
30
31 2
        if (!$this->validateRule($rule)) {
32 1
            throw new RuleException("Unable to set rule '{$name}', invalid rule body");
33
        }
34
35 1
        $this->rules[$name] = $rule;
36
37 1
        return $this;
38
    }
39
40 2
    public function remove(string $name): RuleManager
41
    {
42 2
        if (!$this->has($name)) {
43 1
            throw new RuleException("Undefined rule '{$name}'");
44
        }
45
46 1
        unset($this->rules[$name]);
47
48 1
        return $this;
49
    }
50
51 362
    public function has(string $name): bool
52
    {
53
        return match (true) {
54 362
            isset($this->rules[$name]) => true,
55
            // We are allowing to use class names without direct registration
56 361
            \class_exists($name) => true,
57
            // Relying on container binding
58 362
            default => $this->container->has($name)
59
        };
60
    }
61
62 7
    public function get(string $name): RuleInterface
63
    {
64 7
        if (!$this->has($name)) {
65 1
            throw new RuleException(\sprintf("Undefined rule '%s'", $name));
66
        }
67
68 6
        if (!isset($this->rules[$name])) {
69
            //Rule represented as class name
70 5
            $rule = $name;
71
        } else {
72 1
            $rule = $this->rules[$name];
73
        }
74
75 6
        if ($rule instanceof RuleInterface) {
76 1
            return $rule;
77
        }
78
79 6
        if (\is_string($rule)) {
80
            //We are expecting that rule points to
81 6
            $rule = $this->container->get($rule);
82
83 6
            if (!$rule instanceof RuleInterface) {
84 1
                throw new RuleException(\sprintf(
85 1
                    "Rule '%s' must point to RuleInterface, '%s' given",
86 1
                    $name,
87 1
                    !empty($rule) ? $rule::class : 'null'
88 1
                ));
89
            }
90
91 5
            return $rule;
92
        }
93
94
        //We have to respond using RuleInterface (expecting that rule is callable)
95 1
        return new CallableRule($rule);
96
    }
97
98
    /**
99
     * Must return true if rule is valid.
100
     */
101 2
    private function validateRule(mixed $rule): bool
102
    {
103 2
        if ($rule instanceof \Closure || $rule instanceof RuleInterface) {
104 1
            return true;
105
        }
106
107 2
        if (\is_array($rule)) {
108
            return \is_callable($rule, true);
109
        }
110
111 2
        if (\is_string($rule) && \class_exists($rule)) {
112
            try {
113 1
                $reflection = new \ReflectionClass($rule);
114
            } catch (\ReflectionException) {
115
                return false;
116
            }
117
118 1
            return $reflection->isSubclassOf(RuleInterface::class);
119
        }
120
121 1
        return false;
122
    }
123
}
124