Manager   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 360
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 2
Metric Value
eloc 89
c 6
b 0
f 2
dl 0
loc 360
rs 8.8798
wmc 44

16 Methods

Rating   Name   Duplication   Size   Complexity  
A assignRole() 0 11 4
A __construct() 0 3 1
A considerRestriction() 0 15 5
B considerPermission() 0 38 10
A getAuthorizableMigration() 0 21 3
A getAuthorizableModel() 0 3 1
A detectScope() 0 5 1
A getPermission() 0 15 3
A __get() 0 11 3
A hasPermission() 0 5 1
A regainPermission() 0 5 1
A removeRole() 0 9 3
A grantPermission() 0 9 2
A hasRole() 0 3 1
A checkScopeAndLevel() 0 6 3
A checkScope() 0 6 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Mrluke\Privileges;
4
5
use Illuminate\Database\Eloquent\Model;
6
use InvalidArgumentException;
7
use Mrluke\Configuration\Contracts\ArrayHost as Host;
8
use Mrluke\Configuration\Exceptions\ConfigurationException;
9
use Mrluke\Privileges\Contracts\Authorizable;
10
use Mrluke\Privileges\Contracts\Permitable;
11
use Mrluke\Privileges\Contracts\Role;
12
13
/**
14
 * Manager is a class that provides complex methods
15
 * to assign Authorizable to permissions and role.
16
 *
17
 * @author    Łukasz Sitnicki (mr-luke)
18
 * @link      http://github.com/mr-luke/privileges
19
 *
20
 * @category  Laravel
21
 * @package   mr-luke/privileges
22
 * @license   MIT
23
 * @version   1.0.0
24
 *
25
 * @property  mixed  $allowed_value
26
 * @property  string $authKeyName
27
 * @property  mixed  $denied_value
28
 */
29
class Manager
30
{
31
    /**
32
     * Authorizable primary key name.
33
     *
34
     * @var string
35
     */
36
    protected $authKeyName;
37
38
    /**
39
     * Authorizable primary key type.
40
     *
41
     * @var string
42
     */
43
    protected $authKeyType;
44
45
    /**
46
     * Authorizable table name.
47
     *
48
     * @var string
49
     */
50
    protected $authTable;
51
52
    /**
53
     * Configuration instance.
54
     *
55
     * @var \Mrluke\Configuration\Contracts\ArrayHost
56
     */
57
    protected $config;
58
59
    public function __construct(Host $config)
60
    {
61
        $this->config = $config;
62
    }
63
64
    /**
65
     * Assign Authorizable to given role.
66
     *
67
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
68
     * @param mixed                                     $role
69
     *
70
     * @return void
71
     */
72
    public function assignRole(Authorizable $auth, $role): void
73
    {
74
        if ($role instanceof Role) {
75
            $auth->roles()->syncWithoutDetaching([$role->id]);
76
        } elseif (is_integer($role)) {
77
            $auth->roles()->syncWithoutDetaching([$role]);
78
        } elseif (is_array($role)) {
79
            $auth->roles()->syncWithoutDetaching($role);
80
        } else {
81
            throw new InvalidArgumentException(
82
                sprintf('[role] parameter must be type of integer or instance of \Mrluke\Privileges\Contracts\Role. %s type given.', gettype($role))
83
            );
84
        }
85
    }
86
87
    /**
88
     * Return permission level based on personal's & role's permission.
89
     *
90
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
91
     * @param string|array                              $scopes
92
     *
93
     * @return int
94
     *
95
     * @throws \InvalidArgumentException
96
     */
97
    public function considerPermission(Authorizable $auth, $scopes): int
98
    {
99
        if (!is_array($scopes)) {
100
            $scopes = [$scopes];
101
        }
102
103
        // Validate scopes ahead of all conditions
104
        //
105
        foreach ($scopes as $scope) {
106
            $this->checkScope($scope);
107
        }
108
109
        foreach ($scopes as $scope) {
110
            if ($personal = $this->getPermission($auth, $scope, false)) {
111
                // Personal permissions has priority
112
                // over role's ones.
113
                //
114
                return $personal->level;
115
            }
116
        }
117
118
        $general = 0;
119
        if (!$auth->relationLoaded('roles')) {
120
            $auth->load('roles.permissions');
121
        }
122
123
        foreach ($scopes as $scope) {
124
            foreach ($auth->roles as $r) {
125
                // Let's check if there's a given scope defined
126
                // as a permission in any of Authorizable roles.
127
                //
128
                if ($p = $this->getPermission($r, $scope, false)) {
129
                    ($p->level < $general) ?: $general = $p->level;
130
                }
131
            }
132
        }
133
134
        return $general;
135
    }
136
137
    /**
138
     * Return restrictions based on roles.
139
     *
140
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
141
     *
142
     * @return array
143
     *
144
     * @throws \InvalidArgumentException
145
     */
146
    public function considerRestriction(Authorizable $auth): array
147
    {
148
        $restrictions = [];
149
        $level        = 0;
150
151
        if (!$auth->relationLoaded('roles')) {
152
            $auth->load('roles.permissions');
153
        }
154
155
        foreach ($auth->roles as $r) {
156
            // Let's check if there're restrictions for a roles.
157
            ($level < $r->level) ?: $restrictions = $r->restrictions;
158
        }
159
160
        return is_array($restrictions) ? $restrictions : [];
161
    }
162
163
    /**
164
     * Detect which scope should be applied for given model.
165
     *
166
     * @param string $model
167
     *
168
     * @return string|null
169
     */
170
    public function detectScope(string $model)
171
    {
172
        return $this->config->get(
173
            'mapping.' . $model,
174
            $this->config->get('mapping_default')
175
        );
176
    }
177
178
    /**
179
     * Return Authorizable model reference.
180
     *
181
     * @return string
182
     */
183
    public function getAuthorizableModel(): string
184
    {
185
        return $this->config->get('authorizable');
186
    }
187
188
    /**
189
     * Return Authorizable migration config.
190
     *
191
     * @return array
192
     */
193
    public function getAuthorizableMigration(): array
194
    {
195
        if (is_null($this->authKeyName)) {
196
            $authorizableClass = $this->config->get('authorizable');
197
            $instance          = new $authorizableClass;
198
199
            if (!$instance instanceof Model) {
200
                throw new ConfigurationException(
201
                    sprintf('An instance of [authorizable] should be \Illuminate\Database\Eloquent\Model. %s given.', get_class($instance))
202
                );
203
            }
204
205
            $this->authKeyName = $instance->getKeyName();
206
            $this->authKeyType = $instance->getKeyType();
207
            $this->authTable   = $instance->getTable();
208
        }
209
210
        return [
211
            'key'   => $this->authKeyName,
212
            'type'  => $this->authKeyType,
213
            'table' => $this->authTable,
214
        ];
215
    }
216
217
    /**
218
     * Return Permission for given scope.
219
     *
220
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
221
     * @param string                                  $scope
222
     * @param bool                                    $checkScope
223
     *
224
     * @return \Mrluke\Privileges\Contracts\Permission|null
225
     *
226
     */
227
    public function getPermission(Permitable $subject, string $scope, bool $checkScope = true)
228
    {
229
        if ($checkScope) {
230
            $this->checkScope($scope);
231
        }
232
233
        if (':*' === substr($scope, -2)) {
234
            $scope = substr($scope, 0, -2);
235
236
            return $subject->permissions->sortbyDesc('level')->first(function ($perm) use ($scope) {
237
                return preg_match("/^(${scope}$|${scope}:)/", $perm['scope']);
238
            });
239
        }
240
241
        return $subject->permissions->where('scope', $scope)->first();
242
    }
243
244
    /**
245
     * Grant or update premission for a Permitable.
246
     *
247
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
248
     * @param string                                  $scope
249
     * @param int                                     $level
250
     *
251
     * @return void
252
     *
253
     * @throws \InvalidArgumentException
254
     */
255
    public function grantPermission(Permitable $subject, string $scope, int $level): void
256
    {
257
        $this->checkScopeAndLevel($scope, $level);
258
259
        $permission = $subject->permissions()->ofScope($scope)->first();
260
261
        $permission ? $permission->update(['level' => $level]) : $subject->permissions()->create([
262
            'scope' => $scope,
263
            'level' => $level,
264
        ]);
265
    }
266
267
    /**
268
     * Determine if there's a given scope Permission for a Permitable.
269
     *
270
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
271
     * @param string                                  $scope
272
     *
273
     * @return bool
274
     *
275
     * @throws \InvalidArgumentException
276
     */
277
    public function hasPermission(Permitable $subject, string $scope): bool
278
    {
279
        $this->checkScope($scope);
280
281
        return $subject->permissions->where('scope', $scope)->exists();
282
    }
283
284
    /**
285
     * Determine if Permitable has given Role assigned.
286
     *
287
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
288
     * @param mixed                                   $role
289
     *
290
     * @return bool
291
     */
292
    public function hasRole(Permitable $subject, $role): bool
293
    {
294
        return $subject->roles->where('id', $role->id)->exists();
295
    }
296
297
    /**
298
     * Regain permission for a Permitable.
299
     *
300
     * @param \Mrluke\Privileges\Contracts\Permitable $subject
301
     * @param string                                  $scope
302
     *
303
     * @return void
304
     *
305
     * @throws \InvalidArgumentException
306
     */
307
    public function regainPermission(Permitable $subject, string $scope): void
308
    {
309
        $this->checkScope($scope);
310
311
        $subject->permissions()->ofScope($scope)->delete();
312
    }
313
314
    /**
315
     * Remove Authorizable's role.
316
     *
317
     * @param \Mrluke\Privileges\Contracts\Authorizable $auth
318
     * @param mixed                                     $role
319
     *
320
     * @return void
321
     */
322
    public function removeRole(Authorizable $auth, $role): void
323
    {
324
        if ($role instanceof Role) {
325
            $auth->roles()->detach($role->id);
326
        } elseif (is_integer($role)) {
327
            $auth->roles()->detach($role);
328
        } else {
329
            throw new InvalidArgumentException(
330
                sprintf('[role] parameter must be type of integer or instance of \Mrluke\Privileges\Contracts\Role. %s type given.', gettype($role))
331
            );
332
        }
333
    }
334
335
    /**
336
     * Return class attributes or setting.
337
     *
338
     * @param string $name
339
     *
340
     * @return mixed
341
     */
342
    public function __get(string $name)
343
    {
344
        if (is_null($this->authKeyName)) {
345
            $this->getAuthorizableMigration();
346
        }
347
348
        if (in_array($name, ['authKeyName', 'authKeyType', 'authTable'])) {
349
            return $this->{$name};
350
        }
351
352
        return $this->config->get($name, null);
353
    }
354
355
    /**
356
     * Check if the scope value.
357
     *
358
     * @param string $scope
359
     *
360
     * @return void
361
     *
362
     * @throws \InvalidArgumentException
363
     */
364
    private function checkScope(string $scope): void
365
    {
366
        $scope = explode(':', $scope);
367
368
        if (!in_array($scope[0], $this->config->get('scopes'))) {
369
            throw new InvalidArgumentException('Given [scope] is not allowed.');
370
        }
371
    }
372
373
    /**
374
     * Check if the scope & level values.
375
     *
376
     * @param string $scope
377
     * @param int    $level
378
     *
379
     * @return void
380
     *
381
     * @throws \InvalidArgumentException
382
     */
383
    private function checkScopeAndLevel(string $scope, int $level): void
384
    {
385
        $this->checkScope($scope);
386
387
        if ($level < 0 || $level > 4) {
388
            throw new InvalidArgumentException('Given [level] must be in range of 0-4.');
389
        }
390
    }
391
}
392