Passed
Push — master ( 7f699f...e24ab0 )
by Kirill
03:06
created

RuleManager::set()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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