Test Failed
Push — master ( b8a5a5...3d0ef5 )
by vistart
04:28
created

Member   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 89.22%

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 11
dl 0
loc 320
rs 8.6206
c 0
b 0
f 0
ccs 91
cts 102
cp 0.8922

24 Methods

Rating   Name   Duplication   Size   Complexity  
A getNoInitMemberUser() 0 8 2
A init() 0 10 3
A getMemberUserRules() 0 7 1
A getMemberRoleRules() 0 6 1
A getMemberPositionRules() 0 7 1
A setMemberUser() 0 11 3
A getMemberUser() 0 4 1
A getOrganization() 0 4 1
A setOrganization() 0 4 1
A assignRole() 0 15 4
B assignAdministrator() 0 24 5
A setRole() 0 11 3
C revokeRole() 0 30 7
B revokeAdministrator() 0 24 5
A rules() 0 4 1
A tableName() 0 4 1
A attributeLabels() 0 17 1
A isDepartmentAdministrator() 0 4 1
A isDepartmentCreator() 0 4 1
A isOrganizationAdministrator() 0 4 1
A isOrganizationCreator() 0 4 1
A isAdministrator() 0 4 2
A isCreator() 0 4 2
A isOnlyMember() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Member 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 Member, 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\organization;
14
15
use rhosocial\base\models\models\BaseBlameableModel;
16
use rhosocial\user\rbac\Item;
17
use rhosocial\user\rbac\Role;
18
use rhosocial\user\User;
19
use rhosocial\organization\rbac\roles\DepartmentAdmin;
20
use rhosocial\organization\rbac\roles\DepartmentCreator;
21
use rhosocial\organization\rbac\roles\OrganizationAdmin;
22
use rhosocial\organization\rbac\roles\OrganizationCreator;
23
use rhosocial\organization\queries\OrganizationQuery;
24
use rhosocial\organization\queries\MemberQuery;
25
use Yii;
26
use yii\base\InvalidValueException;
27
use yii\db\IntegrityException;
28
29
/**
30
 * Organization member.
31
 *
32
 * @property string $organization_guid
33
 * @property string $user_guid store guid of user who represents this member.
34
 * @property string $nickname
35
 * @property string $role
36
 * @property string $position
37
 * @property string $description
38
 * 
39
 * @property string $department_guid
40
 * @property string $member_guid
41
 * @property User $memberUser
42
 *
43
 * @version 1.0
44
 * @author vistart <[email protected]>
45
 */
46
class Member extends BaseBlameableModel
47
{
48
    public $createdByAttribute = 'organization_guid';
49
    public $updatedByAttribute = false;
50
    public $hostClass = Organization::class;
51
52
    public $memberAttribute = 'user_guid';
53
    public $memberUserClass = User::class;
54
    public $contentAttribute = 'nickname';
55
    private $noInitMemberUser;
56
    /**
57
     * @return User
58
     */
59 32
    protected function getNoInitMemberUser()
60
    {
61 32
        if (!$this->noInitMemberUser) {
62 32
            $class = $this->memberUserClass;
63 32
            $this->noInitMemberUser = $class::buildNoInitModel();
64
        }
65 32
        return $this->noInitMemberUser;
66
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71 32
    public function init()
72
    {
73 32
        if (!is_string($this->queryClass)) {
74 32
            $this->queryClass = MemberQuery::class;
75
        }
76 32
        if ($this->skipInit) {
77 32
            return;
78
        }
79 32
        parent::init();
80 32
    }
81
82
    public $descriptionAttribute = 'description';
83
84 32
    public function getMemberUserRules()
85
    {
86
        return [
87 32
            [$this->memberAttribute, 'required'],
88 32
            [$this->memberAttribute, 'string', 'max' => 36],
89
        ];
90
    }
91
92 32
    public function getMemberRoleRules()
93
    {
94
        return [
95 32
            ['role', 'string', 'max' => 255],
96
        ];
97
    }
98
99 32
    public function getMemberPositionRules()
100
    {
101
        return [
102 32
            ['position', 'default', 'value' => ''],
103
            ['position', 'string', 'max' => 255],
104
        ];
105
    }
106
107
    /**
108
     * Set member user.
109
     * @param User|string|integer $user
110
     */
111 32
    public function setMemberUser($user)
112
    {
113 32
        $class = $this->memberUserClass;
114 32
        if (is_numeric($user)) {
115
            $user = $class::find()->id($user)->one();
116
        }
117 32
        if ($user instanceof $class) {
118 32
            $user = $user->getGUID();
119
        }
120 32
        $this->{$this->memberAttribute} = $user;
121 32
    }
122
123 32
    public function getMemberUser()
124
    {
125 32
        return $this->hasOne($this->memberUserClass, [$this->getNoInitMemberUser()->guidAttribute => $this->memberAttribute]);
126
    }
127
128
    /**
129
     * Get Organization Query.
130
     * Alias of `getHost` method.
131
     * @return OrganizationQuery
132
     */
133 24
    public function getOrganization()
134
    {
135 24
        return $this->getHost();
136
    }
137
138
    /**
139
     * Set Organization.
140
     * @param BaseOrganization $organization
141
     * @return boolean
142
     */
143 32
    public function setOrganization($organization)
144
    {
145 32
        return $this->setHost($organization);
146
    }
147
148
    /**
149
     * Assign role.
150
     * The setting role operation will not take effect immediately. You should
151
     * wrap this method and the subsequent save operations together into a
152
     * transaction, in order to ensure data cosistency.
153
     * @param Role $role
154
     * @return boolean
155 32
     */
156
    public function assignRole($role)
157 32
    {
158 32
        $user = $this->memberUser;
159
        if (!$user) {
160
            throw new InvalidValueException('Invalid User');
161 32
        }
162
        if ($role instanceof Item) {
163
            $role = $role->name;
164 32
        }
165 32
        $assignment = Yii::$app->authManager->getAssignment($role, $user);
166 32
        if (!$assignment) {
167
            $assignment = Yii::$app->authManager->assign($role, $user->getGUID());
0 ignored issues
show
Unused Code introduced by
$assignment is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
168 32
        }
169
        return $this->setRole($role);
0 ignored issues
show
Documentation introduced by
$role is of type string, but the function expects a object<rhosocial\user\rbac\Role>|null.

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...
170
    }
171
172
    /**
173
     * Assign administrator.
174
     * @return boolean
175
     * @throws \Exception
176 32
     * @throws IntegrityException
177
     */
178 32
    public function assignAdministrator()
179 18
    {
180
        $host = $this->organization;
0 ignored issues
show
Bug introduced by
The property organization does not seem to exist. Did you mean organization_guid?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
181 32
        /* @var $host Organization */
182
        $role = null;
183
        if ($host->type == Organization::TYPE_ORGANIZATION) {
184 32
            $role = new OrganizationAdmin();
185 32
        } elseif ($host->type == Organization::TYPE_DEPARTMENT) {
186
            $role = new DepartmentAdmin();
187
        }
188
        $transaction = Yii::$app->db->beginTransaction();
189
        try {
190
            $this->assignRole($role);
0 ignored issues
show
Bug introduced by
It seems like $role defined by null on line 182 can also be of type null; however, rhosocial\organization\Member::assignRole() does only seem to accept object<rhosocial\user\rbac\Role>, 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...
191 18
            if (!$this->save()) {
192
                throw new IntegrityException("Failed to assign administrator.");
193 18
            }
194 18
            $transaction->commit();
195
        } catch (\Exception $ex) {
196
            $transaction->rollBack();
197 18
            Yii::error($ex->getMessage(), __METHOD__);
198
            throw $ex;
199
        }
200 18
        return true;
201
    }
202 18
203 18
    /**
204 18
     * Set role.
205 18
     * @param Role $role
206 16
     * @return boolean
207
     */
208
    public function setRole($role = null)
209 18
    {
210 18
        if (empty($role)) {
211
            $role = '';
212
        }
213 18
        if ($role instanceof Item) {
214
            $role = $role->name;
215
        }
216
        $this->role = $role;
217
        return true;
218
    }
219 18
220
    /**
221
     * Revoke role.
222 32
     * @param Role $role
223
     */
224 32
    public function revokeRole($role)
225
    {
226
        $user = $this->memberUser;
227
        if (!$user) {
228
            throw new InvalidValueException('Invalid User');
229
        }
230 32
        if ($role instanceof Item) {
231
            $role = $role->name;
232 32
        }
233
        $transaction = Yii::$app->db->beginTransaction();
234
        try {
235
            $assignment = Yii::$app->authManager->getAssignment($role, $user);
236
            if ($assignment) {
237
                $count = (int)($user->getOfMembers()->role($role)->count());
238 1
                if ($count == 1) {
239
                    Yii::$app->authManager->revoke($role, $user);
240
                }
241 1
            }
242 1
            $this->setRole();
243 1
            if (!$this->save()) {
244 1
                throw new IntegrityException('Save failed.');
245 1
            }
246 1
            $transaction->commit();
247 1
        } catch (\Exception $ex) {
248 1
            $transaction->rollBack();
249 1
            Yii::error($ex->getMessage(), __METHOD__);
250 1
            throw $ex;
251 1
        }
252 1
        return true;
253
    }
254
255
    /**
256 4
     * Revoke administrator.
257
     * @return boolean
258 4
     * @throws IntegrityException
259
     */
260
    public function revokeAdministrator()
261 13
    {
262
        $host = $this->organization;
0 ignored issues
show
Bug introduced by
The property organization does not seem to exist. Did you mean organization_guid?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
263 13
        /* @var $host Organization */
264
        $role = null;
265
        if ($host->type == Organization::TYPE_ORGANIZATION) {
266 4
            $role = new OrganizationAdmin();
267
        } elseif ($host->type == Organization::TYPE_DEPARTMENT) {
268 4
            $role = new DepartmentAdmin();
269
        }
270
        $transaction = Yii::$app->db->beginTransaction();
271 13
        try {
272
            $this->revokeRole($role);
0 ignored issues
show
Bug introduced by
It seems like $role defined by null on line 264 can also be of type null; however, rhosocial\organization\Member::revokeRole() does only seem to accept object<rhosocial\user\rbac\Role>, 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...
273 13
            if (!$this->save()) {
274
                throw new IntegrityException("Failed to revoke administrator.");
275
            }
276
            $transaction->commit();
277
        } catch (\Exception $ex) {
278
            $transaction->rollBack();
279
            Yii::error($ex->getMessage(), __METHOD__);
280 4
            throw $ex;
281
        }
282 4
        return true;
283
    }
284
285
    public function rules()
286
    {
287
        return array_merge($this->getMemberUserRules(), $this->getMemberRoleRules(), $this->getMemberPositionRules(), parent::rules());
288
    }
289 13
290
    /**
291 13
     * @inheritdoc
292
     */
293
    public static function tableName()
294
    {
295
        return '{{%organization_member}}';
296
    }
297
298 2
    /**
299
     * @inheritdoc
300 2
     */
301
    public function attributeLabels()
302
    {
303
        return [
304
            'guid' => Yii::t('app', 'GUID'),
305
            'id' => Yii::t('app', 'ID'),
306
            'organization_guid' => Yii::t('app', 'Organization GUID'),
307
            'user_guid' => Yii::t('app', 'User GUID'),
308
            'nickname' => Yii::t('app', 'Nickname'),
309
            'role' => Yii::t('app', 'Role'),
310
            'position' => Yii::t('app', 'Position'),
311
            'description' => Yii::t('app', 'Description'),
312
            'ip' => Yii::t('app', 'IP'),
313
            'ip_type' => Yii::t('app', 'IP Address Type'),
314
            'created_at' => Yii::t('app', 'Create Time'),
315
            'updated_at' => Yii::t('app', 'Update Time'),
316
        ];
317
    }
318
319
    public function isDepartmentAdministrator()
320
    {
321
        return $this->role == (new DepartmentAdmin)->name;
322
    }
323
    
324
    public function isDepartmentCreator()
325
    {
326
        return $this->role == (new DepartmentCreator)->name;
327
    }
328
329
    public function isOrganizationAdministrator()
330
    {
331
        return $this->role == (new OrganizationAdmin)->name;
332
    }
333
    
334
    public function isOrganizationCreator()
335
    {
336
        return $this->role == (new OrganizationCreator)->name;
337
    }
338
339
    /**
340
     * Check whether current member is administrator.
341
     * @return boolean
342
     */
343
    public function isAdministrator()
344
    {
345
        return $this->isDepartmentAdministrator() || $this->isOrganizationAdministrator();
346
    }
347
348
    /**
349
     * Check whether current member is creator.
350
     * @return boolean
351
     */
352
    public function isCreator()
353
    {
354
        return $this->isDepartmentCreator() || $this->isOrganizationCreator();
355
    }
356
357
    /**
358
     * We think it a `member` if `role` property is empty.
359
     * @return boolean
360
     */
361
    public function isOnlyMember()
362
    {
363
        return empty($this->role);
364
    }
365
}
366