Completed
Push — master ( 93e8e1...4666a2 )
by vistart
03:17
created

BaseOrganization::removeMember()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 1
crap 12
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\traits\SelfBlameableTrait;
16
use rhosocial\base\models\queries\BaseUserQuery;
17
use rhosocial\user\User;
18
use rhosocial\organization\queries\MemberQuery;
19
use rhosocial\organization\queries\OrganizationQuery;
20
use Yii;
21
22
/**
23
 * Organization.
24
 * The organization can open sub-organizations & departments.
25
 * An organization can exist either alone or as a sub-organization of an
26
 * organization but not department.
27
 *
28
 * @method Member createMember(array $config) Create member who belongs to this.
29
 * @property integer $type Whether indicate this instance is an organization or a department.
30
 
31
 * @version 1.0
32
 * @author vistart <[email protected]>
33
 */
34
abstract class BaseOrganization extends User
35
{
36
    use SelfBlameableTrait;
37
38
    const TYPE_ORGANIZATION = 1;
39
    const TYPE_DEPARTMENT = 2;
40
41
    /**
42
     * @var boolean Organization does not need password and corresponding features.
43
     */
44
    public $passwordHashAttribute = false;
45
46
    /**
47
     * @var boolean Organization does not need password and corresponding features.
48
     */
49
    public $passwordResetTokenAttribute = false;
50
51
    /**
52
     * @var boolean Organization does not need password and corresponding features.
53
     */
54
    public $passwordHistoryClass = false;
55
56
    /**
57
     * @var boolean Organization does not need source.
58
     */
59
    public $sourceAttribute = false;
60
61
    /**
62
     * @var boolean Organization does not need auth key.
63
     */
64
    public $authKeyAttribute = false;
65
66
    /**
67
     * @var boolean Organization does not need access token.
68
     */
69
    public $accessTokenAttribute = false;
70
71
    /**
72
     *
73
     * @var boolean Organization does not need login log.
74
     */
75
    public $loginLogClass = false;
76
77
    public $profileClass = Profile::class;
78
79
    public $memberClass = Member::class;
80
    private $noInitMember;
81
    /**
82
     * @return Member
83
     */
84
    protected function getNoInitMember()
85
    {
86
        if (!$this->noInitMember) {
87
            $class = $this->memberClass;
88
            $this->noInitMember = $class::buildNoInitMember();
89
        }
90
        return $this->noInitMember;
91
    }
92
93 8
    public function init()
94
    {
95 8
        if (!is_string($this->queryClass)) {
96
            $this->queryClass = OrganizationQuery::class;
97
        }
98 8
        if (class_exists($this->memberClass)) {
99 8
            $this->addSubsidiaryClass('Member', ['class' => Member::class]);
100
        }
101 8
        if ($this->skipInit) {
102 8
            return;
103
        }
104 8
        parent::init();
105 8
    }
106
107
    /**
108
     * @inheritdoc
109
     */
110
    public function attributeLabels()
111
    {
112
        return [
113
            'guid' => Yii::t('app', 'GUID'),
114
            'id' => Yii::t('app', 'ID'),
115
            'ip' => Yii::t('app', 'IP'),
116
            'ip_type' => Yii::t('app', 'IP Address Type'),
117
            'parent' => Yii::t('app', 'Parent'),
118
            'created_at' => Yii::t('app', 'Create Time'),
119
            'updated_at' => Yii::t('app', 'Update Time'),
120
            'status' => Yii::t('app', 'Status'),
121
            'type' => Yii::t('app', 'Type'),
122
        ];
123
    }
124
125
    /**
126
     * @inheritdoc
127
     */
128 8
    public static function tableName()
129
    {
130 8
        return '{{%organization}}';
131
    }
132
133
    abstract protected function typeAttributeBehavior();
134
135
    /**
136
     * @inheritdoc
137
     */
138 8
    public function behaviors()
139
    {
140 8
        return array_merge(parent::behaviors(), $this->typeAttributeBehavior());
141
    }
142
143
    abstract protected function getTypeRules();
144
145 8
    public function rules()
146
    {
147 8
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
148
    }
149
150
    /**
151
     * Get Member Query.
152
     * @return MemberQuery
153
     */
154
    public function getMembers()
155
    {
156
        return $this->hasMany($this->memberClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->inverseOf('organization');
157
    }
158
159
    /**
160
     * Get organization member users' query.
161
     * @return BaseUserQuery
162
     */
163
    public function getOrganizationMemberUsers()
164
    {
165
        $noInit = $this->getNoInitMember();
166
        $class = $noInit->memberUserClass;
167
        $noInitUser = $class::buildNoInitModel();
168
        return $this->hasMany($class, [$this->guidAttribute => $noInitUser->guidAttribute])->via('members')->inverseOf('atOrganizations');
169
    }
170
 
171
    /**
172
     * Get department member users' query.
173
     * @return BaseUserQuery
174
     */
175
    public function getDepartmentMemberUsers()
176
    {
177
        $noInit = $this->getNoInitMember();
178
        $class = $noInit->memberUserClass;
179
        $noInitUser = $class::buildNoInitModel();
180
        return $this->hasMany($class, [$this->guidAttribute => $noInitUser->guidAttribute])->via('members')->inverseOf('atDepartments');
181
    }
182
183
    /**
184
     * Get member with specified user.
185
     * @param User|string|integer $user
186
     * @return Member Null if `user` is not in this organization.
187
     */
188
    public function getMember($user)
189
    {
190
        if ($user instanceof $this->memberClass) {
191
            return $user;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $user; (rhosocial\user\User) is incompatible with the return type documented by rhosocial\organization\BaseOrganization::getMember of type rhosocial\organization\Member|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
192
        }
193
        return $this->getMembers()->user($user)->one();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getMembers()->user($user)->one(); of type yii\db\ActiveRecord|array|null adds the type array to the return on line 193 which is incompatible with the return type documented by rhosocial\organization\BaseOrganization::getMember of type rhosocial\organization\Member|null.
Loading history...
194
    }
195
196
    /**
197
     * Add member to organization.
198
     * @param Member|User|string|integer $member
199
     * @see createMemberModel
200
     * @see createMemberModelWithUser
201
     * @return boolean
202
     */
203
    public function addMember(&$member)
204
    {
205
        if ($this->getIsNewRecord()) {
206
            return false;
207
        }
208
        $model = null;
209
        if ($member instanceof Member) {
210
            $model = $this->createMemberModel($member);
211
        }
212
        if (($member instanceof User) || is_string($member) || is_int($member)) {
213
            $model = $this->createMemberModelWithUser($member);
214
        }
215
        $member = $model;
216
        return ($member instanceof Member) ? $member->save() : false;
217
    }
218
219
    /**
220
     * Create member model, and set organization with this.
221
     * @param Member $member If this parameter is not new record, it's organization
222
     * will be set with this, and return it. Otherwise, it will extract `User`
223
     * model and create new `Member` model.
224
     * @see createMemberModelWithUser
225
     * @return Member
226
     */
227
    public function createMemberModel($member)
228
    {
229
        if (!$member->getIsNewRecord()) {
230
            $member->setOrganization($this);
231
            return $member;
232
        }
233
        return $this->createMemberModelWithUser($member->memberUser);
234
    }
235
236
    /**
237
     * Create member model with user, and set organization with this.
238
     * @param User|string|integer $user
239
     * @return Member
240
     */
241 7
    public function createMemberModelWithUser($user)
242
    {
243
        $config = [
244 7
            'memberUser' => $user,
245 7
            'organization' => $this,
246 7
            'nickname' => '',
247
        ];
248 7
        if ($user->profile) {
249 7
            $config['nickname'] = $user->profile->nickname;
250
        }
251 7
        return $this->createMember($config);
252
    }
253
254
    /**
255
     * Remove member.
256
     * @param Member|User $member
257
     * @return boolean
258
     */
259
    public function removeMember(&$member)
260
    {
261
        if ($this->getIsNewRecord()) {
262
            return false;
263
        }
264
        $member = $this->getMember($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->getMember($member) on line 264 can also be of type object<rhosocial\organization\Member>; however, rhosocial\organization\B...ganization::getMember() does only seem to accept object<rhosocial\user\User>|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...
265
        return $member && $member->delete() > 0;
266
    }
267
}
268