Model   B
last analyzed

Complexity

Total Complexity 51

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Test Coverage

Coverage 99.34%

Importance

Changes 3
Bugs 2 Features 1
Metric Value
eloc 138
dl 0
loc 334
ccs 150
cts 151
cp 0.9934
rs 7.92
c 3
b 2
f 1
wmc 51

17 Methods

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

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\Constant\Constants;
10
use Casbin\Exceptions\CasbinException;
11
use Casbin\Log\Log;
12
use Casbin\Util\Util;
13
14
/**
15
 * Class Model.
16
 * Represents the whole access control model.
17
 *
18
 * @package Casbin\Model
19
 * @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...
20
 */
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...
21
class Model extends Policy
22
{
23
    const DEFAULT_DOMAIN = '';
24
    const DEFAULT_SEPARATOR = '::';
25
26
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
27
     * @var array<string, string>
28
     */
29
    protected $sectionNameMap = [
30
        'r' => 'request_definition',
31
        'p' => 'policy_definition',
32
        'g' => 'role_definition',
33
        'e' => 'policy_effect',
34
        'm' => 'matchers',
35
    ];
36
37 390
    public function __construct()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
38
    {
39 390
    }
40
41 351
    public function __clone()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __clone()
Loading history...
42
    {
43 351
        $this->sectionNameMap = $this->sectionNameMap;
44 351
        $newAstMap = [];
45 351
        foreach ($this->items as $ptype => $ast) {
46 351
            foreach ($ast as $i => $v) {
47 351
                $newAstMap[$ptype][$i] = clone $v;
48
            }
49
        }
50 351
        $this->items = $newAstMap;
51 234
    }
52
53
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
54
     * @param ConfigContract $cfg
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
55
     * @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...
56
     * @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...
57
     *
58
     * @return bool
59
     * @throws CasbinException
60
     */
61 381
    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...
62
    {
63 381
        $value = $cfg->getString($this->sectionNameMap[$sec] . '::' . $key);
64
65 381
        return $this->addDef($sec, $key, $value);
66
    }
67
68
    /**
69
     * Adds an assertion to the model.
70
     *
71
     * @param string $sec
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
72
     * @param string $key
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
73
     * @param string $value
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
74
     *
75
     * @return bool
76
     * @throws CasbinException
77
     */
78 390
    public function addDef(string $sec, string $key, string $value): bool
79
    {
80 390
        if ('' == $value) {
81 381
            return false;
82
        }
83
84 390
        $ast = new Assertion();
85 390
        $ast->key = $key;
86 390
        $ast->value = $value;
87
88 390
        if ('r' == $sec || 'p' == $sec) {
89 390
            $ast->tokens = explode(',', $ast->value);
90 390
            foreach ($ast->tokens as $i => $token) {
91 390
                $ast->tokens[$i] = $key . '_' . trim($token);
92
            }
93
        } else {
94 390
            $ast->value = Util::removeComments(Util::escapeAssertion($ast->value));
95
        }
96
97 390
        $this->items[$sec][$key] = $ast;
98
99 390
        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 381
    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 381
        if (1 == $i) {
110 381
            return '';
111
        }
112
113 381
        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 381
    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 381
        $i = 1;
124 254
        for (; ;) {
125 381
            if (!$this->loadAssertion($cfg, $sec, $sec . $this->getKeySuffix($i))) {
126 381
                break;
127
            } else {
128 381
                ++$i;
129
            }
130
        }
131 254
    }
132
133
    /**
134
     * Creates an empty model.
135
     *
136
     * @return Model
137
     */
138 363
    public static function newModel(): self
139
    {
140 363
        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 351
    public static function newModelFromFile(string $path): self
152
    {
153 351
        $m = self::newModel();
154
155 351
        $m->loadModel($path);
156
157 351
        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 375
    public function loadModel(string $path): void
184
    {
185 375
        $cfg = Config::newConfig($path);
186
187 375
        $this->loadSection($cfg, 'r');
188 375
        $this->loadSection($cfg, 'p');
189 375
        $this->loadSection($cfg, 'e');
190 375
        $this->loadSection($cfg, 'm');
191
192 375
        $this->loadSection($cfg, 'g');
193 250
    }
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 354
    public function printModel(): void
217
    {
218 354
        Log::logPrint('Model:');
219 354
        foreach ($this->items as $k => $v) {
220 354
            foreach ($v as $i => $j) {
221 354
                Log::logPrintf('%s.%s: %s', $k, $i, $j->value);
222
            }
223
        }
224 236
    }
225
226
    /**
227
     * Loads an initial function map.
228
     *
229
     * @return FunctionMap
230
     */
231 357
    public static function loadFunctionMap(): FunctionMap
232
    {
233 357
        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 330
    public function sortPoliciesBySubjectHierarchy(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function sortPoliciesBySubjectHierarchy()
Loading history...
296
    {
297 330
        if ($this->items['e']['e']->value != Constants::SUBJECT_PRIORITY_EFFECT) {
298 324
            return;
299
        }
300 6
        $subIndex = 0;
301
302 6
        foreach ($this->items['p'] as $ptype => $assertion) {
303
            try {
304 6
                $domainIndex = $this->getFieldIndex($ptype, Constants::DOMAIN_INDEX);
305 3
            } catch (CasbinException $e) {
306 3
                $domainIndex = -1;
307
            }
308 6
            $policies = &$assertion->policy;
309 6
            $subjectHierarchyMap = $this->getSubjectHierarchyMap($this->items['g']['g']->policy);
310
311 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...
312 6
                $domain1 = self::DEFAULT_DOMAIN;
313 6
                $domain2 = self::DEFAULT_DOMAIN;
314 6
                if ($domainIndex != -1) {
315 3
                    $domain1 = $i[$domainIndex];
316 3
                    $domain2 = $j[$domainIndex];
317
                }
318 6
                $name1 = $this->getNameWithDomain($domain1, $i[$subIndex]);
319 6
                $name2 = $this->getNameWithDomain($domain2, $j[$subIndex]);
320
321 6
                $p1 = $subjectHierarchyMap[$name1] ?? 0;
322 6
                $p2 = $subjectHierarchyMap[$name2] ?? 0;
323
324 6
                if ($p1 == $p2) {
325 6
                    return 0;
326
                }
327 6
                return ($p1 > $p2) ? -1 : 1;
328 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...
329
330 6
            foreach ($assertion->policy as $i => $policy) {
331 6
                $assertion->policyMap[implode(',', $policy)] = $i;
332
            }
333
        }
334 4
    }
335
336 330
    public function sortPoliciesByPriority(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function sortPoliciesByPriority()
Loading history...
337
    {
338 330
        foreach ($this->items['p'] as $ptype => $assertion) {
339
            try {
340 330
                $priorityIndex = $this->getFieldIndex($ptype, Constants::PRIORITY_INDEX);
341 327
            } catch (CasbinException $e) {
342 327
                continue;
343
            }
344 6
            $policies = &$assertion->policy;
345 2
            usort($policies, function ($i, $j) use ($priorityIndex): 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...
346 6
                $p1 = $i[$priorityIndex];
347 6
                $p2 = $j[$priorityIndex];
348 6
                if ($p1 == $p2) {
349 6
                    return 0;
350
                }
351 6
                return ($p1 < $p2) ? -1 : 1;
352 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...
353 6
            foreach ($assertion->policy as $i => $policy) {
354 6
                $assertion->policyMap[implode(',', $policy)] = $i;
355
            }
356
        }
357 220
    }
358
}
359