Test Failed
Push — master ( a11c58...e4bbdf )
by vistart
03:50
created

DbManager   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 408
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 62.81%

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 55
c 2
b 0
f 1
lcom 1
cbo 8
dl 0
loc 408
ccs 152
cts 242
cp 0.6281
rs 6.8

16 Methods

Rating   Name   Duplication   Size   Complexity  
A revokeFailedAssignment() 0 15 4
A getUserGuidsByRole() 0 10 2
B addItem() 0 24 4
B updateItem() 0 31 4
A addRule() 0 21 3
A updateRule() 0 23 3
B getRolesByUser() 0 22 5
A getDirectPermissionsByUser() 0 14 2
B getInheritedPermissionsByUser() 0 26 4
B getAssignment() 0 31 5
B getAssignments() 0 30 5
A assign() 0 19 2
A revoke() 0 14 3
A revokeAll() 0 14 3
A populateItem() 0 19 4
A getChildren() 0 14 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/**
4
 *  _   __ __ _____ _____ ___  ____  _____
5
 * | | / // // ___//_  _//   ||  __||_   _|
6
 * | |/ // /(__  )  / / / /| || |     | |
7
 * |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link https://vistart.me/
9
 * @copyright Copyright (c) 2016 - 2017 vistart
10
 * @license https://vistart.me/license/
11
 */
12
13
namespace rhosocial\user\rbac;
14
15
use rhosocial\user\User;
16
use yii\db\Expression;
17
use yii\db\Query;
18
19
/**
20
 * This DbManager replaces the UserID of original DbManager with the UserGUID.
21
 *
22
 * @see User
23
 * @version 1.0
24
 * @author vistart <[email protected]>
25
 */
26
class DbManager extends \yii\rbac\DbManager
27
{
28
    /**
29
     * @inheritdoc
30
     */
31
    protected function addItem($item)
32
    {
33
        $time = date('Y-m-d H:i:s');
34
        if ($item->createdAt === null) {
35
            $item->createdAt = $time;
0 ignored issues
show
Documentation Bug introduced by
The property $createdAt was declared of type integer, but $time is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
36
        }
37
        if ($item->updatedAt === null) {
38
            $item->updatedAt = $time;
0 ignored issues
show
Documentation Bug introduced by
The property $updatedAt was declared of type integer, but $time is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
39
        }
40
        $this->db->createCommand()
41
            ->insert($this->itemTable, [
42
                'name' => $item->name,
43
                'type' => $item->type,
44
                'description' => $item->description,
45
                'rule_name' => $item->ruleName,
46
                'data' => $item->data === null ? null : serialize($item->data),
47
                'created_at' => $item->createdAt,
48
                'updated_at' => $item->updatedAt,
49
            ])->execute();
50
51
        $this->invalidateCache();
52
53
        return true;
54
    }
55
    
56
    /**
57
     * @inheritdoc
58
     */
59
    protected function updateItem($name, $item)
60
    {
61
        if ($item->name !== $name && !$this->supportsCascadeUpdate()) {
62
            $this->db->createCommand()
63
                ->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name])
64
                ->execute();
65
            $this->db->createCommand()
66
                ->update($this->itemChildTable, ['child' => $item->name], ['child' => $name])
67
                ->execute();
68
            $this->db->createCommand()
69
                ->update($this->assignmentTable, ['item_name' => $item->name], ['item_name' => $name])
70
                ->execute();
71
        }
72
73
        $item->updatedAt = date('Y-m-d H:i:s');
0 ignored issues
show
Documentation Bug introduced by
The property $updatedAt was declared of type integer, but date('Y-m-d H:i:s') is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
74
75
        $this->db->createCommand()
76
            ->update($this->itemTable, [
77
                'name' => $item->name,
78
                'description' => $item->description,
79
                'rule_name' => $item->ruleName,
80
                'data' => $item->data === null ? null : serialize($item->data),
81
                'updated_at' => $item->updatedAt,
82
            ], [
83
                'name' => $name,
84
            ])->execute();
85
86
        $this->invalidateCache();
87
88
        return true;
89
    }
90
91
    /**
92
     * @inheritdoc
93
     */
94 12
    protected function addRule($rule)
95
    {
96
        $time = date('Y-m-d H:i:s');
97
        if ($rule->createdAt === null) {
98
            $rule->createdAt = $time;
0 ignored issues
show
Documentation Bug introduced by
The property $createdAt was declared of type integer, but $time is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
99
        }
100
        if ($rule->updatedAt === null) {
101
            $rule->updatedAt = $time;
0 ignored issues
show
Documentation Bug introduced by
The property $updatedAt was declared of type integer, but $time is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
102
        }
103
        $this->db->createCommand()
104
            ->insert($this->ruleTable, [
105
                'name' => $rule->name,
106
                'data' => serialize($rule),
107
                'created_at' => $rule->createdAt,
108 12
                'updated_at' => $rule->updatedAt,
109
            ])->execute();
110
111
        $this->invalidateCache();
112
113
        return true;
114
    }
115
    
116
    /**
117
     * @inheritdoc
118
     */
119
    protected function updateRule($name, $rule)
120
    {
121
        if ($rule->name !== $name && !$this->supportsCascadeUpdate()) {
122
            $this->db->createCommand()
123
                ->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name])
124
                ->execute();
125
        }
126
127
        $rule->updatedAt = date('Y-m-d H:i:s');
0 ignored issues
show
Documentation Bug introduced by
The property $updatedAt was declared of type integer, but date('Y-m-d H:i:s') is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
128
129
        $this->db->createCommand()
130
            ->update($this->ruleTable, [
131
                'name' => $rule->name,
132
                'data' => serialize($rule),
133
                'updated_at' => $rule->updatedAt,
134
            ], [
135
                'name' => $name,
136
            ])->execute();
137
138
        $this->invalidateCache();
139
140
        return true;
141
    }
142
    
143
    /**
144
     * Get roles by user.
145
     * @param string|User $userGuid
146
     * @return array
147
     */
148 1
    public function getRolesByUser($userGuid)
149
    {
150 1
        if (!isset($userGuid) || $userGuid === '') {
151 1
            return [];
152
        }
153
        
154 1
        if ($userGuid instanceof User) {
155 1
            $userGuid = $userGuid->getGUID();
156 1
        }
157
158 1
        $query = (new Query)->select('b.*')
159 1
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
160 1
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
161 1
            ->andWhere(['a.user_guid' => (string) $userGuid])
162 1
            ->andWhere(['b.type' => Item::TYPE_ROLE]);
163
164 1
        $roles = [];
165 1
        foreach ($query->all($this->db) as $row) {
166 1
            $roles[$row['name']] = $this->populateItem($row);
167 1
        }
168 1
        return $roles;
169
    }
170
171
    /**
172
     * Returns all permissions that are directly assigned to user.
173
     * @param string|User $userGuid the user GUID (see [[\rhosocial\user\User::guid]])
174
     * @return Permission[] all direct permissions that the user has. The array is indexed by the permission names.
175
     */
176 3
    protected function getDirectPermissionsByUser($userGuid)
177
    {
178 3
        $query = (new Query)->select('b.*')
179 3
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
180 3
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
181 3
            ->andWhere(['a.user_guid' => (string) $userGuid])
182 3
            ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
183
184 3
        $permissions = [];
185 3
        foreach ($query->all($this->db) as $row) {
186
            $permissions[$row['name']] = $this->populateItem($row);
187 3
        }
188 3
        return $permissions;
189
    }
190
191
    /**
192
     * Returns all permissions that the user inherits from the roles assigned to him.
193
     * @param string|User $userGuid the user GUID (see [[\rhosocial\user\User::guid]])
194
     * @return Permission[] all inherited permissions that the user has. The array is indexed by the permission names.
195
     */
196 3
    protected function getInheritedPermissionsByUser($userGuid)
197
    {
198 3
        $query = (new Query)->select('item_name')
199 3
            ->from($this->assignmentTable)
200 3
            ->where(['user_guid' => (string) $userGuid]);
201
202 3
        $childrenList = $this->getChildrenList();
203 3
        $result = [];
204 3
        foreach ($query->column($this->db) as $roleName) {
205 1
            $this->getChildrenRecursive($roleName, $childrenList, $result);
206 3
        }
207
208 3
        if (empty($result)) {
209 2
            return [];
210
        }
211
212 1
        $query = (new Query)->from($this->itemTable)->where([
213 1
            'type' => Item::TYPE_PERMISSION,
214 1
            'name' => array_keys($result),
215 1
        ]);
216 1
        $permissions = [];
217 1
        foreach ($query->all($this->db) as $row) {
218 1
            $permissions[$row['name']] = $this->populateItem($row);
219 1
        }
220 1
        return $permissions;
221
    }
222
223
    /**
224
     * @inheritdoc
225
     */
226 10
    public function getAssignment($roleName, $userGuid)
227
    {
228 10
        if (empty($userGuid)) {
229 1
            return null;
230
        }
231
        
232 10
        if ($userGuid instanceof User) {
233 9
            $userGuid = $userGuid->getGUID();
234 9
        }
235
236 10
        $row = (new Query)->from($this->assignmentTable)
237 10
            ->where(['user_guid' => (string) $userGuid, 'item_name' => $roleName])
238 10
            ->one($this->db);
239
240 10
        if ($row === false) {
241 1
            return null;
242
        }
243
        
244 9
        $assignment = new Assignment([
245 9
            'userGuid' => $row['user_guid'],
246 9
            'roleName' => $row['item_name'],
247 9
            'createdAt' => $row['created_at'],
248 9
            'failedAt' => $row['failed_at'],
249 9
        ]);
250
        
251 9
        if ($this->revokeFailedAssignment($assignment)) {
252 2
            return null;
253
        }
254
255 7
        return $assignment;
256
    }
257
    
258
    /**
259
     * Revoke failed assignment.
260
     * If assignment's `failedAt` attribute is `null`, false will be given directly.
261
     * @param Assignment $assignment
262
     * @return boolean
263
     */
264 9
    protected function revokeFailedAssignment(Assignment $assignment)
265
    {
266 9
        if ($assignment->failedAt === null) {
267 7
            return false;
268
        }
269 2
        if (strtotime($assignment->failedAt) < strtotime(date('Y-m-d H:i:s'))) {
270 2
            $role = $this->getRole($assignment->roleName);
271 2
            if (!$role) {
272 1
                return true;
273
            }
274 1
            $this->revoke($role, $assignment->userGuid);
0 ignored issues
show
Compatibility introduced by
$role of type object<yii\rbac\Item> is not a sub-type of object<yii\rbac\Role>. It seems like you assume a child class of the class yii\rbac\Item to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
Bug introduced by
It seems like $assignment->userGuid can also be of type object<rhosocial\user\User>; however, rhosocial\user\rbac\DbManager::revoke() does only seem to accept string|integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
275 1
            return true;
276
        }
277
        return false;
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283 7
    public function getAssignments($userGuid)
284
    {
285 7
        if (empty($userGuid)) {
286 1
            return [];
287
        }
288
        
289 7
        if ($userGuid instanceof User) {
290 1
            $userGuid = $userGuid->getGUID();
291 1
        }
292
293 7
        $query = (new Query)
294 7
            ->from($this->assignmentTable)
295 7
            ->where(['user_guid' => (string) $userGuid]);
296
297 7
        $assignments = [];
298 7
        foreach ($query->all($this->db) as $row) {
299 6
            $assignment = new Assignment([
300 6
                'userGuid' => $row['user_guid'],
301 6
                'roleName' => $row['item_name'],
302 6
                'createdAt' => $row['created_at'],
303 6
                'failedAt' => $row['failed_at'],
304 6
            ]);
305 6
            if ($this->revokeFailedAssignment($assignment)) {
306
                continue;
307
            }
308 6
            $assignments[$row['item_name']] = $assignment;
309 7
        }
310
311 7
        return $assignments;
312
    }
313
314
    /**
315
     * @inheritdoc
316
     */
317 14
    public function assign($role, $userGuid, $failedAt = null)
318
    {
319 14
        $assignment = new Assignment([
320 14
            'userGuid' => $userGuid,
321 14
            'roleName' => $role->name,
322 14
            'createdAt' => date('Y-m-d H:i:s'),
323 14
            'failedAt' => empty($failedAt) ? null : $failedAt,
324 14
        ]);
325
326 14
        $this->db->createCommand()
327 14
            ->insert($this->assignmentTable, [
328 14
                'user_guid' => $assignment->userGuid,
329 14
                'item_name' => $assignment->roleName,
330 14
                'created_at' => $assignment->createdAt,
331 14
                'failed_at' => $assignment->failedAt,
332 14
            ])->execute();
333
334 14
        return $assignment;
335
    }
336
337
    /**
338
     * @inheritdoc
339
     */
340 2
    public function revoke($role, $userGuid)
341
    {
342 2
        if (empty($userGuid)) {
343 1
            return false;
344
        }
345
        
346 2
        if ($userGuid instanceof User) {
347 1
            $userGuid = $userGuid->getGUID();
348 1
        }
349
350 2
        return $this->db->createCommand()
351 2
            ->delete($this->assignmentTable, ['user_guid' => (string) $userGuid, 'item_name' => $role->name])
352 2
            ->execute() > 0;
353
    }
354
355
    /**
356
     * @inheritdoc
357
     */
358 1
    public function revokeAll($userGuid)
359
    {
360 1
        if (empty($userGuid)) {
361 1
            return false;
362
        }
363
        
364 1
        if ($userGuid instanceof User) {
365 1
            $userGuid = $userGuid->getGUID();
366 1
        }
367
368 1
        return $this->db->createCommand()
369 1
            ->delete($this->assignmentTable, ['user_guid' => (string) $userGuid])
370 1
            ->execute() > 0;
371
    }
372
373
    /**
374
     * Returns all role assignment information for the specified role.
375
     * @param string $roleName
376
     * @return Assignment[] the assignments. An empty array will be
377
     * returned if role is not assigned to any user.
378
     * @since 2.0.7
379
     */
380 1
    public function getUserGuidsByRole($roleName)
381
    {
382 1
        if (empty($roleName)) {
383 1
            return [];
384
        }
385
386 1
        return (new Query)->select('[[user_guid]]')
387 1
            ->from($this->assignmentTable)
388 1
            ->where(['item_name' => $roleName])->column($this->db);
389
    }
390
391
    /**
392
     * Populates an auth item with the data fetched from database
393
     * @param array $row the data from the auth item table
394
     * @return Item the populated auth item instance (either Role or Permission)
395
     */
396 10
    protected function populateItem($row)
397
    {
398 10
        $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::class : Role::class;
399
400 10
        if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) {
401 10
            $data = null;
402 10
        }
403
404 10
        return new $class([
405 10
            'name' => $row['name'],
406 10
            'type' => $row['type'],
407 10
            'description' => $row['description'],
408 10
            'ruleName' => $row['rule_name'],
409 10
            'data' => $data,
410 10
            'color' => $row['color'],
411 10
            'createdAt' => $row['created_at'],
412 10
            'updatedAt' => $row['updated_at'],
413 10
        ]);
414
    }
415
416
    /**
417
     * @inheritdoc
418
     */
419
    public function getChildren($name)
420
    {
421
        $query = (new Query)
422
            ->select(['name', 'type', 'description', 'rule_name', 'data', 'color', 'created_at', 'updated_at'])
423
            ->from([$this->itemTable, $this->itemChildTable])
424
            ->where(['parent' => $name, 'name' => new Expression('[[child]]')]);
425
426
        $children = [];
427
        foreach ($query->all($this->db) as $row) {
428
            $children[$row['name']] = $this->populateItem($row);
429
        }
430
431
        return $children;
432
    }
433
}
434