Model   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 337
Duplicated Lines 0 %

Test Coverage

Coverage 99.35%

Importance

Changes 3
Bugs 2 Features 1
Metric Value
eloc 141
c 3
b 2
f 1
dl 0
loc 337
ccs 154
cts 155
cp 0.9935
rs 7.44
wmc 52

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getNameWithDomain() 0 3 1
A loadSection() 0 8 3
A newModelFromFile() 0 7 1
C getSubjectHierarchyMap() 0 52 12
A loadAssertion() 0 5 1
A addDef() 0 23 5
A sortPoliciesByPriority() 0 20 6
A loadFunctionMap() 0 3 1
A loadModel() 0 10 1
A getKeySuffix() 0 7 2
A newModelFromString() 0 7 1
A __construct() 0 2 1
A printModel() 0 6 3
A loadModelFromText() 0 10 1
A newModel() 0 3 1
A __clone() 0 10 3
B sortPoliciesBySubjectHierarchy() 0 38 9

How to fix   Complexity   

Complex Class

Complex classes like Model 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.

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 Model, and based on these observations, apply Extract Interface, too.

1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
declare(strict_types=1);
4
5
namespace Casbin\Model;
6
7
use Casbin\Config\Config;
8
use Casbin\Config\ConfigContract;
9
use Casbin\Exceptions\CasbinException;
10
use Casbin\Log\Log;
11
use Casbin\Util\Util;
12
13
/**
14
 * Class Model.
15
 * Represents the whole access control model.
16
 *
17
 * @package Casbin\Model
18
 * @author [email protected]
0 ignored issues
show
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 1
Loading history...
19
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
20
class Model extends Policy
21
{
22
    const DEFAULT_DOMAIN = '';
23
    const DEFAULT_SEPARATOR = '::';
24
25
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
26
     * @var array<string, string>
27
     */
28
    protected $sectionNameMap = [
29
        'r' => 'request_definition',
30
        'p' => 'policy_definition',
31
        'g' => 'role_definition',
32
        'e' => 'policy_effect',
33
        'm' => 'matchers',
34
    ];
35
36 369
    public function __construct()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
37
    {
38 369
    }
39
40 330
    public function __clone()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __clone()
Loading history...
41
    {
42 330
        $this->sectionNameMap = $this->sectionNameMap;
43 330
        $newAstMap = [];
44 330
        foreach ($this->items as $ptype => $ast) {
45 330
            foreach ($ast as $i => $v) {
46 330
                $newAstMap[$ptype][$i] = clone $v;
47
            }
48
        }
49 330
        $this->items = $newAstMap;
50 220
    }
51
52
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
53
     * @param ConfigContract $cfg
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
54
     * @param string $sec
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 9 spaces after parameter type; 1 found
Loading history...
55
     * @param string $key
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 9 spaces after parameter type; 1 found
Loading history...
56
     *
57
     * @return bool
58
     * @throws CasbinException
59
     */
60 360
    private function loadAssertion(ConfigContract $cfg, string $sec, string $key): bool
0 ignored issues
show
Coding Style introduced by
Private method name "Model::loadAssertion" must be prefixed with an underscore
Loading history...
61
    {
62 360
        $value = $cfg->getString($this->sectionNameMap[$sec] . '::' . $key);
63
64 360
        return $this->addDef($sec, $key, $value);
65
    }
66
67
    /**
68
     * Adds an assertion to the model.
69
     *
70
     * @param string $sec
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
71
     * @param string $key
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
72
     * @param string $value
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
73
     *
74
     * @return bool
75
     * @throws CasbinException
76
     */
77 369
    public function addDef(string $sec, string $key, string $value): bool
78
    {
79 369
        if ('' == $value) {
80 360
            return false;
81
        }
82
83 369
        $ast = new Assertion();
84 369
        $ast->key = $key;
85 369
        $ast->value = $value;
86 369
        $ast->initPriorityIndex();
87
88 369
        if ('r' == $sec || 'p' == $sec) {
89 369
            $ast->tokens = explode(',', $ast->value);
90 369
            foreach ($ast->tokens as $i => $token) {
91 369
                $ast->tokens[$i] = $key . '_' . trim($token);
92
            }
93
        } else {
94 369
            $ast->value = Util::removeComments(Util::escapeAssertion($ast->value));
95
        }
96
97 369
        $this->items[$sec][$key] = $ast;
98
99 369
        return true;
100
    }
101
102
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
103
     * @param int $i
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
104
     *
105
     * @return string
106
     */
107 360
    private function getKeySuffix(int $i): string
0 ignored issues
show
Coding Style introduced by
Private method name "Model::getKeySuffix" must be prefixed with an underscore
Loading history...
108
    {
109 360
        if (1 == $i) {
110 360
            return '';
111
        }
112
113 360
        return (string)$i;
114
    }
115
116
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
117
     * @param ConfigContract $cfg
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
118
     * @param string $sec
0 ignored issues
show
Coding Style introduced by
Expected 9 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
119
     * @throws CasbinException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
120
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
121 360
    private function loadSection(ConfigContract $cfg, string $sec): void
0 ignored issues
show
Coding Style introduced by
Private method name "Model::loadSection" must be prefixed with an underscore
Loading history...
122
    {
123 360
        $i = 1;
124 240
        for (; ;) {
125 360
            if (!$this->loadAssertion($cfg, $sec, $sec . $this->getKeySuffix($i))) {
126 360
                break;
127
            } else {
128 360
                ++$i;
129
            }
130
        }
131 240
    }
132
133
    /**
134
     * Creates an empty model.
135
     *
136
     * @return Model
137
     */
138 342
    public static function newModel(): self
139
    {
140 342
        return new self();
141
    }
142
143
    /**
144
     * Creates a model from a .CONF file.
145
     *
146
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
147
     *
148
     * @return Model
149
     * @throws CasbinException
150
     */
151 330
    public static function newModelFromFile(string $path): self
152
    {
153 330
        $m = self::newModel();
154
155 330
        $m->loadModel($path);
156
157 330
        return $m;
158
    }
159
160
    /**
161
     * Creates a model from a string which contains model text.
162
     *
163
     * @param string $text
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
164
     *
165
     * @return Model
166
     * @throws CasbinException
167
     */
168 3
    public static function newModelFromString(string $text): self
169
    {
170 3
        $m = self::newModel();
171
172 3
        $m->loadModelFromText($text);
173
174 3
        return $m;
175
    }
176
177
    /**
178
     * Loads the model from model CONF file.
179
     *
180
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
181
     * @throws CasbinException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
182
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
183 354
    public function loadModel(string $path): void
184
    {
185 354
        $cfg = Config::newConfig($path);
186
187 354
        $this->loadSection($cfg, 'r');
188 354
        $this->loadSection($cfg, 'p');
189 354
        $this->loadSection($cfg, 'e');
190 354
        $this->loadSection($cfg, 'm');
191
192 354
        $this->loadSection($cfg, 'g');
193 236
    }
194
195
    /**
196
     * Loads the model from the text.
197
     *
198
     * @param string $text
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
199
     * @throws CasbinException
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
200
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
201 6
    public function loadModelFromText(string $text): void
202
    {
203 6
        $cfg = Config::newConfigFromText($text);
204
205 6
        $this->loadSection($cfg, 'r');
206 6
        $this->loadSection($cfg, 'p');
207 6
        $this->loadSection($cfg, 'e');
208 6
        $this->loadSection($cfg, 'm');
209
210 6
        $this->loadSection($cfg, 'g');
211 4
    }
212
213
    /**
214
     * Prints the model to the log.
215
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
216 333
    public function printModel(): void
217
    {
218 333
        Log::logPrint('Model:');
219 333
        foreach ($this->items as $k => $v) {
220 333
            foreach ($v as $i => $j) {
221 333
                Log::logPrintf('%s.%s: %s', $k, $i, $j->value);
222
            }
223
        }
224 222
    }
225
226
    /**
227
     * Loads an initial function map.
228
     *
229
     * @return FunctionMap
230
     */
231 336
    public static function loadFunctionMap(): FunctionMap
232
    {
233 336
        return FunctionMap::loadFunctionMap();
234
    }
235
236 6
    public function getNameWithDomain(string $domain, string $name): string
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getNameWithDomain()
Loading history...
237
    {
238 6
        return $domain . self::DEFAULT_SEPARATOR . $name;
239
    }
240
241 6
    public function getSubjectHierarchyMap(array $policies): array
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getSubjectHierarchyMap()
Loading history...
242
    {
243 6
        $subjectHierarchyMap = [];
244
        // Tree structure of role
245 6
        $policyMap = [];
246 6
        foreach ($policies as $policy) {
247 6
            if (count($policy) < 2) {
248
                throw new CasbinException('policy g expect 2 more params');
249
            }
250 6
            $domain = self::DEFAULT_DOMAIN;
251 6
            if (count($policy) != 2) {
252 3
                $domain = $policy[2];
253
            }
254 6
            $child = $this->getNameWithDomain($domain, $policy[0]);
255 6
            $parent = $this->getNameWithDomain($domain, $policy[1]);
256 6
            $policyMap[$parent][] = $child;
257 6
            if (!isset($subjectHierarchyMap[$child])) {
258 6
                $subjectHierarchyMap[$child] = 0;
259
            }
260 6
            if (!isset($subjectHierarchyMap[$parent])) {
261 6
                $subjectHierarchyMap[$parent] = 0;
262
            }
263 6
            $subjectHierarchyMap[$child] = 1;
264
        }
265
        // Use queues for levelOrder
266 6
        $queue = [];
267 6
        foreach ($subjectHierarchyMap as $k => $v) {
268 6
            $root = $k;
269 6
            if ($v != 0) {
270 6
                continue;
271
            }
272 6
            $lv = 0;
273 6
            $queue[] = $root;
274 6
            while (count($queue) != 0) {
275 6
                $sz = count($queue);
276 6
                for ($i = 0; $i < $sz; $i++) {
277 6
                    $node = $queue[array_key_first($queue)];
278 6
                    unset($queue[array_key_first($queue)]);
279
280 6
                    $nodeValue = $node;
281 6
                    $subjectHierarchyMap[$nodeValue] = $lv;
282 6
                    if (isset($policyMap[$nodeValue])) {
283 6
                        foreach ($policyMap[$nodeValue] as $child) {
284 6
                            $queue[] = $child;
285
                        }
286
                    }
287
                }
288 6
                $lv++;
289
            }
290
        }
291
292 6
        return $subjectHierarchyMap;
293
    }
294
295 309
    public function sortPoliciesBySubjectHierarchy(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function sortPoliciesBySubjectHierarchy()
Loading history...
296
    {
297 309
        if ($this->items['e']['e']->value != 'subjectPriority(p_eft) || deny') {
298 303
            return;
299
        }
300 6
        $subIndex = 0;
301 6
        $domainIndex = -1;
302 6
        foreach ($this->items['p'] as $ptype => $assertion) {
303 6
            foreach ($assertion->tokens as $index => $token) {
304 6
                if ($token == sprintf('%s_dom', $ptype)) {
305 3
                    $domainIndex = $index;
306 5
                    break;
307
                }
308
            }
309 6
            $policies = &$assertion->policy;
310 6
            $subjectHierarchyMap = $this->getSubjectHierarchyMap($this->items['g']['g']->policy);
311
312 2
            usort($policies, function ($i, $j) use ($subIndex, $domainIndex, $subjectHierarchyMap): int {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
313 6
                $domain1 = self::DEFAULT_DOMAIN;
314 6
                $domain2 = self::DEFAULT_DOMAIN;
315 6
                if ($domainIndex != -1) {
316 3
                    $domain1 = $i[$domainIndex];
317 3
                    $domain2 = $j[$domainIndex];
318
                }
319 6
                $name1 = $this->getNameWithDomain($domain1, $i[$subIndex]);
320 6
                $name2 = $this->getNameWithDomain($domain2, $j[$subIndex]);
321
322 6
                $p1 = $subjectHierarchyMap[$name1] ?? 0;
323 6
                $p2 = $subjectHierarchyMap[$name2] ?? 0;
324
325 6
                if ($p1 == $p2) {
326 6
                    return 0;
327
                }
328 6
                return ($p1 > $p2) ? -1 : 1;
329 6
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
330
331 6
            foreach ($assertion->policy as $i => $policy) {
332 6
                $assertion->policyMap[implode(',', $policy)] = $i;
333
            }
334
        }
335 4
    }
336
337 309
    public function sortPoliciesByPriority(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function sortPoliciesByPriority()
Loading history...
338
    {
339 309
        foreach ($this->items['p'] as $ptype => $assertion) {
340 309
            $index = array_search(sprintf("%s_priority", $ptype), $assertion->tokens);
341 309
            if ($index !== false) {
342 3
                $assertion->priorityIndex = intval($index);
343
            } else {
344 306
                continue;
345
            }
346 3
            $policies = &$assertion->policy;
347 1
            usort($policies, function ($i, $j) use ($assertion): int {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
348 3
                $p1 = $i[$assertion->priorityIndex];
349 3
                $p2 = $j[$assertion->priorityIndex];
350 3
                if ($p1 == $p2) {
351 3
                    return 0;
352
                }
353 3
                return ($p1 < $p2) ? -1 : 1;
354 3
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
355 3
            foreach ($assertion->policy as $i => $policy) {
356 3
                $assertion->policyMap[implode(',', $policy)] = $i;
357
            }
358
        }
359 206
    }
360
}
361