Completed
Push — master ( 93d695...69007e )
by vistart
05:29
created

UserRelationTrait::buildRelation()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 10
Bugs 0 Features 0
Metric Value
c 10
b 0
f 0
dl 0
loc 19
ccs 17
cts 17
cp 1
rs 8.8571
cc 5
eloc 14
nc 7
nop 2
crap 5
1
<?php
2
3
/**
4
 *  _   __ __ _____ _____ ___  ____  _____
5
 * | | / // // ___//_  _//   ||  __||_   _|
6
 * | |/ // /(__  )  / / / /| || |     | |
7
 * |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link http://vistart.name/
9
 * @copyright Copyright (c) 2016 vistart
10
 * @license http://vistart.name/license/
11
 */
12
13
namespace vistart\Models\traits;
14
15
use vistart\Models\models\BaseUserModel;
16
use vistart\Models\traits\MultipleBlameableTrait as mb;
17
18
/**
19
 * Relation features.
20
 * Note: Several methods associated with "inserting", "updating" and "removing" may
21
 * involve more DB operations, I strongly recommend those methods to be placed in
22
 * transaction execution, in order to ensure data consistency.
23
 * 若使用关系组功能,则使用此 trait 的类必须与使用 UserRelationGroupTrait 的类配合使用。
24
 * @property array $groupGuids the guid array of all groups which owned by current relation.
25
 * @property-read array $allGroups
26
 * @property-read array $nonGroupMembers
27
 * @property-read integer $groupsCount
28
 * @property-read array $groupsRules
29
 * @property boolean $isFavorite
30
 * @property-read \vistart\Models\models\BaseUserRelationModel $opposite
31
 * @version 2.0
32
 * @author vistart <[email protected]>
33
 */
34
trait UserRelationTrait
35
{
36
    use mb {
37
        mb::addBlame as addGroup;
38
        mb::createBlame as createGroup;
39
        mb::addOrCreateBlame as addOrCreateGroup;
40
        mb::removeBlame as removeGroup;
41
        mb::removeAllBlames as removeAllGroups;
42
        mb::getBlame as getGroup;
43
        mb::getOrCreateBlame as getOrCreateGroup;
44
        mb::getBlameds as getGroupMembers;
45
        mb::getBlameGuids as getGroupGuids;
46
        mb::setBlameGuids as setGroupGuids;
47
        mb::getAllBlames as getAllGroups;
48
        mb::getNonBlameds as getNonGroupMembers;
49
        mb::getBlamesCount as getGroupsCount;
50
        mb::getMultipleBlameableAttributeRules as getGroupsRules;
51
        mb::getEmptyBlamesJson as getEmptyGroupJson;
52
    }
53
54
    /**
55
     * @var string the another party of the relation.
56
     */
57
    public $otherGuidAttribute = 'other_guid';
58
59
    /**
60
     * @var string
61
     */
62
    public $remarkAttribute = 'remark';
63
    public static $relationSingle = 0;
64
    public static $relationMutual = 1;
65
    public $relationType = 1;
66
    public $relationTypes = [
67
        0 => 'Single',
68
        1 => 'Mutual',
69
    ];
70
71
    /**
72
     * @var string the attribute name of which determines the relation type.
73
     */
74
    public $mutualTypeAttribute = 'type';
75
    public static $mutualTypeNormal = 0x00;
76
    public static $mutualTypeSuspend = 0x01;
77
78
    /**
79
     * @var array Mutual types.
80
     */
81
    public static $mutualTypes = [
82
        0x00 => 'Normal',
83
        0x01 => 'Suspend',
84
    ];
85
86
    /**
87
     * @var string the attribute name of which determines the `favorite` field.
88
     */
89
    public $favoriteAttribute = 'favorite';
90
91
    /**
92
     * Get whether this relation is favorite or not.
93
     * @return boolean
94
     */
95 1
    public function getIsFavorite()
96
    {
97 1
        $favoriteAttribute = $this->favoriteAttribute;
98 1
        return is_string($favoriteAttribute) ? (int) $this->$favoriteAttribute > 0 : null;
99
    }
100
101
    /**
102
     * Set favorite.
103
     * @param boolean $fav
104
     */
105 1
    public function setIsFavorite($fav)
106
    {
107 1
        $favoriteAttribute = $this->favoriteAttribute;
108 1
        return is_string($favoriteAttribute) ? $this->$favoriteAttribute = ($fav ? 1 : 0) : null;
109
    }
110
111
    /**
112
     * @inheritdoc
113
     */
114 8
    public function rules()
115
    {
116 8
        return array_merge(parent::rules(), $this->getUserRelationRules());
117
    }
118
119
    /**
120
     * Validation rules associated with user relation.
121
     * @return array rules.
122
     */
123 8
    public function getUserRelationRules()
124
    {
125 8
        $rules = [];
126 8
        if ($this->relationType == static::$relationMutual) {
127
            $rules = [
128 7
                [[$this->mutualTypeAttribute], 'in', 'range' => array_keys(static::$mutualTypes)],
129 7
                [[$this->mutualTypeAttribute], 'default', 'value' => static::$mutualTypeNormal],
130 7
            ];
131 7
        }
132 8
        return array_merge($rules, $this->getRemarkRules(), $this->getFavoriteRules(), $this->getGroupsRules(), $this->getOtherGuidRules());
0 ignored issues
show
Bug introduced by
It seems like getGroupsRules() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
133
    }
134
135
    /**
136
     * Get remark.
137
     * @return string remark.
138
     */
139
    public function getRemark()
140
    {
141
        $remarkAttribute = $this->remarkAttribute;
142
        return is_string($remarkAttribute) ? $this->$remarkAttribute : null;
143
    }
144
145
    /**
146
     * Set remark.
147
     * @param string $remark
148
     * @return string remark.
149
     */
150
    public function setRemark($remark)
151
    {
152
        $remarkAttribute = $this->remarkAttribute;
153
        return is_string($remarkAttribute) ? $this->$remarkAttribute = $remark : null;
154
    }
155
156
    /**
157
     * Validation rules associated with remark attribute.
158
     * @return array rules.
159
     */
160 8
    public function getRemarkRules()
161
    {
162 8
        return is_string($this->remarkAttribute) ? [
163 8
            [[$this->remarkAttribute], 'string'],
164 8
            [[$this->remarkAttribute], 'default', 'value' => ''],
165 8
            ] : [];
166
    }
167
168
    /**
169
     * Validation rules associated with favorites attribute.
170
     * @return array rules.
171
     */
172 8
    public function getFavoriteRules()
173
    {
174 8
        return is_string($this->favoriteAttribute) ? [
175 8
            [[$this->favoriteAttribute], 'boolean'],
176 8
            [[$this->favoriteAttribute], 'default', 'value' => 0],
177 8
            ] : [];
178
    }
179
180
    /**
181
     * Validation rules associated with other guid attribute.
182
     * @return array rules.
183
     */
184 8
    public function getOtherGuidRules()
185
    {
186
        $rules = [
187 8
            [[$this->otherGuidAttribute], 'required'],
188 8
            [[$this->otherGuidAttribute], 'string', 'max' => 36],
189 8
            [[$this->otherGuidAttribute, $this->createdByAttribute], 'unique', 'targetAttribute' => [$this->otherGuidAttribute, $this->createdByAttribute]],
0 ignored issues
show
Bug introduced by
The property createdByAttribute does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
190 8
        ];
191 8
        return $rules;
192
    }
193
194
    /**
195
     * Attach events associated with user relation.
196
     */
197 8
    public function initUserRelationEvents()
198
    {
199 8
        $this->on(static::EVENT_INIT, [$this, 'onInitBlamesLimit']);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
200 8
        $this->on(static::$eventNewRecordCreated, [$this, 'onInitGroups']);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
201 8
        $this->on(static::$eventNewRecordCreated, [$this, 'onInitRemark']);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
202 8
        $this->on(static::$eventMultipleBlamesChanged, [$this, 'onBlamesChanged']);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
203 8
        $this->on(static::EVENT_AFTER_INSERT, [$this, 'onInsertRelation']);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
204 8
        $this->on(static::EVENT_AFTER_UPDATE, [$this, 'onUpdateRelation']);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
205 8
        $this->on(static::EVENT_AFTER_DELETE, [$this, 'onDeleteRelation']);
0 ignored issues
show
Bug introduced by
It seems like on() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
206 8
    }
207
208
    /**
209
     * Get opposite relation to self.
210
     * @return \vistart\Models\models\BaseUserRelationModel
211
     */
212 7
    public function getOpposite()
213
    {
214 7
        if ($this->isNewRecord) {
0 ignored issues
show
Bug introduced by
The property isNewRecord does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
215 1
            return null;
216
        }
217 7
        $createdByAttribute = $this->createdByAttribute;
218 7
        $otherGuidAttribute = $this->otherGuidAttribute;
219 7
        return static::find()->opposite($this->$createdByAttribute, $this->$otherGuidAttribute);
220
    }
221
222
    /**
223
     * Build a suspend mutual relation, not support single relation.
224
     * @param BaseUserModel|string $user Initiator or its GUID.
225
     * @param BaseUserModel|string $other Recipient or its GUID.
226
     * @return \vistart\Models\models\BaseUserRelationModel The relation will be
227
     * given if exists, or return a new relation.
228
     */
229 1
    public static function buildSuspendRelation($user, $other)
230
    {
231 1
        $relation = static::buildRelation($user, $other);
232 1
        $btAttribute = $relation->mutualTypeAttribute;
233 1
        $relation->$btAttribute = static::$mutualTypeSuspend;
234 1
        return $relation;
235
    }
236
237
    /**
238
     * Build a normal relation.
239
     * @param BaseUserModel|string $user Initiator or its GUID.
240
     * @param BaseUserModel|string $other Recipient or its GUID.
241
     * @return \vistart\Models\models\BaseUserRelationModel The relation will be
242
     * given if exists, or return a new relation.
243
     */
244 8
    public static function buildNormalRelation($user, $other)
245
    {
246 8
        $relation = static::buildRelation($user, $other);
247 8
        if ($relation->relationType == static::$relationMutual) {
248 7
            $btAttribute = $relation->mutualTypeAttribute;
249 7
            $relation->$btAttribute = static::$mutualTypeNormal;
250 7
        }
251 8
        return $relation;
252
    }
253
254
    /**
255
     * Build relation between initiator and recipient.
256
     * @param BaseUserModel|string $user Initiator or its GUID.
257
     * @param BaseUserModel|string $other Recipient or its GUID.
258
     * @return \vistart\Models\models\BaseUserRelationModel The relation will be
259
     * given if exists, or return a new relation.
260
     */
261 8
    protected static function buildRelation($user, $other)
262
    {
263 8
        $relation = static::find()->initiators($user)->recipients($other)->one();
264 8
        if (!$relation) {
265 8
            $rni = static::buildNoInitModel();
266 8
            $createdByAttribute = $rni->createdByAttribute;
267 8
            $otherGuidAttribute = $rni->otherGuidAttribute;
268 8
            $userClass = $rni->userClass;
269 8
            if ($user instanceof BaseUserModel) {
270 8
                $userClass = $userClass ? : get_class($user);
271 8
                $user = $user->guid;
272 8
            }
273 8
            if ($other instanceof BaseUserModel) {
274 8
                $other = $other->guid;
275 8
            }
276 8
            $relation = new static([$createdByAttribute => $user, $otherGuidAttribute => $other, 'userClass' => $userClass]);
0 ignored issues
show
Unused Code introduced by
The call to UserRelationTrait::__construct() has too many arguments starting with array($createdByAttribut...erClass' => $userClass).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
277 8
        }
278 8
        return $relation;
279
    }
280
281
    /**
282
     * Build opposite mutual relation throughout the current relation, not support
283
     * single relation. The opposite relation will be given if existed.
284
     * @param \vistart\Models\models\BaseUserRelationModel $relation
285
     * @return \vistart\Models\models\BaseUserRelationModel
286
     */
287 7
    protected static function buildOppositeRelation($relation)
288
    {
289 7
        if ($relation->relationType == static::$relationSingle) {
290
            return null;
291
        }
292 7
        $createdByAttribute = $relation->createdByAttribute;
293 7
        $otherGuidAttribute = $relation->otherGuidAttribute;
294 7
        $mutualTypeAttribute = $relation->mutualTypeAttribute;
295 7
        $opposite = static::buildRelation($relation->$otherGuidAttribute, $relation->$createdByAttribute);
296 7
        $opposite->$mutualTypeAttribute = $relation->$mutualTypeAttribute;
297 7
        return $opposite;
298
    }
299
300
    /**
301
     * Remove myself.
302
     * @return integer|false The number of relations removed, or false if the remove
303
     * is unsuccessful for some reason. Note that it is possible the number of relations
304
     * removed is 0, even though the remove execution is successful.
305
     */
306 2
    public function remove()
307
    {
308 2
        return $this->delete();
0 ignored issues
show
Bug introduced by
The method delete() does not exist on vistart\Models\traits\UserRelationTrait. Did you maybe mean onDeleteRelation()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
309
    }
310
311
    /**
312
     * Remove first relation between initiator(s) and recipient(s).
313
     * @param BaseUserModel|string|array $user Initiator or its guid, or array of them.
314
     * @param BaseUserModel|string|array $other Recipient or its guid, or array of them.
315
     * @return integer|false The number of relations removed.
316
     */
317 1
    public static function removeOneRelation($user, $other)
318
    {
319 1
        return static::find()->initiators($user)->recipients($other)->one()->delete();
320
    }
321
322
    /**
323
     * Remove all relations between initiator(s) and recipient(s).
324
     * @param BaseUserModel|string|array $user Initiator or its guid, or array of them.
325
     * @param BaseUserModel|string|array $other Recipient or its guid, or array of them.
326
     * @return integer The number of relations removed.
327
     */
328 2
    public static function removeAllRelations($user, $other)
329
    {
330 2
        $rni = static::buildNoInitModel();
331 2
        $createdByAttribute = $rni->createdByAttribute;
332 2
        $otherGuidAttribute = $rni->otherGuidAttribute;
333 2
        return static::deleteAll([$createdByAttribute => $user, $otherGuidAttribute => $other]);
334
    }
335
336
    /**
337
     * Get first relation between initiator(s) and recipient(s).
338
     * @param BaseUserModel|string|array $user Initiator or its guid, or array of them.
339
     * @param BaseUserModel|string|array $other Recipient or its guid, or array of them.
340
     * @return \vistart\Models\models\BaseUserRelationModel
341
     */
342 1
    public static function findOneRelation($user, $other)
343
    {
344 1
        return static::find()->initiators($user)->recipients($other)->one();
345
    }
346
347
    /**
348
     * Get first opposite relation between initiator(s) and recipient(s).
349
     * @param BaseUserModel|string $user Initiator or its guid, or array of them.
350
     * @param BaseUserModel|string $other Recipient or its guid, or array of them.
351
     * @return \vistart\Models\models\BaseUserRelationModel
352
     */
353 7
    public static function findOneOppositeRelation($user, $other)
354
    {
355 7
        return static::find()->initiators($other)->recipients($user)->one();
356
    }
357
358
    /**
359
     * Get user's or users' all relations, or by specified groups.
360
     * @param BaseUserModel|string|array $user Initiator or its GUID, or Initiators or their GUIDs.
361
     * @param BaseUserRelationGroupModel|string|array|null $groups UserRelationGroup or its guid, or array of them. If you do not want to delimit the groups, please assign null.
362
     * @return array all eligible relations
363
     */
364
    public static function findOnesAllRelations($user, $groups = null)
365
    {
366
        return static::find()->initiators($user)->groups($groups)->all();
367
    }
368
369
    /**
370
     * Initialize groups attribute.
371
     * @param \yii\base\Event $event
372
     */
373 8
    public function onInitGroups($event)
374
    {
375 8
        $sender = $event->sender;
376 8
        $sender->removeAllGroups();
377 8
    }
378
379
    /**
380
     * Initialize remark attribute.
381
     * @param \yii\base\Event $event
382
     */
383 8
    public function onInitRemark($event)
384
    {
385 8
        $sender = $event->sender;
386 8
        $remarkAttribute = $sender->remarkAttribute;
387 8
        is_string($remarkAttribute) ? $sender->$remarkAttribute = '' : null;
388 8
    }
389
390
    /**
391
     * The event triggered after insert new relation.
392
     * The opposite relation should be inserted without triggering events
393
     * simultaneously after new relation inserted,
394
     * @param \yii\base\Event $event
395
     */
396 8
    public function onInsertRelation($event)
397
    {
398 8
        $sender = $event->sender;
399 8
        if ($sender->relationType == static::$relationMutual) {
400 7
            $opposite = static::buildOppositeRelation($sender);
401 7
            $opposite->off(static::EVENT_AFTER_INSERT, [$opposite, 'onInsertRelation']);
402 7
            if (!$opposite->save()) {
403
                $opposite->recordWarnings();
404
            }
405 7
            $opposite->on(static::EVENT_AFTER_INSERT, [$opposite, 'onInsertRelation']);
406 7
        }
407 8
    }
408
409
    /**
410
     * The event triggered after update relation.
411
     * The opposite relation should be updated without triggering events
412
     * simultaneously after existed relation removed.
413
     * @param \yii\base\Event $event
414
     */
415 3
    public function onUpdateRelation($event)
416
    {
417 3
        $sender = $event->sender;
418 3
        if ($sender->relationType == static::$relationMutual) {
419 3
            $opposite = static::buildOppositeRelation($sender);
420 3
            $opposite->off(static::EVENT_AFTER_UPDATE, [$opposite, 'onUpdateRelation']);
421 3
            if (!$opposite->save()) {
422
                $opposite->recordWarnings();
423
            }
424 3
            $opposite->on(static::EVENT_AFTER_UPDATE, [$opposite, 'onUpdateRelation']);
425 3
        }
426 3
    }
427
428
    /**
429
     * The event triggered after delete relation.
430
     * The opposite relation should be deleted without triggering events
431
     * simultaneously after existed relation removed.
432
     * @param \yii\base\Event $event
433
     */
434 2
    public function onDeleteRelation($event)
435
    {
436 2
        $sender = $event->sender;
437 2
        if ($sender->relationType == static::$relationMutual) {
438 2
            $createdByAttribute = $sender->createdByAttribute;
439 2
            $otherGuidAttribute = $sender->otherGuidAttribute;
440 2
            $sender->off(static::EVENT_AFTER_DELETE, [$sender, 'onDeleteRelation']);
441 2
            static::removeAllRelations($sender->$otherGuidAttribute, $sender->$createdByAttribute);
442 2
            $sender->on(static::EVENT_AFTER_DELETE, [$sender, 'onDeleteRelation']);
443 2
        }
444 2
    }
445
}
446