Acl   F
last analyzed

Complexity

Total Complexity 73

Size/Duplication

Total Lines 403
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 97.75%

Importance

Changes 0
Metric Value
wmc 73
lcom 1
cbo 9
dl 0
loc 403
ccs 174
cts 178
cp 0.9775
rs 2.56
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
C addRule() 0 72 17
A getRuleClass() 0 4 1
A setRuleClass() 0 16 4
A hasRule() 0 16 4
A isAllowed() 0 4 1
C isAllowedReturnResultSimple() 0 49 12
B isAllowedReturnResult() 0 38 8
A getNames() 0 16 5
C removeRule() 0 59 17
A removeAllRules() 0 4 1
A removeRuleById() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like Acl 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 Acl, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SimpleAcl;
4
5
use SimpleAcl\Exception\InvalidArgumentException;
6
use SimpleAcl\Exception\RuntimeException;
7
use SimpleAcl\Resource\ResourceAggregateInterface;
8
use SimpleAcl\Role\RoleAggregateInterface;
9
10
/**
11
 * Access Control List (ACL) management.
12
 *
13
 * @package SimpleAcl
14
 */
15
class Acl
16
{
17
  /**
18
   * Contains registered rules.
19
   *
20
   * @var Rule[]
21
   */
22
  protected $rules = [];
23
24
  /**
25
   * Class name used when rule created from string.
26
   *
27
   * @var string
28
   */
29
  protected $ruleClass = 'SimpleAcl\Rule';
30
31
  /**
32
   * Adds rule.
33
   *
34
   * Assign $role, $resource and $action to added rule.
35
   * If rule was already registered only change $role, $resource and $action for that rule.
36
   *
37
   * This method accept 1, 2, 3 or 4 arguments:
38
   *
39
   * addRule($rule)
40
   * addRule($rule, $action)
41
   * addRule($role, $resource, $rule)
42
   * addRule($role, $resource, $rule, $action)
43
   *
44
   * @param Role     ..$role
45
   * @param Resource ..$resource
46
   * @param Rule|string ..$rule
47
   * @param mixed    ..$action
48
   *
49
   * @throws InvalidArgumentException
50 37
   */
51
  public function addRule()
52 37
  {
53 37
    $args = \func_get_args();
54
    $argsCount = \count($args);
55 37
56 37
    $role = null;
57 37
    $resource = null;
58
    $action = null;
59 37
60
    if ($argsCount == 4 || $argsCount == 3) {
61 34
62 34
      $role = $args[0];
63 34
      $resource = $args[1];
64
      $rule = $args[2];
65 34
66 33
      if ($argsCount == 4) {
67 33
        $action = $args[3];
68
      }
69 37
70
    } elseif ($argsCount == 2) {
71 2
72 2
      $rule = $args[0];
73
      $action = $args[1];
74 4
75 2
    } elseif ($argsCount == 1) {
76 2
      $rule = $args[0];
77 1
    } else {
78
      throw new InvalidArgumentException(__METHOD__ . ' accepts only one, tow, three or four arguments');
79
    }
80
81 36
    if (
82 36
        null !== $role
83
        &&
84 36
        !$role instanceof Role
85 1
    ) {
86
      throw new InvalidArgumentException('Role must be an instance of SimpleAcl\Role or null');
87
    }
88
89 35
    if (
90 35
        null !== $resource
91
        &&
92 35
        !$resource instanceof Resource
93 1
    ) {
94
      throw new InvalidArgumentException('Resource must be an instance of SimpleAcl\Resource or null');
95
    }
96 34
97 3
    if (\is_string($rule)) {
98 3
      $ruleClass = $this->getRuleClass();
99 3
      $rule = new $ruleClass($rule);
100
    }
101 34
102 1
    if (!$rule instanceof Rule) {
103
      throw new InvalidArgumentException('Rule must be an instance of SimpleAcl\Rule or string');
104
    }
105 33
106
    $exchange = $this->hasRule($rule);
107 33
108 5
    if ($exchange) {
109 5
      $rule = $exchange;
110 33
    } else {
111
      $this->rules[] = $rule;
112
    }
113 33
114 31
    if ($argsCount == 3 || $argsCount == 4) {
115 31
      $rule->setRole($role);
116 31
      $rule->setResource($resource);
117
    }
118 33
119 31
    if ($argsCount == 2 || $argsCount == 4) {
120 31
      $rule->setAction($action);
121 33
    }
122
  }
123
124
  /**
125
   * Return rule class.
126
   *
127
   * @return string
128 5
   */
129
  public function getRuleClass(): string
130 5
  {
131
    return $this->ruleClass;
132
  }
133
134
  /**
135
   * Set rule class.
136
   *
137
   * @param string $ruleClass
138 5
   */
139
  public function setRuleClass(string $ruleClass)
140 5
  {
141 1
    if (!class_exists($ruleClass)) {
142
      throw new RuntimeException('Rule class not exist');
143
    }
144
145
    if (
146 4
        $ruleClass != 'SimpleAcl\Rule'
147 2
        &&
148 4
        !is_subclass_of($ruleClass, 'SimpleAcl\Rule')
149 1
    ) {
150
      throw new RuntimeException('Rule class must be instance of SimpleAcl\Rule');
151
    }
152 3
153 3
    $this->ruleClass = $ruleClass;
154
  }
155
156
  /**
157
   * Return true if rule was already added.
158
   *
159
   * @param Rule|mixed $needRule Rule or rule's id
160
   *
161
   * @return bool|Rule
162 33
   */
163
  public function hasRule($needRule)
164 33
  {
165 33
    if ($needRule instanceof Rule) {
166 33
      $needRuleId = $needRule->id;
167 1
    } else {
168
      $needRuleId = $needRule;
169
    }
170 33
171 24
    foreach ($this->rules as $rule) {
172 6
      if ($rule->id === $needRuleId) {
173
        return $rule;
174 33
      }
175
    }
176 33
177
    return false;
178
  }
179
180
  /**
181
   * Checks is access allowed.
182
   *
183
   * @param string|RoleAggregateInterface     $roleName
184
   * @param string|ResourceAggregateInterface $resourceName
185
   * @param string                            $ruleName
186
   *
187
   * @return bool
188 28
   */
189
  public function isAllowed($roleName, $resourceName, $ruleName)
190 28
  {
191
    return $this->isAllowedReturnResult($roleName, $resourceName, $ruleName)->get();
192
  }
193
194
  /**
195
   * Simple checks is access allowed.
196
   *
197
   * @param string|RoleAggregateInterface     $roleAggregate
198
   * @param string|ResourceAggregateInterface $resourceAggregate
199
   * @param string                            $ruleName
200
   * @param RuleResultCollection              $ruleResultCollection
201
   *
202
   * @return RuleResultCollection|null null if there wasn't a clear result
203 28
   */
204
  protected function isAllowedReturnResultSimple($roleAggregate, $resourceAggregate, $ruleName, $ruleResultCollection)
205
  {
206 28
    if (
207 28
        \is_string($ruleName)
208 28
        &&
209 28
        \is_string($roleAggregate)
210 24
        &&
211 28
        \is_string($resourceAggregate)
212
    ) {
213 22
214
      foreach ($this->rules as $ruleTmp) {
215
216 21
        // INFO: we can't use "getName()" here, because of performance issue
217 9
        if ($ruleTmp->name !== $ruleName) {
218
          continue;
219
        }
220 19
221 19
        $resourceTmp = $ruleTmp->getResource();
222
        $roleTmp = $ruleTmp->getRole();
223
224
        if (
225
            (
226 19
                $resourceTmp instanceof Resource
227 19
                &&
228 19
                $resourceTmp->getName() === $resourceAggregate
229 19
            )
230
            &&
231
            (
232 19
                $roleTmp instanceof Role
233 18
                &&
234 18
                $roleTmp->getName() === $roleAggregate
235 19
            )
236 17
        ) {
237
          $resultTmp = $ruleTmp->isAllowed($ruleName, $roleAggregate, $resourceAggregate);
238 17
239
          if ($resultTmp && null === $resultTmp->getAction()) {
240
            unset($resultTmp);
241
          } else {
242 17
            // Set null if rule don't match any role or resource.
243
            $ruleResultCollection->add($resultTmp);
244 17
245
            return $ruleResultCollection;
246
          }
247 15
        }
248 15
      }
249
    }
250 22
251
    return null;
252
  }
253
254
  /**
255
   * Checks is access allowed.
256
   *
257
   * @param string|RoleAggregateInterface     $roleAggregate
258
   * @param string|ResourceAggregateInterface $resourceAggregate
259
   * @param string                            $ruleName
260
   *
261
   * @return RuleResultCollection
262 28
   */
263
  public function isAllowedReturnResult($roleAggregate, $resourceAggregate, $ruleName)
264 28
  {
265
    $ruleResultCollection = new RuleResultCollection();
266 28
267 28
    $tmpResult = $this->isAllowedReturnResultSimple($roleAggregate, $resourceAggregate, $ruleName, $ruleResultCollection);
268 17
    if ($tmpResult !== null) {
269
      return $tmpResult;
270
    }
271 22
272 22
    $roles = $this->getNames($roleAggregate);
273
    $resources = $this->getNames($resourceAggregate);
274 22
275 21
    foreach ($roles as $roleName) {
276 20
      foreach ($resources as $resourceName) {
277
        foreach ($this->rules as $rule) {
278
279 18
          if (
280 18
              \is_string($ruleName)
281 18
              &&
282 18
              !is_subclass_of($rule, 'SimpleAcl\Rule')
283 17
              &&
284 18
              $rule->getName() !== $ruleName
285 7
          ) {
286
            continue;
287
          }
288 17
289
          $rule->resetAggregate($roleAggregate, $resourceAggregate);
290 17
291
          $result = $rule->isAllowed($ruleName, $roleName, $resourceName);
292
293 17
          // Set null if rule don't match any role or resource.
294 20
          $ruleResultCollection->add($result);
295 21
        }
296 22
      }
297
    }
298 22
299
    return $ruleResultCollection;
300
  }
301
302
  /**
303
   * Get names.
304
   *
305
   * @param string|RoleAggregateInterface|ResourceAggregateInterface $object
306
   *
307
   * @return array
308 22
   */
309
  protected function getNames($object): array
310 22
  {
311 19
    if (\is_string($object) || null === $object) {
312 7
      return [$object];
313 5
    }
314 5
315 4
    if ($object instanceof RoleAggregateInterface) {
316
      return $object->getRolesNames();
317
    }
318 1
319
    if ($object instanceof ResourceAggregateInterface) {
320
      return $object->getResourcesNames();
321
    }
322
323
    return [];
324
  }
325
326
  /**
327
   * Remove rules by rule name and (or) role and resource.
328
   *
329 3
   * @param null|string $roleName
330
   * @param null|string $resourceName
331
   * @param null|string $ruleName
332 3
   * @param bool        $all
333 3
   */
334 3
  public function removeRule($roleName = null, $resourceName = null, $ruleName = null, bool $all = true)
335 3
  {
336 3
    if (
337 3
        null === $roleName
338 2
        &&
339
        null === $resourceName
340 2
        &&
341
        null === $ruleName
342
    ) {
343 2
      $this->removeAllRules();
344
345
      return;
346
    }
347 2
348
    foreach ($this->rules as $ruleIndex => $rule) {
349
      if (
350 2
          (
351 2
              $ruleName === null
352 2
              ||
353 2
              (
354 2
                  $ruleName !== null
355
                  &&
356
                  $ruleName === $rule->getName()
357 2
              )
358
          )
359
          &&
360 2
          (
361 2
              $roleName === null
362 2
              ||
363 2
              (
364 2
                  $roleName !== null
365
                  &&
366
                  $rule->getRole()
367 2
                  &&
368
                  $rule->getRole()->getName() === $roleName
369
              )
370 2
          )
371 2
          &&
372 2
          (
373 2
              $resourceName === null
374 2
              ||
375 2
              (
376 2
                  $resourceName !== null
377
                  &&
378 1
                  $rule->getResource()
379 1
                  &&
380
                  $rule->getResource()->getName() === $resourceName
381
              )
382
          )
383 1
      ) {
384 2
385 2
        unset($this->rules[$ruleIndex]);
386
        if (!$all) {
387
          return;
388
        }
389
390
      }
391 3
    }
392
  }
393 3
394 3
  /**
395
   * Remove all rules.
396
   */
397
  public function removeAllRules()
398
  {
399
    $this->rules = [];
400
  }
401 2
402
  /**
403 2
   * Removes rule by its id.
404 2
   *
405 2
   * @param mixed $ruleId
406
   */
407 2
  public function removeRuleById($ruleId)
408
  {
409 1
    foreach ($this->rules as $ruleIndex => $rule) {
410 1
      if ($rule->id === $ruleId) {
411
        unset($this->rules[$ruleIndex]);
412
413
        return;
414
      }
415
    }
416
  }
417
}
418