DbManager   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 415
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 57
lcom 1
cbo 7
dl 0
loc 415
rs 5.04
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A addItem() 0 24 4
A updateItem() 0 31 4
A addRule() 0 21 3
A updateRule() 0 23 3
A getRolesByUser() 0 22 5
A getDirectPermissionsByUser() 0 14 2
A getInheritedPermissionsByUser() 0 26 4
A getAssignment() 0 31 5
A revokeFailedAssignment() 0 15 4
A getAssignments() 0 30 5
A assign() 0 22 3
A revoke() 0 18 4
A revokeAll() 0 14 3
A getUserGuidsByRole() 0 10 2
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 = gmdate('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 = gmdate('Y-m-d H:i:s');
0 ignored issues
show
Documentation Bug introduced by
The property $updatedAt was declared of type integer, but gmdate('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
    protected function addRule($rule)
95
    {
96
        $time = gmdate('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
                '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 = gmdate('Y-m-d H:i:s');
0 ignored issues
show
Documentation Bug introduced by
The property $updatedAt was declared of type integer, but gmdate('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
    public function getRolesByUser($userGuid)
149
    {
150
        if (!isset($userGuid) || $userGuid === '') {
151
            return [];
152
        }
153
        
154
        if ($userGuid instanceof User) {
155
            $userGuid = $userGuid->getGUID();
156
        }
157
158
        $query = (new Query)->select('b.*')
159
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
160
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
161
            ->andWhere(['a.user_guid' => (string) $userGuid])
162
            ->andWhere(['b.type' => Item::TYPE_ROLE]);
163
164
        $roles = [];
165
        foreach ($query->all($this->db) as $row) {
166
            $roles[$row['name']] = $this->populateItem($row);
167
        }
168
        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
    protected function getDirectPermissionsByUser($userGuid)
177
    {
178
        $query = (new Query)->select('b.*')
179
            ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable])
180
            ->where('{{a}}.[[item_name]]={{b}}.[[name]]')
181
            ->andWhere(['a.user_guid' => (string) $userGuid])
182
            ->andWhere(['b.type' => Item::TYPE_PERMISSION]);
183
184
        $permissions = [];
185
        foreach ($query->all($this->db) as $row) {
186
            $permissions[$row['name']] = $this->populateItem($row);
187
        }
188
        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
    protected function getInheritedPermissionsByUser($userGuid)
197
    {
198
        $query = (new Query)->select('item_name')
199
            ->from($this->assignmentTable)
200
            ->where(['user_guid' => (string) $userGuid]);
201
202
        $childrenList = $this->getChildrenList();
203
        $result = [];
204
        foreach ($query->column($this->db) as $roleName) {
205
            $this->getChildrenRecursive($roleName, $childrenList, $result);
206
        }
207
208
        if (empty($result)) {
209
            return [];
210
        }
211
212
        $query = (new Query)->from($this->itemTable)->where([
213
            'type' => Item::TYPE_PERMISSION,
214
            'name' => array_keys($result),
215
        ]);
216
        $permissions = [];
217
        foreach ($query->all($this->db) as $row) {
218
            $permissions[$row['name']] = $this->populateItem($row);
219
        }
220
        return $permissions;
221
    }
222
223
    /**
224
     * @inheritdoc
225
     */
226
    public function getAssignment($roleName, $userGuid)
227
    {
228
        if (empty($userGuid)) {
229
            return null;
230
        }
231
        
232
        if ($userGuid instanceof User) {
233
            $userGuid = $userGuid->getGUID();
234
        }
235
236
        $row = (new Query)->from($this->assignmentTable)
237
            ->where(['user_guid' => (string) $userGuid, 'item_name' => $roleName])
238
            ->one($this->db);
239
240
        if ($row === false) {
241
            return null;
242
        }
243
        
244
        $assignment = new Assignment([
245
            'userGuid' => $row['user_guid'],
246
            'roleName' => $row['item_name'],
247
            'createdAt' => $row['created_at'],
248
            'failedAt' => $row['failed_at'],
249
        ]);
250
        
251
        if ($this->revokeFailedAssignment($assignment)) {
252
            return null;
253
        }
254
255
        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
    protected function revokeFailedAssignment(Assignment $assignment)
265
    {
266
        if ($assignment->failedAt === null) {
267
            return false;
268
        }
269
        if (strtotime($assignment->failedAt) < strtotime(gmdate('Y-m-d H:i:s'))) {
270
            $role = $this->getRole($assignment->roleName);
271
            if (!$role) {
272
                return true;
273
            }
274
            $this->revoke($role->name, $assignment->userGuid);
0 ignored issues
show
Documentation introduced by
$role->name is of type string, but the function expects a object<yii\rbac\Role>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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