Completed
Push — master ( 4666a2...f8cbc9 )
by vistart
07:25 queued 04:35
created

Organization::attributeLabels()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 0
cts 11
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 11
nc 1
nop 0
crap 2
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\DepartmentQuery;
20
use rhosocial\organization\queries\OrganizationQuery;
21
use Yii;
22
23
/**
24
 * Base Organization.
25
 * This class is an abstract class that can not be instantiated directly.
26
 * You can use [[Organization]] or [[Department]] instead.
27
 *
28
 * @method Member createMember(array $config) Create member who is subordinate 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
class Organization 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 12
    public function init()
94
    {
95 12
        $this->parentAttribute = 'parent_guid';
96 12
        if (class_exists($this->memberClass)) {
97 12
            $this->addSubsidiaryClass('Member', ['class' => Member::class]);
98 12
        }
99 12
        if ($this->skipInit) {
100 12
            return;
101
        }
102 12
        parent::init();
103 12
    }
104
105
    /**
106
     * @inheritdoc
107
     */
108
    public function attributeLabels()
109
    {
110
        return [
111
            'guid' => Yii::t('app', 'GUID'),
112
            'id' => Yii::t('app', 'ID'),
113
            'ip' => Yii::t('app', 'IP'),
114
            'ip_type' => Yii::t('app', 'IP Address Type'),
115
            'parent' => Yii::t('app', 'Parent'),
116
            'created_at' => Yii::t('app', 'Create Time'),
117
            'updated_at' => Yii::t('app', 'Update Time'),
118
            'status' => Yii::t('app', 'Status'),
119
            'type' => Yii::t('app', 'Type'),
120
        ];
121
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126 12
    public static function tableName()
127
    {
128 12
        return '{{%organization}}';
129
    }
130
131 11
    protected function getTypeRules()
132
    {
133
        return [
134 11
            ['type', 'default', 'value' => static::TYPE_ORGANIZATION],
135 11
            ['type', 'required'],
136 11
            ['type', 'in', 'range' => [static::TYPE_ORGANIZATION, static::TYPE_DEPARTMENT]],
137 11
        ];
138
    }
139
140 11
    public function rules()
141
    {
142 11
        return array_merge(parent::rules(), $this->getTypeRules(), $this->getSelfBlameableRules());
143
    }
144
145
    /**
146
     * Get Member Query.
147
     * @return MemberQuery
148
     */
149
    public function getMembers()
150
    {
151
        return $this->hasMany($this->memberClass, [$this->guidAttribute => $this->getNoInitMember()->createdByAttribute])->inverseOf('organization');
152
    }
153
154
    /**
155
     * Get organization member users' query.
156
     * @return BaseUserQuery
157
     */
158
    public function getMemberUsers()
159
    {
160
        $noInit = $this->getNoInitMember();
161
        $class = $noInit->memberUserClass;
162
        $noInitUser = $class::buildNoInitModel();
163
        return $this->hasMany($class, [$this->guidAttribute => $noInitUser->guidAttribute])->via('members')->inverseOf('atOrganizations');
164
    }
165
166
    /**
167
     * Get member with specified user.
168
     * @param User|string|integer $user
169
     * @return Member Null if `user` is not in this organization.
170
     */
171
    public function getMember($user)
172
    {
173
        if ($user instanceof $this->memberClass) {
174
            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\Organization::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...
175
        }
176
        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 176 which is incompatible with the return type documented by rhosocial\organization\Organization::getMember of type rhosocial\organization\Member|null.
Loading history...
177
    }
178
179
    /**
180
     * Add member to organization.
181
     * @param Member|User|string|integer $member
182
     * @see createMemberModel
183
     * @see createMemberModelWithUser
184
     * @return boolean
185
     */
186
    public function addMember(&$member)
187
    {
188
        if ($this->getIsNewRecord()) {
189
            return false;
190
        }
191
        $model = null;
192
        if ($member instanceof Member) {
193
            $model = $this->createMemberModel($member);
194
        }
195
        if (($member instanceof User) || is_string($member) || is_int($member)) {
196
            $model = $this->createMemberModelWithUser($member);
197
        }
198
        $member = $model;
199
        return ($member instanceof Member) ? $member->save() : false;
200
    }
201
202
    /**
203
     * Create member model, and set organization with this.
204
     * @param Member $member If this parameter is not new record, it's organization
205
     * will be set with this, and return it. Otherwise, it will extract `User`
206
     * model and create new `Member` model.
207
     * @see createMemberModelWithUser
208
     * @return Member
209
     */
210
    public function createMemberModel($member)
211
    {
212
        if (!$member->getIsNewRecord()) {
213
            $member->setOrganization($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<rhosocial\organization\Organization>, but the function expects a object<rhosocial\organization\BaseOrganization>.

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...
214
            return $member;
215
        }
216
        return $this->createMemberModelWithUser($member->memberUser);
217
    }
218
219
    /**
220
     * Create member model with user, and set organization with this.
221
     * @param User|string|integer $user
222
     * @return Member
223
     */
224 10
    public function createMemberModelWithUser($user)
225
    {
226
        $config = [
227 10
            'memberUser' => $user,
228 10
            'organization' => $this,
229 10
            'nickname' => '',
230 10
        ];
231 10
        if ($user->profile) {
232 10
            $config['nickname'] = $user->profile->nickname;
233 10
        }
234 10
        return $this->createMember($config);
235
    }
236
237
    /**
238
     * Remove member.
239
     * @param Member|User $member
240
     * @return boolean
241
     */
242
    public function removeMember(&$member)
243
    {
244
        if ($this->getIsNewRecord()) {
245
            return false;
246
        }
247
        $member = $this->getMember($member);
0 ignored issues
show
Bug introduced by
It seems like $member defined by $this->getMember($member) on line 247 can also be of type object<rhosocial\organization\Member>; however, rhosocial\organization\Organization::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...
248
        return $member && $member->delete() > 0;
249
    }
250
}
251