Passed
Push — master ( b5549b...886b4c )
by vistart
04:08
created

Member   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 84.56%

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 11
dl 0
loc 320
ccs 115
cts 136
cp 0.8456
rs 8.6206
c 0
b 0
f 0

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 34
    protected function getNoInitMemberUser()
60
    {
61 34
        if (!$this->noInitMemberUser) {
62 34
            $class = $this->memberUserClass;
63 34
            $this->noInitMemberUser = $class::buildNoInitModel();
64
        }
65 34
        return $this->noInitMemberUser;
66
    }
67
68
    /**
69
     * @inheritdoc
70
     */
71 34
    public function init()
72
    {
73 34
        if (!is_string($this->queryClass)) {
74 34
            $this->queryClass = MemberQuery::class;
75
        }
76 34
        if ($this->skipInit) {
77 34
            return;
78
        }
79 34
        parent::init();
80 34
    }
81
82
    public $descriptionAttribute = 'description';
83
84 34
    public function getMemberUserRules()
85
    {
86
        return [
87 34
            [$this->memberAttribute, 'required'],
88 34
            [$this->memberAttribute, 'string', 'max' => 36],
89
        ];
90
    }
91
92 34
    public function getMemberRoleRules()
93
    {
94
        return [
95 34
            ['role', 'string', 'max' => 255],
96
        ];
97
    }
98
99 34
    public function getMemberPositionRules()
100
    {
101
        return [
102 34
            ['position', 'default', 'value' => ''],
103
            ['position', 'string', 'max' => 255],
104
        ];
105
    }
106
107
    /**
108
     * Set member user.
109
     * @param User|string|integer $user
110
     */
111 34
    public function setMemberUser($user)
112
    {
113 34
        $class = $this->memberUserClass;
114 34
        if (is_numeric($user)) {
115
            $user = $class::find()->id($user)->one();
116
        }
117 34
        if ($user instanceof $class) {
118 34
            $user = $user->getGUID();
119
        }
120 34
        $this->{$this->memberAttribute} = $user;
121 34
    }
122
123 34
    public function getMemberUser()
124
    {
125 34
        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 26
    public function getOrganization()
134
    {
135 26
        return $this->getHost();
136
    }
137
138
    /**
139
     * Set Organization.
140
     * @param BaseOrganization $organization
141
     * @return boolean
142
     */
143 34
    public function setOrganization($organization)
144
    {
145 34
        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
     */
156 34
    public function assignRole($role)
157
    {
158 34
        $user = $this->memberUser;
159 34
        if (!$user) {
160
            throw new InvalidValueException('Invalid User');
161
        }
162 34
        if ($role instanceof Item) {
163 7
            $role = $role->name;
164
        }
165 34
        $assignment = Yii::$app->authManager->getAssignment($role, $user);
166 34
        if (!$assignment) {
167 34
            $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
        }
169 34
        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
     * @throws IntegrityException
177
     */
178 7
    public function assignAdministrator()
179
    {
180 7
        $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
        /* @var $host Organization */
182 7
        $role = null;
183 7
        if ($host->type == Organization::TYPE_ORGANIZATION) {
184 7
            $role = new OrganizationAdmin();
185 1
        } elseif ($host->type == Organization::TYPE_DEPARTMENT) {
186 1
            $role = new DepartmentAdmin();
187
        }
188 7
        $transaction = Yii::$app->db->beginTransaction();
189
        try {
190 7
            $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 7
            if (!$this->save()) {
192
                throw new IntegrityException("Failed to assign administrator.");
193
            }
194 7
            $transaction->commit();
195
        } catch (\Exception $ex) {
196
            $transaction->rollBack();
197
            Yii::error($ex->getMessage(), __METHOD__);
198
            throw $ex;
199
        }
200 7
        return true;
201
    }
202
203
    /**
204
     * Set role.
205
     * @param Role $role
206
     * @return boolean
207
     */
208 34
    public function setRole($role = null)
209
    {
210 34
        if (empty($role)) {
211 15
            $role = '';
212
        }
213 34
        if ($role instanceof Item) {
214
            $role = $role->name;
215
        }
216 34
        $this->role = $role;
217 34
        return true;
218
    }
219
220
    /**
221
     * Revoke role.
222
     * @param Role $role
223
     */
224 15
    public function revokeRole($role)
225
    {
226 15
        $user = $this->memberUser;
227 15
        if (!$user) {
228
            throw new InvalidValueException('Invalid User');
229
        }
230 15
        if ($role instanceof Item) {
231 1
            $role = $role->name;
232
        }
233 15
        $transaction = Yii::$app->db->beginTransaction();
234
        try {
235 15
            $assignment = Yii::$app->authManager->getAssignment($role, $user);
236 15
            if ($assignment) {
237 15
                $count = (int)($user->getOfMembers()->role($role)->count());
238 15
                if ($count == 1) {
239 13
                    Yii::$app->authManager->revoke($role, $user);
240
                }
241
            }
242 15
            $this->setRole();
243 15
            if (!$this->save()) {
244
                throw new IntegrityException('Save failed.');
245
            }
246 15
            $transaction->commit();
247
        } catch (\Exception $ex) {
248
            $transaction->rollBack();
249
            Yii::error($ex->getMessage(), __METHOD__);
250
            throw $ex;
251
        }
252 15
        return true;
253
    }
254
255
    /**
256
     * Revoke administrator.
257
     * @return boolean
258
     * @throws IntegrityException
259
     */
260 1
    public function revokeAdministrator()
261
    {
262 1
        $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
        /* @var $host Organization */
264 1
        $role = null;
265 1
        if ($host->type == Organization::TYPE_ORGANIZATION) {
266 1
            $role = new OrganizationAdmin();
267
        } elseif ($host->type == Organization::TYPE_DEPARTMENT) {
268
            $role = new DepartmentAdmin();
269
        }
270 1
        $transaction = Yii::$app->db->beginTransaction();
271
        try {
272 1
            $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 1
            if (!$this->save()) {
274
                throw new IntegrityException("Failed to revoke administrator.");
275
            }
276 1
            $transaction->commit();
277
        } catch (\Exception $ex) {
278
            $transaction->rollBack();
279
            Yii::error($ex->getMessage(), __METHOD__);
280
            throw $ex;
281
        }
282 1
        return true;
283
    }
284
285 34
    public function rules()
286
    {
287 34
        return array_merge($this->getMemberUserRules(), $this->getMemberRoleRules(), $this->getMemberPositionRules(), parent::rules());
288
    }
289
290
    /**
291
     * @inheritdoc
292
     */
293 34
    public static function tableName()
294
    {
295 34
        return '{{%organization_member}}';
296
    }
297
298
    /**
299
     * @inheritdoc
300
     */
301 1
    public function attributeLabels()
302
    {
303
        return [
304 1
            'guid' => Yii::t('user', 'GUID'),
305 1
            'id' => Yii::t('user', 'ID'),
306 1
            'organization_guid' => Yii::t('organization', 'Organization GUID'),
307 1
            'user_guid' => Yii::t('organization', 'User GUID'),
308 1
            'nickname' => Yii::t('user', 'Nickname'),
309 1
            'role' => Yii::t('organization', 'Role'),
310 1
            'position' => Yii::t('organization', 'Member Position'),
311 1
            'description' => Yii::t('organization', 'Description'),
312 1
            'ip' => Yii::t('user', 'IP Address'),
313 1
            'ip_type' => Yii::t('user', 'IP Address Type'),
314 1
            'created_at' => Yii::t('user', 'Creation Time'),
315 1
            'updated_at' => Yii::t('user', 'Last Updated Time'),
316
        ];
317
    }
318
319 6
    public function isDepartmentAdministrator()
320
    {
321 6
        return $this->role == (new DepartmentAdmin)->name;
322
    }
323
    
324 14
    public function isDepartmentCreator()
325
    {
326 14
        return $this->role == (new DepartmentCreator)->name;
327
    }
328
329 6
    public function isOrganizationAdministrator()
330
    {
331 6
        return $this->role == (new OrganizationAdmin)->name;
332
    }
333
    
334 14
    public function isOrganizationCreator()
335
    {
336 14
        return $this->role == (new OrganizationCreator)->name;
337
    }
338
339
    /**
340
     * Check whether current member is administrator.
341
     * @return boolean
342
     */
343 6
    public function isAdministrator()
344
    {
345 6
        return $this->isDepartmentAdministrator() || $this->isOrganizationAdministrator();
346
    }
347
348
    /**
349
     * Check whether current member is creator.
350
     * @return boolean
351
     */
352 14
    public function isCreator()
353
    {
354 14
        return $this->isDepartmentCreator() || $this->isOrganizationCreator();
355
    }
356
357
    /**
358
     * We think it a `member` if `role` property is empty.
359
     * @return boolean
360
     */
361 2
    public function isOnlyMember()
362
    {
363 2
        return empty($this->role);
364
    }
365
}
366