Completed
Push — master ( fa3ca9...034150 )
by vistart
05:25
created

UserRelationTrait::getRemarkRules()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
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\base\models\traits;
14
15
use rhosocial\base\models\models\BaseUserModel;
16
use rhosocial\base\models\traits\MultipleBlameableTrait as mb;
17
use yii\base\ModelEvent;
18
use yii\base\InvalidConfigException;
19
use yii\base\InvalidValueException;
20
use yii\db\Connection;
21
use yii\db\IntegrityException;
22
23
/**
24
 * Relation features.
25
 * This trait should be used in user relation model which is extended from
26
 * [[BaseBlameableModel]], and is specified `$hostClass` property. And the user
27
 * class should be extended from [[BaseUserModel]], or any other classes used
28
 * [[UserTrait]].
29
 * Notice: Several methods associated with "inserting", "updating" and "removing" may
30
 * involve more DB operations, I strongly recommend those methods to be placed in
31
 * transaction execution, in order to ensure data consistency.
32
 * If you want to use group feature, the class used [[UserRelationGroupTrait]]
33
 * must be used coordinately.
34
 * @property array $groupGuids the guid array of all groups which owned by current relation.
35
 * @property-read array $favoriteRules
36
 * @property boolean $isFavorite
37
 * @property-read static $opposite
38
 * @property-read array $otherGuidRules
39
 * @property string $remark
40
 * @property-read array $remarkRules
41
 * @property-read array $userRelationRules
42
 * @property-read mixed $group
43
 * @property-read array $groupMembers
44
 * @property array $groupGuids
45
 * @property-read array $allGroups
46
 * @property-read array $nonGroupMembers
47
 * @property-read integer $groupsCount
48
 * @property-read array $groupsRules
49
 * @version 1.0
50
 * @author vistart <[email protected]>
51
 */
52
trait UserRelationTrait
53
{
54
    use mb,
55
        MutualTrait {
56
        mb::addBlame as addGroup;
57
        mb::createBlame as createGroup;
58
        mb::addOrCreateBlame as addOrCreateGroup;
59
        mb::removeBlame as removeGroup;
60
        mb::removeAllBlames as removeAllGroups;
61
        mb::getBlame as getGroup;
62
        mb::getOrCreateBlame as getOrCreateGroup;
63
        mb::getBlameds as getGroupMembers;
64
        mb::getBlameGuids as getGroupGuids;
65
        mb::setBlameGuids as setGroupGuids;
66
        mb::getAllBlames as getAllGroups;
67
        mb::getNonBlameds as getNonGroupMembers;
68
        mb::getBlamesCount as getGroupsCount;
69
        mb::getMultipleBlameableAttributeRules as getGroupsRules;
70
    }
71
72
    /**
73
     * @var string
74
     */
75
    public $remarkAttribute = 'remark';
76
    public static $relationSingle = 0;
77
    public static $relationMutual = 1;
78
    public $relationType = 1;
79
    public static $relationTypes = [
80
        0 => 'Single',
81
        1 => 'Mutual',
82
    ];
83
84
    /**
85
     * @var string the attribute name of which determines the relation type.
86
     */
87
    public $mutualTypeAttribute = 'type';
88
    public static $mutualTypeNormal = 0x00;
89
    public static $mutualTypeSuspend = 0x01;
90
91
    /**
92
     * @var array Mutual types.
93
     */
94
    public static $mutualTypes = [
95
        0x00 => 'Normal',
96
        0x01 => 'Suspend',
97
    ];
98
99
    /**
100
     * @var string the attribute name of which determines the `favorite` field.
101
     */
102
    public $favoriteAttribute = 'favorite';
103
104
    /**
105
     * Permit to build self relation.
106
     * @var boolean 
107
     */
108
    public $relationSelf = false;
109
110
    /**
111
     * Get whether this relation is favorite or not.
112
     * @return boolean
113
     */
114 1
    public function getIsFavorite()
115
    {
116 1
        $favoriteAttribute = $this->favoriteAttribute;
117 1
        return (is_string($favoriteAttribute) && !empty($favoriteAttribute)) ? (int) $this->$favoriteAttribute > 0 : null;
118
    }
119
120
    /**
121
     * Set favorite.
122
     * @param boolean $fav
123
     */
124 1
    public function setIsFavorite($fav)
125
    {
126 1
        $favoriteAttribute = $this->favoriteAttribute;
127 1
        return (is_string($favoriteAttribute) && !empty($favoriteAttribute)) ? $this->$favoriteAttribute = ($fav ? 1 : 0) : null;
128
    }
129
130
    /**
131
     * @inheritdoc
132
     */
133 30
    public function rules()
134
    {
135 30
        return array_merge(parent::rules(), $this->getUserRelationRules());
136
    }
137
138
    /**
139
     * Validation rules associated with user relation.
140
     * @return array rules.
141
     */
142 30
    public function getUserRelationRules()
143
    {
144 30
        $rules = [];
145 30
        if ($this->relationType == static::$relationMutual) {
146
            $rules = [
147 12
                [[$this->mutualTypeAttribute], 'in', 'range' => array_keys(static::$mutualTypes)],
148 12
                [[$this->mutualTypeAttribute], 'default', 'value' => static::$mutualTypeNormal],
149
            ];
150
        }
151 30
        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...
152
    }
153
154
    /**
155
     * Get remark.
156
     * @return string remark.
157
     */
158 1
    public function getRemark()
159
    {
160 1
        $remarkAttribute = $this->remarkAttribute;
161 1
        return (is_string($remarkAttribute) && !empty($remarkAttribute)) ? $this->$remarkAttribute : null;
162
    }
163
164
    /**
165
     * Set remark.
166
     * @param string $remark
167
     * @return string remark.
168
     */
169 1
    public function setRemark($remark)
170
    {
171 1
        $remarkAttribute = $this->remarkAttribute;
172 1
        return (is_string($remarkAttribute) && !empty($remarkAttribute)) ? $this->$remarkAttribute = $remark : null;
173
    }
174
175
    /**
176
     * Validation rules associated with remark attribute.
177
     * @return array rules.
178
     */
179 30
    public function getRemarkRules()
180
    {
181 30
        return is_string($this->remarkAttribute) ? [
182 30
            [[$this->remarkAttribute], 'string'],
183 30
            [[$this->remarkAttribute], 'default', 'value' => ''],
184 30
            ] : [];
185
    }
186
187
    /**
188
     * Validation rules associated with favorites attribute.
189
     * @return array rules.
190
     */
191 30
    public function getFavoriteRules()
192
    {
193 30
        return is_string($this->favoriteAttribute) ? [
194 30
            [[$this->favoriteAttribute], 'boolean'],
195 30
            [[$this->favoriteAttribute], 'default', 'value' => 0],
196 30
            ] : [];
197
    }
198
199
    /**
200
     * Validation rules associated with other guid attribute.
201
     * @return array rules.
202
     */
203 30
    public function getOtherGuidRules()
204
    {
205 30
        $rules = array_merge($this->getMutualRules(), [
206 30
            [[$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...
207
        ]);
208 30
        return $rules;
209
    }
210
211
    /**
212
     * Attach events associated with user relation.
213
     */
214 33
    public function initUserRelationEvents()
215
    {
216 33
        $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...
217 33
        $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...
218 33
        $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...
219 33
        $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...
220 33
        $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...
221 33
        $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...
222 33
        $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...
223 33
    }
224
225
    /**
226
     * Get opposite relation against self.
227
     * @return static
228
     */
229 1
    public function getOpposite()
230
    {
231 1
        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...
232 1
            return null;
233
        }
234 1
        return static::find()->opposite($this->initiator, $this->recipient);
235
    }
236
237
    /**
238
     * Check whether the initiator is followed by recipient.
239
     * @param BaseUserModel $initiator
240
     * @param BaseUserModel $recipient
241
     * @return boolean
242
     */
243 6
    public static function isFollowed($initiator, $recipient)
244
    {
245 6
        return static::find()->initiators($recipient)->recipients($initiator)->exists();
246
    }
247
248
    /**
249
     * Check whether the initiator is following recipient.
250
     * @param BaseUserModel $initiator
251
     * @param BaseUserModel $recipient
252
     * @return boolean
253
     */
254 6
    public static function isFollowing($initiator, $recipient)
255
    {
256 6
        return static::find()->initiators($initiator)->recipients($recipient)->exists();
257
    }
258
259
    /**
260
     * Check whether the initiator is following and followed by recipient mutually (Single Relation).
261
     * Or check whether the initiator and recipient are friend whatever the mutual type is normal or suspend.
262
     * @param BaseUserModel $initiator
263
     * @param BaseUserModel $recipient
264
     * @return boolean
265
     */
266 3
    public static function isMutual($initiator, $recipient)
267
    {
268 3
        return static::isFollowed($initiator, $recipient) && static::isFollowing($initiator, $recipient);
269
    }
270
271
    /**
272
     * Check whether the initiator is following and followed by recipient mutually (Single Relation).
273
     * Or check whether the initiator and recipient are friend if the mutual type is normal.
274
     * @param BaseUserModel $initiator
275
     * @param BaseUserModel $recipient
276
     * @return boolean
277
     */
278 6
    public static function isFriend($initiator, $recipient)
279
    {
280 6
        $query = static::find();
281 6
        $model = $query->noInitModel;
282
        /* @var $model static */
283 6
        if ($model->relationType == static::$relationSingle) {
284 2
            return static::isMutual($initiator, $recipient);
285
        }
286 4
        if ($model->relationType == static::$relationMutual) {
287 4
            $relation = static::find()->initiators($initiator)->recipients($recipient)->andWhere([$model->mutualTypeAttribute => static::$mutualTypeNormal])->exists();
288 4
            $inverse = static::find()->recipients($initiator)->initiators($recipient)->andWhere([$model->mutualTypeAttribute => static::$mutualTypeNormal])->exists();
289 4
            return $relation && $inverse;
290
        }
291
        return false;
292
    }
293
294
    /**
295
     * Build new or return existed suspend mutual relation, or return null if
296
     * current type is not mutual.
297
     * @see buildRelation()
298
     * @param BaseUserModel|string $user Initiator or its GUID.
299
     * @param BaseUserModel|string $other Recipient or its GUID.
300
     * @return static The relation will be
301
     * given if exists, or return a new relation.
302
     */
303 15
    public static function buildSuspendRelation($user, $other)
304
    {
305 15
        $relation = static::buildRelation($user, $other);
306 15
        if (!$relation || $relation->relationType != static::$relationMutual) {
307 1
            return null;
308
        }
309 14
        $relation->setMutualType(static::$mutualTypeSuspend);
310 14
        return $relation;
311
    }
312
313
    /**
314
     * Build new or return existed normal relation.
315
     * The status of mutual relation will be changed to normal if it is not. 
316
     * @see buildRelation()
317
     * @param BaseUserModel|string $user Initiator or its GUID.
318
     * @param BaseUserModel|string $other Recipient or its GUID.
319
     * @return static The relation will be
320
     * given if exists, or return a new relation.
321
     */
322 33
    public static function buildNormalRelation($user, $other)
323
    {
324 33
        $relation = static::buildRelation($user, $other);
325 33
        if (!$relation) {
326 1
            return null;
327
        }
328 33
        if ($relation->relationType == static::$relationMutual) {
329 14
            $relation->setMutualType(static::$mutualTypeNormal);
330
        }
331 33
        return $relation;
332
    }
333
    
334
    /**
335
     * Transform relation from suspend to normal.
336
     * Note: You should ensure the relation model is not new one.
337
     * @param static $relation
0 ignored issues
show
introduced by
The type UserRelationTrait for parameter $relation is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
338
     * @return boolean
339
     */
340 2
    public static function transformSuspendToNormal($relation)
341
    {
342 2
        if (!$relation || !($relation instanceof static) || $relation->getIsNewRecord() || $relation->relationType != static::$relationMutual) {
0 ignored issues
show
Bug introduced by
It seems like getIsNewRecord() 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...
343 1
            return false;
344
        }
345 1
        $new = static::buildNormalRelation($relation->initiator, $relation->recipient);
346 1
        return $new->save() && $relation->refresh();
0 ignored issues
show
Bug introduced by
It seems like save() 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...
Bug introduced by
It seems like refresh() 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...
347
    }
348
    
349
    /**
350
     * Revert relation from normal to suspend.
351
     * Note: You should ensure the relation model is not new one.
352
     * @param static $relation
0 ignored issues
show
introduced by
The type UserRelationTrait for parameter $relation is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
353
     * @return boolean
354
     */
355 2
    public static function revertNormalToSuspend($relation)
356
    {
357 2
        if (!$relation || !($relation instanceof static) || $relation->getIsNewRecord() || $relation->relationType != static::$relationMutual) {
0 ignored issues
show
Bug introduced by
It seems like getIsNewRecord() 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...
358 1
            return false;
359
        }
360 1
        $new = static::buildSuspendRelation($relation->initiator, $relation->recipient);
361 1
        return $new->save() && $relation->refresh();
0 ignored issues
show
Bug introduced by
It seems like save() 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...
Bug introduced by
It seems like refresh() 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...
362
    }
363
364
    /**
365
     * Build new or return existed relation between initiator and recipient.
366
     * If relation between initiator and recipient is not found, new relation will
367
     * be built. If initiator and recipient are the same one and it is not allowed
368
     * to build self relation, null will be given.
369
     * If you want to know whether the relation exists, you can check the return
370
     * value of `getIsNewRecord()` method.
371
     * @param BaseUserModel|string $user Initiator or its GUID.
372
     * @param BaseUserModel|string $other Recipient or its GUID.
373
     * @return static The relation will be
374
     * given if exists, or return a new relation. Or return null if not allowed
375
     * to build self relation,
376
     */
377 33
    protected static function buildRelation($user, $other)
378
    {
379 33
        $relationQuery = static::find()->initiators($user)->recipients($other);
380 33
        $noInit = $relationQuery->noInitModel;
381 33
        $relation = $relationQuery->one();
382 33
        if (!$relation) {
383 33
            $createdByAttribute = $noInit->createdByAttribute;
384 33
            $otherGuidAttribute = $noInit->otherGuidAttribute;
385 33
            $hostClass = $noInit->hostClass;
386 33
            if ($user instanceof BaseUserModel) {
387 33
                $hostClass = $hostClass ? : $user->className();
388 33
                $user = $user->getGUID();
389
            }
390 33
            if ($other instanceof BaseUserModel) {
391 33
                $other = $other->getGUID();
392
            }
393 33
            if (!$noInit->relationSelf && $user == $other) {
394 1
                return null;
395
            }
396 33
            $relation = new static([$createdByAttribute => $user, $otherGuidAttribute => $other, 'hostClass' => $hostClass]);
0 ignored issues
show
Unused Code introduced by
The call to UserRelationTrait::__construct() has too many arguments starting with array($createdByAttribut...stClass' => $hostClass).

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...
397
        }
398 33
        return $relation;
399
    }
400
401
    /**
402
     * Build opposite relation throughout the current relation. The opposite
403
     * relation will be given if existed.
404
     * @param static $relation
0 ignored issues
show
introduced by
The type UserRelationTrait for parameter $relation is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
405
     * @return static
406
     */
407 12
    protected static function buildOppositeRelation($relation)
408
    {
409 12
        if (!$relation) {
410
            return null;
411
        }
412 12
        $createdByAttribute = $relation->createdByAttribute;
413 12
        $otherGuidAttribute = $relation->otherGuidAttribute;
414 12
        $opposite = static::buildRelation($relation->$otherGuidAttribute, $relation->$createdByAttribute);
415 12
        if ($relation->relationType == static::$relationSingle) {
416
            $opposite->relationType = static::$relationSingle;
417 12
        } elseif ($relation->relationType == static::$relationMutual) {
418 12
            $opposite->setMutualType($relation->getMutualType());
419
        }
420 12
        return $opposite;
421
    }
422
    
423
    /**
424
     * Get mutual type.
425
     * @return integer
426
     */
427 12
    public function getMutualType()
428
    {
429 12
        $btAttribute = $this->mutualTypeAttribute;
430 12
        if (is_string($btAttribute) && !empty($btAttribute)) {
431 12
            return $this->$btAttribute;
432
        }
433
        return static::$mutualTypeNormal;
434
    }
435
    
436
    /**
437
     * Set mutual type.
438
     * @param integer $type
439
     * @return integer
440
     */
441 14
    protected function setMutualType($type)
442
    {
443 14
        if (!array_key_exists($type, static::$mutualTypes)) {
444
            $type = static::$mutualTypeNormal;
445
        }
446 14
        $btAttribute = $this->mutualTypeAttribute;
447 14
        if (is_string($btAttribute) && !empty($btAttribute)) {
448 14
            return $this->$btAttribute = $type;
449
        }
450
        return static::$mutualTypeNormal;
451
    }
452
    
453
    /**
454
     * Insert relation, the process is placed in a transaction.
455
     * Note: This feature only support relational databases and skip all errors.
456
     * If you don't want to use transaction or database doesn't support it,
457
     * please use `save()` directly.
458
     * @param static $relation
0 ignored issues
show
introduced by
The type UserRelationTrait for parameter $relation is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
459
     * @param Connection $db
460
     * @return boolean
461
     * @throws InvalidValueException
462
     * @throws InvalidConfigException
463
     * @throws IntegrityException
464
     */
465 1
    public static function insertRelation($relation, Connection $db = null)
466
    {
467 1
        if (!$relation || !($relation instanceof static)) {
468 1
            return false;
469
        }
470 1
        if (!$relation->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
It seems like getIsNewRecord() 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...
471 1
            throw new InvalidValueException('This relation is not new one.');
472
        }
473 1
        if (!$db && isset(\Yii::$app->db) && \Yii::$app->db instanceof Connection) {
474 1
            $db = \Yii::$app->db;
475
        }
476 1
        if (!$db) {
477
            throw new InvalidConfigException('Invalid database connection.');
478
        }
479
        /* @var $db Connection */
480 1
        $transaction = $db->beginTransaction();
481
        try {
482 1
            if (!$relation->save()) {
0 ignored issues
show
Bug introduced by
It seems like save() 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...
483
                throw new IntegrityException('Relation insert failed.');
484
            }
485 1
            $transaction->commit();
486
        } catch (\Exception $ex) {
487
            $transaction->rollBack();
488
            return false;
489
        }
490 1
        return true;
491
    }
492
    
493
    /**
494
     * Remove relation, the process is placed in transaction.
495
     * Note: This feature only support relational databases and skip all errors.
496
     * If you don't want to use transaction or database doesn't support it,
497
     * please use `remove()` directly.
498
     * @param static $relation
0 ignored issues
show
introduced by
The type UserRelationTrait for parameter $relation is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
499
     * @param Connection $db
500
     * @return boolean|integer
501
     * @throws InvalidConfigException
502
     */
503 1
    public static function removeRelation($relation, Connection $db = null)
504
    {
505 1
        if (!$relation || !($relation instanceof static) || $relation->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
It seems like getIsNewRecord() 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...
506 1
            return false;
507
        }
508
        
509 1
        if (!$db && isset(\Yii::$app->db) && \Yii::$app->db instanceof Connection) {
510 1
            $db = \Yii::$app->db;
511
        }
512 1
        if (!$db) {
513
            throw new InvalidConfigException('Invalid database connection.');
514
        }
515
        /* @var $db Connection */
516 1
        $transaction = $db->beginTransaction();
517
        try {
518 1
            $result = $relation->remove();
519 1
            $transaction->commit();
520
        } catch (\Exception $ex) {
521
            $transaction->rollBack();
522
            return false;
523
        }
524 1
        return $result;
525
    }
526
527
    /**
528
     * Remove myself.
529
     * @return integer|false The number of relations removed, or false if the remove
530
     * is unsuccessful for some reason. Note that it is possible the number of relations
531
     * removed is 0, even though the remove execution is successful.
532
     */
533 33
    public function remove()
534
    {
535 33
        return $this->delete();
0 ignored issues
show
Bug introduced by
The method delete() does not exist on rhosocial\base\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...
536
    }
537
538
    /**
539
     * Remove first relation between initiator(s) and recipient(s).
540
     * @param BaseUserModel|string|array $user Initiator or its guid, or array of them.
541
     * @param BaseUserModel|string|array $other Recipient or its guid, or array of them.
542
     * @return integer|false The number of relations removed.
543
     */
544 1
    public static function removeOneRelation($user, $other)
545
    {
546 1
        $model = static::find()->initiators($user)->recipients($other)->one();
547 1
        if ($model instanceof static) {
548 1
            return $model->remove();
549
        }
550
        return false;
551
    }
552
553
    /**
554
     * Remove all relations between initiator(s) and recipient(s).
555
     * @param BaseUserModel|string|array $user Initiator or its guid, or array of them.
556
     * @param BaseUserModel|string|array $other Recipient or its guid, or array of them.
557
     * @return integer The number of relations removed.
558
     */
559 15
    public static function removeAllRelations($user, $other)
560
    {
561 15
        $rni = static::buildNoInitModel();
562 15
        $createdByAttribute = $rni->createdByAttribute;
563 15
        $otherGuidAttribute = $rni->otherGuidAttribute;
564 15
        return static::deleteAll([$createdByAttribute => BaseUserModel::compositeGUIDs($user), $otherGuidAttribute => BaseUserModel::compositeGUIDs($other)]);
565
    }
566
567
    /**
568
     * Get first relation between initiator(s) and recipient(s).
569
     * @param BaseUserModel|string|array $user Initiator or its guid, or array of them.
570
     * @param BaseUserModel|string|array $other Recipient or its guid, or array of them.
571
     * @return static
572
     */
573 4
    public static function findOneRelation($user, $other)
574
    {
575 4
        return static::find()->initiators($user)->recipients($other)->one();
576
    }
577
578
    /**
579
     * Get first opposite relation between initiator(s) and recipient(s).
580
     * @param BaseUserModel|string $user Initiator or its guid, or array of them.
581
     * @param BaseUserModel|string $other Recipient or its guid, or array of them.
582
     * @return static
583
     */
584 1
    public static function findOneOppositeRelation($user, $other)
585
    {
586 1
        return static::find()->initiators($other)->recipients($user)->one();
587
    }
588
589
    /**
590
     * Get user's or users' all relations, or by specified groups.
591
     * @param BaseUserModel|string|array $user Initiator or its GUID, or Initiators or their GUIDs.
592
     * @param BaseUserRelationGroupModel|string|array|null $groups UserRelationGroup
593
     * or its guid, or array of them. If you do not want to delimit the groups, please assign null.
594
     * @return array all eligible relations
595
     */
596 1
    public static function findOnesAllRelations($user, $groups = null)
597
    {
598 1
        return static::find()->initiators($user)->groups($groups)->all();
599
    }
600
601
    /**
602
     * Initialize groups attribute.
603
     * @param ModelEvent $event
604
     */
605 33
    public function onInitGroups($event)
606
    {
607 33
        $sender = $event->sender;
608 33
        $sender->removeAllGroups();
609 33
    }
610
611
    /**
612
     * Initialize remark attribute.
613
     * @param ModelEvent $event
614
     */
615 33
    public function onInitRemark($event)
616
    {
617 33
        $sender = $event->sender;
618 33
        $remarkAttribute = $sender->remarkAttribute;
619 33
        (is_string($remarkAttribute) && !empty($remarkAttribute)) ? $sender->$remarkAttribute = '' : null;
620 33
    }
621
622
    /**
623
     * The event triggered after insert new relation.
624
     * The opposite relation should be inserted without triggering events
625
     * simultaneously after new relation inserted,
626
     * @param ModelEvent $event
627
     * @throws IntegrityException throw if insert failed.
628
     */
629 30
    public function onInsertRelation($event)
630
    {
631 30
        $sender = $event->sender;
632 30
        if ($sender->relationType == static::$relationMutual) {
633 12
            $opposite = static::buildOppositeRelation($sender);
634 12
            $opposite->off(static::EVENT_AFTER_INSERT, [$opposite, 'onInsertRelation']);
0 ignored issues
show
Bug introduced by
It seems like off() 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...
635 12
            if (!$opposite->save()) {
0 ignored issues
show
Bug introduced by
It seems like save() 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...
636
                $opposite->recordWarnings();
0 ignored issues
show
Bug introduced by
It seems like recordWarnings() 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...
637
                throw new IntegrityException('Reverse relation insert failed.');
638
            }
639 12
            $opposite->on(static::EVENT_AFTER_INSERT, [$opposite, '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...
640
        }
641 30
    }
642
643
    /**
644
     * The event triggered after update relation.
645
     * The opposite relation should be updated without triggering events
646
     * simultaneously after existed relation removed.
647
     * @param ModelEvent $event
648
     * @throw IntegrityException throw if update failed.
649
     */
650 4
    public function onUpdateRelation($event)
651
    {
652 4
        $sender = $event->sender;
653 4
        if ($sender->relationType == static::$relationMutual) {
654 2
            $opposite = static::buildOppositeRelation($sender);
655 2
            $opposite->off(static::EVENT_AFTER_UPDATE, [$opposite, 'onUpdateRelation']);
0 ignored issues
show
Bug introduced by
It seems like off() 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...
656 2
            if (!$opposite->save()) {
0 ignored issues
show
Bug introduced by
It seems like save() 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...
657
                $opposite->recordWarnings();
0 ignored issues
show
Bug introduced by
It seems like recordWarnings() 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...
658
                throw new IntegrityException('Reverse relation update failed.');
659
            }
660 2
            $opposite->on(static::EVENT_AFTER_UPDATE, [$opposite, '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...
661
        }
662 4
    }
663
664
    /**
665
     * The event triggered after delete relation.
666
     * The opposite relation should be deleted without triggering events
667
     * simultaneously after existed relation removed.
668
     * @param ModelEvent $event
669
     */
670 33
    public function onDeleteRelation($event)
671
    {
672 33
        $sender = $event->sender;
673 33
        if ($sender->relationType == static::$relationMutual) {
674 14
            $createdByAttribute = $sender->createdByAttribute;
675 14
            $otherGuidAttribute = $sender->otherGuidAttribute;
676 14
            $sender->off(static::EVENT_AFTER_DELETE, [$sender, 'onDeleteRelation']);
677 14
            static::removeAllRelations($sender->$otherGuidAttribute, $sender->$createdByAttribute);
678 14
            $sender->on(static::EVENT_AFTER_DELETE, [$sender, 'onDeleteRelation']);
679
        }
680 33
    }
681
}
682