Test Failed
Push — master ( 13dd6b...7f7a95 )
by vistart
03:43
created

BaseOrganization::createMemberModelWithUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 0
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
crap 6
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 1
93
    public function init()
94 1
    {
95 1
        if (!is_string($this->queryClass)) {
96
            $this->queryClass = OrganizationQuery::class;
97 1
        }
98 1
        if (class_exists($this->memberClass)) {
99
            $this->addSubsidiaryClass('Member', ['class' => Member::class]);
100 1
        }
101 1
        if ($this->skipInit) {
102
            return;
103 1
        }
104 1
        parent::init();
105
    }
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 1
     */
128
    public static function tableName()
129 1
    {
130
        return '{{%organization}}';
131
    }
132
133
    abstract protected function typeAttributeBehavior();
134
135
    /**
136
     * @inheritdoc
137 1
     */
138
    public function behaviors()
139 1
    {
140
        return array_merge(parent::behaviors(), $this->typeAttributeBehavior());
141
    }
142
143
    abstract protected function getTypeRules();
144 1
145
    public function rules()
146 1
    {
147
        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 member users' query.
161
     * @return BaseUserQuery
162
     */
163
    public function getMemberUsers()
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 member with specified user.
173
     * @param User|string|integer $user
174
     * @return Member Null if `user` is not in this organization.
175
     */
176
    public function getMember($user)
177
    {
178
        if ($user instanceof $this->memberClass) {
179
            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...
180
        }
181
        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 181 which is incompatible with the return type documented by rhosocial\organization\BaseOrganization::getMember of type rhosocial\organization\Member|null.
Loading history...
182
    }
183
184
    /**
185
     * Add member to organization.
186
     * @param Member|User|string|integer $member
187
     * @see createMemberModel
188
     * @see createMemberModelWithUser
189
     * @return boolean
190
     */
191
    public function addMember(&$member)
192
    {
193
        if ($this->getIsNewRecord()) {
194
            return false;
195
        }
196
        $model = null;
197
        if ($member instanceof Member) {
198
            $model = $this->createMemberModel($member);
199
        }
200
        if (($member instanceof User) || is_string($member) || is_int($member)) {
201
            $model = $this->createMemberModelWithUser($member);
202
        }
203
        $member = $model;
204
        return ($member instanceof Member) ? $member->save() : false;
205
    }
206
207
    /**
208
     * Create member model, and set organization with this.
209
     * @param Member $member If this parameter is not new record, it's organization
210
     * will be set with this, and return it. Otherwise, it will extract `User`
211
     * model and create new `Member` model.
212
     * @see createMemberModelWithUser
213
     * @return Member
214
     */
215
    public function createMemberModel($member)
216
    {
217
        if (!$member->getIsNewRecord()) {
218
            $member->setOrganization($this);
219
            return $member;
220
        }
221
        return $this->createMemberModelWithUser($member->memberUser);
222
    }
223
224
    /**
225
     * Create member model with user, and set organization with this.
226
     * @param User|string|integer $user
227
     * @return Member
228
     */
229
    public function createMemberModelWithUser($user)
230
    {
231
        $config = [
232
            'memberUser' => $user,
233
            'organization' => $this,
234
            'nickname' => '',
235
        ];
236
        if ($user->profile) {
237
            $config['nickname'] = $user->profile->nickname;
238
        }
239
        return $this->createMember($config);
240
    }
241
242
    /**
243
     * Remove member.
244
     * @param Member|User $member
245
     * @return boolean
246
     */
247
    public function removeMember(&$member)
248
    {
249
        if ($this->getIsNewRecord()) {
250
            return false;
251
        }
252
        $member = $this->getMember($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->getMember($member) on line 252 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...
253
        return $member && $member->delete() > 0;
254
    }
255
}
256