Completed
Push — master ( 0281fd...d368a1 )
by vistart
06:50
created

UserRelationTrait::findOneOppositeRelation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
crap 1
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
 *
50
 * @method {$this->multiBlamesClass} createGroup(BaseUserModel $user, array $config = [])
51
 * @method array|false addGroup({$this->multiBlamesClass} $blame)
52
 * @method array|false addOrCreateGroup(){$this->multiBlamesClass}|array &$blame = null, BaseUserModel $user = null)
53
 * @method array|false removeGroup({$this->multiBlamesClass}|string $blame)
54
 * @method array|false removeAllGroups()
55
 * @method {$this->multiBlamesClass} getGroup(string $blameGuid)
56
 * @method {$this->multiBlamesClass} getOrCreateGroup(string $blameGuid, BaseUserModel $user = null))
57
 * @method array getGroupMembers({$this->multiBlamesClass} $blame) Get all members that belongs to 
58
 * @method array getGroupGuids(bool $checkValid = false)
59
 * @method array|false setGroupGuids(array $guids = [], bool $checkValid = false)
60
 * @method array getOwnGroups() Get all groups that owned this relation.
61
 * @method array setOwnGroups(array $blames)
62
 * @method array isGroupContained({$this->multiBlamesClass} $blame)
63
 * @method array getAllGroups() Get all groups created by whom created this relation.
64
 * @method array getNonGroupMembers(BaseUserModel $user) Get members that do not belong to any group.
65
 * @method integer getGroupsCount() Get the count of groups of this relation.
66
 * @method array getEmptyGroups() Get the groups which does not contain any relations.
67
 * @method array getGroupsRules() Get rules associated with group attribute.
68
 *
69
 * @version 1.0
70
 * @author vistart <[email protected]>
71
 */
72
trait UserRelationTrait
73
{
74
    use mb,
75
        MutualTrait {
76
        mb::addBlame as addGroup;
77
        mb::createBlame as createGroup;
78
        mb::addOrCreateBlame as addOrCreateGroup;
79
        mb::removeBlame as removeGroup;
80
        mb::removeAllBlames as removeAllGroups;
81
        mb::getBlame as getGroup;
82
        mb::getOrCreateBlame as getOrCreateGroup;
83
        mb::getBlameds as getGroupMembers;
84
        mb::getBlameGuids as getGroupGuids;
85
        mb::setBlameGuids as setGroupGuids;
86
        mb::getOwnBlames as getOwnGroups;
87
        mb::setOwnBlames as setOwnGroups;
88
        mb::isBlameOwned as isGroupContained;
89
        mb::getAllBlames as getAllGroups;
90
        mb::getNonBlameds as getNonGroupMembers;
91
        mb::getBlamesCount as getGroupsCount;
92
        mb::getEmptyBlames as getEmptyGroups;
93
        mb::getMultipleBlameableAttributeRules as getGroupsRules;
94
    }
95
96
    /**
97
     * @var string
98
     */
99
    public $remarkAttribute = 'remark';
100
    public static $relationSingle = 0;
101
    public static $relationMutual = 1;
102
    public $relationType = 1;
103
    public static $relationTypes = [
104
        0 => 'Single',
105
        1 => 'Mutual',
106
    ];
107
108
    /**
109
     * @var string the attribute name of which determines the relation type.
110
     */
111
    public $mutualTypeAttribute = 'type';
112
    public static $mutualTypeNormal = 0x00;
113
    public static $mutualTypeSuspend = 0x01;
114
115
    /**
116
     * @var array Mutual types.
117
     */
118
    public static $mutualTypes = [
119
        0x00 => 'Normal',
120
        0x01 => 'Suspend',
121
    ];
122
123
    /**
124
     * @var string the attribute name of which determines the `favorite` field.
125
     */
126
    public $favoriteAttribute = 'favorite';
127
128
    /**
129
     * Permit to build self relation.
130
     * @var boolean 
131
     */
132
    public $relationSelf = false;
133
134
    /**
135
     * Get whether this relation is favorite or not.
136
     * @return boolean
137
     */
138 1
    public function getIsFavorite()
139
    {
140 1
        $favoriteAttribute = $this->favoriteAttribute;
141 1
        return (is_string($favoriteAttribute) && !empty($favoriteAttribute)) ? (int) $this->$favoriteAttribute > 0 : null;
142
    }
143
144
    /**
145
     * Set favorite.
146
     * @param boolean $fav
147
     */
148 1
    public function setIsFavorite($fav)
149
    {
150 1
        $favoriteAttribute = $this->favoriteAttribute;
151 1
        return (is_string($favoriteAttribute) && !empty($favoriteAttribute)) ? $this->$favoriteAttribute = ($fav ? 1 : 0) : null;
152
    }
153
154
    /**
155
     * @inheritdoc
156
     */
157 40
    public function rules()
158
    {
159 40
        return array_merge(parent::rules(), $this->getUserRelationRules());
160
    }
161
162
    /**
163
     * Validation rules associated with user relation.
164
     * @return array rules.
165
     */
166 40
    public function getUserRelationRules()
167
    {
168 40
        $rules = [];
169 40
        if ($this->relationType == static::$relationMutual) {
170
            $rules = [
171 12
                [[$this->mutualTypeAttribute], 'in', 'range' => array_keys(static::$mutualTypes)],
172 12
                [[$this->mutualTypeAttribute], 'default', 'value' => static::$mutualTypeNormal],
173
            ];
174
        }
175 40
        return array_merge($rules, $this->getRemarkRules(), $this->getFavoriteRules(), $this->getGroupsRules(), $this->getOtherGuidRules());
176
    }
177
178
    /**
179
     * Get remark.
180
     * @return string remark.
181
     */
182 1
    public function getRemark()
183
    {
184 1
        $remarkAttribute = $this->remarkAttribute;
185 1
        return (is_string($remarkAttribute) && !empty($remarkAttribute)) ? $this->$remarkAttribute : null;
186
    }
187
188
    /**
189
     * Set remark.
190
     * @param string $remark
191
     * @return string remark.
192
     */
193 1
    public function setRemark($remark)
194
    {
195 1
        $remarkAttribute = $this->remarkAttribute;
196 1
        return (is_string($remarkAttribute) && !empty($remarkAttribute)) ? $this->$remarkAttribute = $remark : null;
197
    }
198
199
    /**
200
     * Validation rules associated with remark attribute.
201
     * @return array rules.
202
     */
203 40
    public function getRemarkRules()
204
    {
205 40
        return is_string($this->remarkAttribute) ? [
206 40
            [[$this->remarkAttribute], 'string'],
207 40
            [[$this->remarkAttribute], 'default', 'value' => ''],
208 40
            ] : [];
209
    }
210
211
    /**
212
     * Validation rules associated with favorites attribute.
213
     * @return array rules.
214
     */
215 40
    public function getFavoriteRules()
216
    {
217 40
        return is_string($this->favoriteAttribute) ? [
218 40
            [[$this->favoriteAttribute], 'boolean'],
219 40
            [[$this->favoriteAttribute], 'default', 'value' => 0],
220 40
            ] : [];
221
    }
222
223
    /**
224
     * Validation rules associated with other guid attribute.
225
     * @return array rules.
226
     */
227 40
    public function getOtherGuidRules()
228
    {
229 40
        $rules = array_merge($this->getMutualRules(), [
230 40
            [[$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...
231
        ]);
232 40
        return $rules;
233
    }
234
235
    /**
236
     * Attach events associated with user relation.
237
     */
238 43
    public function initUserRelationEvents()
239
    {
240 43
        $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...
241 43
        $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...
242 43
        $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...
243 43
        $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...
244 43
        $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...
245 43
        $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...
246 43
        $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...
247 43
    }
248
249
    /**
250
     * Get opposite relation against self.
251
     * @return static
252
     */
253 1
    public function getOpposite()
254
    {
255 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...
256 1
            return null;
257
        }
258 1
        return static::find()->opposite($this->initiator, $this->recipient);
259
    }
260
261
    /**
262
     * Check whether the initiator is followed by recipient.
263
     * @param BaseUserModel $initiator
264
     * @param BaseUserModel $recipient
265
     * @return boolean
266
     */
267 6
    public static function isFollowed($initiator, $recipient)
268
    {
269 6
        return static::find()->initiators($recipient)->recipients($initiator)->exists();
270
    }
271
272
    /**
273
     * Check whether the initiator is following recipient.
274
     * @param BaseUserModel $initiator
275
     * @param BaseUserModel $recipient
276
     * @return boolean
277
     */
278 6
    public static function isFollowing($initiator, $recipient)
279
    {
280 6
        return static::find()->initiators($initiator)->recipients($recipient)->exists();
281
    }
282
283
    /**
284
     * Check whether the initiator is following and followed by recipient mutually (Single Relation).
285
     * Or check whether the initiator and recipient are friend whatever the mutual type is normal or suspend.
286
     * @param BaseUserModel $initiator
287
     * @param BaseUserModel $recipient
288
     * @return boolean
289
     */
290 3
    public static function isMutual($initiator, $recipient)
291
    {
292 3
        return static::isFollowed($initiator, $recipient) && static::isFollowing($initiator, $recipient);
293
    }
294
295
    /**
296
     * Check whether the initiator is following and followed by recipient mutually (Single Relation).
297
     * Or check whether the initiator and recipient are friend if the mutual type is normal.
298
     * @param BaseUserModel $initiator
299
     * @param BaseUserModel $recipient
300
     * @return boolean
301
     */
302 6
    public static function isFriend($initiator, $recipient)
303
    {
304 6
        $query = static::find();
305 6
        $model = $query->noInitModel;
306
        /* @var $model static */
307 6
        if ($model->relationType == static::$relationSingle) {
308 2
            return static::isMutual($initiator, $recipient);
309
        }
310 4
        if ($model->relationType == static::$relationMutual) {
311 4
            $relation = static::find()->initiators($initiator)->recipients($recipient)->andWhere([$model->mutualTypeAttribute => static::$mutualTypeNormal])->exists();
312 4
            $inverse = static::find()->recipients($initiator)->initiators($recipient)->andWhere([$model->mutualTypeAttribute => static::$mutualTypeNormal])->exists();
313 4
            return $relation && $inverse;
314
        }
315
        return false;
316
    }
317
318
    /**
319
     * Build new or return existed suspend mutual relation, or return null if
320
     * current type is not mutual.
321
     * @see buildRelation()
322
     * @param BaseUserModel|string $user Initiator or its GUID.
323
     * @param BaseUserModel|string $other Recipient or its GUID.
324
     * @return static The relation will be
325
     * given if exists, or return a new relation.
326
     */
327 15
    public static function buildSuspendRelation($user, $other)
328
    {
329 15
        $relation = static::buildRelation($user, $other);
330 15
        if (!$relation || $relation->relationType != static::$relationMutual) {
331 1
            return null;
332
        }
333 14
        $relation->setMutualType(static::$mutualTypeSuspend);
334 14
        return $relation;
335
    }
336
337
    /**
338
     * Build new or return existed normal relation.
339
     * The status of mutual relation will be changed to normal if it is not. 
340
     * @see buildRelation()
341
     * @param BaseUserModel|string $user Initiator or its GUID.
342
     * @param BaseUserModel|string $other Recipient or its GUID.
343
     * @return static The relation will be
344
     * given if exists, or return a new relation.
345
     */
346 43
    public static function buildNormalRelation($user, $other)
347
    {
348 43
        $relation = static::buildRelation($user, $other);
349 43
        if (!$relation) {
350 1
            return null;
351
        }
352 43
        if ($relation->relationType == static::$relationMutual) {
353 14
            $relation->setMutualType(static::$mutualTypeNormal);
354
        }
355 43
        return $relation;
356
    }
357
    
358
    /**
359
     * Transform relation from suspend to normal.
360
     * Note: You should ensure the relation model is not new one.
361
     * @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...
362
     * @return boolean
363
     */
364 2
    public static function transformSuspendToNormal($relation)
365
    {
366 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...
367 1
            return false;
368
        }
369 1
        $new = static::buildNormalRelation($relation->initiator, $relation->recipient);
370 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...
371
    }
372
    
373
    /**
374
     * Revert relation from normal to suspend.
375
     * Note: You should ensure the relation model is not new one.
376
     * @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...
377
     * @return boolean
378
     */
379 2
    public static function revertNormalToSuspend($relation)
380
    {
381 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...
382 1
            return false;
383
        }
384 1
        $new = static::buildSuspendRelation($relation->initiator, $relation->recipient);
385 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...
386
    }
387
388
    /**
389
     * Build new or return existed relation between initiator and recipient.
390
     * If relation between initiator and recipient is not found, new relation will
391
     * be built. If initiator and recipient are the same one and it is not allowed
392
     * to build self relation, null will be given.
393
     * If you want to know whether the relation exists, you can check the return
394
     * value of `getIsNewRecord()` method.
395
     * @param BaseUserModel|string $user Initiator or its GUID.
396
     * @param BaseUserModel|string $other Recipient or its GUID.
397
     * @return static The relation will be
398
     * given if exists, or return a new relation. Or return null if not allowed
399
     * to build self relation,
400
     */
401 43
    protected static function buildRelation($user, $other)
402
    {
403 43
        $relationQuery = static::find()->initiators($user)->recipients($other);
404 43
        $noInit = $relationQuery->noInitModel;
405 43
        $relation = $relationQuery->one();
406 43
        if (!$relation) {
407 43
            $hostClass = $noInit->hostClass;
408 43
            if ($user instanceof BaseUserModel) {
409 43
                $hostClass = $hostClass ? : $user->className();
0 ignored issues
show
Unused Code introduced by
$hostClass is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
410 43
                $user = $user->getGUID();
411
            }
412 43
            if ($other instanceof BaseUserModel) {
413 43
                $other = $other->getGUID();
414
            }
415 43
            if (!$noInit->relationSelf && $user == $other) {
416 1
                return null;
417
            }
418 43
            $relation = new static(['host' => $user, 'recipient' => $other]);
0 ignored issues
show
Unused Code introduced by
The call to UserRelationTrait::__construct() has too many arguments starting with array('host' => $user, 'recipient' => $other).

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