Completed
Push — master ( 76e0e0...e4688d )
by vistart
05:35
created

MultipleBlameableTrait::BlameOwned()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 4
cp 0
rs 9.2
c 0
b 0
f 0
cc 4
eloc 4
nc 2
nop 1
crap 20
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\helpers\Number;
16
use rhosocial\base\models\events\MultipleBlameableEvent;
17
use rhosocial\base\models\models\BaseUserModel;
18
use yii\base\ModelEvent;
19
use yii\base\InvalidCallException;
20
use yii\base\InvalidConfigException;
21
use yii\base\InvalidParamException;
22
23
/**
24
 * 一个模型的某个属性可能对应多个责任者,该 trait 用于处理此种情况。此种情况违反
25
 * 了关系型数据库第一范式,因此此 trait 只适用于责任者属性修改不频繁的场景,在开
26
 * 发时必须严格测试数据一致性,并同时考量性能。
27
 * Basic Principles:
28
 * <ol>
29
 * <li>when adding blame, it will check whether each of blames including to be
30
 * added is valid.
31
 * </li>
32
 * <li>when removing blame, as well as counting, getting or setting list of them,
33
 * it will also check whether each of blames is valid.
34
 * </li>
35
 * <li>By default, once blame was deleted, the guid of it is not removed from
36
 * list of blames immediately. It will check blame if valid when adding, removing,
37
 * counting, getting and setting it. You can define a blame model and attach it
38
 * events triggered when inserting, updating and deleting a blame, then disable
39
 * checking the validity of blames.
40
 * </li>
41
 * </ol>
42
 * Notice:
43
 * <ol>
44
 * <li>You must specify two properties: $multiBlamesClass and $multiBlamesAttribute.
45
 * <ul>
46
 * <li>$multiBlamesClass specify the class name of blame.</li>
47
 * <li>$multiBlamesAttribute specify the field name of blames.</li>
48
 * </ul>
49
 * </li>
50
 * <li>You should rename the following methods to be needed optionally.</li>
51
 * </ol>
52
 * @property-read array $multiBlamesAttributeRules
53
 * @property string[] $blameGuids
54
 * @property-read array $allBlames
55
 * @property-read array $nonBlameds
56
 * @property-read integer $blamesCount
57
 * @version 1.0
58
 * @author vistart <[email protected]>
59
 */
60
trait MultipleBlameableTrait
61
{
62
63
    /**
64
     * @var string class name of multiple blameable class.
65
     */
66
    public $multiBlamesClass = '';
67
68
    /**
69
     * @var string name of multiple blameable attribute.
70
     */
71
    public $multiBlamesAttribute = 'blames';
72
73
    /**
74
     * @var integer the limit of blames. it should be greater than or equal 1, and
75
     * less than or equal 10.
76
     */
77
    public $blamesLimit = 10;
78
79
    /**
80
     * @var boolean determines whether blames list has been changed.
81
     */
82
    public $blamesChanged = false;
83
84
    /**
85
     * @var string event name.
86
     */
87
    public static $eventMultipleBlamesChanged = 'multipleBlamesChanged';
88
89
    /**
90
     * Get the rules associated with multiple blameable attribute.
91
     * @return array rules.
92
     */
93 36
    public function getMultipleBlameableAttributeRules()
94
    {
95 36
        return is_string($this->multiBlamesAttribute) ? [
96 36
            [[$this->multiBlamesAttribute], 'string', 'max' => $this->blamesLimit * 16],
97 36
            [[$this->multiBlamesAttribute], 'default', 'value' => ''],
98 36
            ] : [];
99
    }
100
101
    /**
102
     * Add specified blame.
103
     * @param {$this->multiBlamesClass}|string $blame
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass}|string could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
104
     * @return false|array
105
     * @throws InvalidParamException if blame existed.
106
     * @throws InvalidCallException if blame limit reached.
107
     */
108 2
    public function addBlame($blame)
109
    {
110 2
        if (!is_string($this->multiBlamesAttribute)) {
111
            return false;
112
        }
113 2
        $blameGuid = '';
114 2
        if (is_string($blame)) {
115 1
            $blameGuid = $blame;
116
        }
117 2
        if ($blame instanceof $this->multiBlamesClass) {
118 1
            $blameGuid = $blame->getGUID();
119
        }
120 2
        $blameGuids = $this->getBlameGuids(true);
121 2
        if (array_search($blameGuid, $blameGuids)) {
122
            throw new InvalidParamException('the blame has existed.');
123
        }
124 2
        if ($this->getBlamesCount() >= $this->blamesLimit) {
125
            throw new InvalidCallException("the limit($this->blamesLimit) of blames has been reached.");
126
        }
127 2
        $blameGuids[] = $blameGuid;
128 2
        $this->setBlameGuids($blameGuids);
129 2
        return $this->getBlameGuids();
130
    }
131
132
    /**
133
     * Create blame.
134
     * @param BaseUserModel $user who will own this blame.
135
     * @param array $config blame class configuration array.
136
     * @return {$this->multiBlamesClass}
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass} could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
137
     */
138 4
    public static function createBlame($user, $config = [])
139
    {
140 4
        if (!($user instanceof BaseUserModel)) {
141 2
            $message = 'the type of user instance must be the extended class of BaseUserModel.';
142 2
            throw new InvalidParamException($message);
143
        }
144 3
        $mbClass = static::buildNoInitModel();
145 3
        $mbi = $mbClass->multiBlamesClass;
146 3
        return $user->create($mbi::className(), $config);
147
    }
148
149
    /**
150
     * Add specified blame, or create it before adding if doesn't exist.
151
     * But you should save the blame instance before adding it, or the operation
152
     * will fail.
153
     * @param {$this->multiBlamesClass}|string|array $blame
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass}|string|array could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
154
     * It will be regarded as blame's guid if it is a string. And assign the
155
     * reference parameter $blame the instance if it existed, or create one if not
156
     * found.
157
     * If it is {$this->multiBlamesClass} instance and existed, then will add it, or
158
     * false will be given if it is not found in database. So if you want to add
159
     * blame instance, you should save it before adding.
160
     * If it is a array, it will be regarded as configuration array of blame.
161
     * Notice! This parameter passed by reference, so it must be a variable!
162
     * @param BaseUserModel $user whose blame.
163
     * If null, it will take this blameable model's user.
164
     * @return false|array false if blame created failed or not enable this feature.
165
     * blames guid array if created and added successfully.
166
     * @throws InvalidConfigException
167
     * @throws InvalidParamException
168
     * @see addBlame()
169
     */
170 1
    public function addOrCreateBlame(&$blame = null, $user = null)
171
    {
172 1
        if (!is_string($this->multiBlamesClass)) {
173
            throw new InvalidConfigException('$multiBlamesClass must be specified if you want to use multiple blameable features.');
174
        }
175 1
        if (is_array($blame)) {
176 1
            if ($user == null) {
177 1
                $user = $this->host;
0 ignored issues
show
Bug introduced by
The property host 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...
178
            }
179 1
            $blame = static::getOrCreateBlame($blame, $user);
180 1
            if (!$blame->save()) {
181
                return false;
182
            }
183 1
            return $this->addBlame($blame->getGUID());
184
        }
185
        $blameGuid = '';
186
        if (is_string($blame)) {
187
            $blameGuid = $blame;
188
        }
189
        if ($blame instanceof $this->multiBlamesClass) {
190
            $blameGuid = $blame->getGUID();
191
        }
192
        if (($mbi = static::getBlame($blameGuid)) !== null) {
193
            return $this->addBlame($mbi);
194
        }
195
        return false;
196
    }
197
198
    /**
199
     * Remove specified blame.
200
     * @param {$this->multiBlamesClass} $blame
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass} could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
201
     * @return false|array all guids in json format.
202
     */
203
    public function removeBlame($blame)
204
    {
205
        if (!is_string($this->multiBlamesAttribute)) {
206
            return false;
207
        }
208
        $blameGuid = '';
209
        if (is_string($blame)) {
210
            $blameGuid = $blame;
211
        }
212
        if ($blame instanceof $this->multiBlamesClass) {
213
            $blameGuid = $blame->guid;
214
        }
215
        $blameGuids = $this->getBlameGuids(true);
216
        if (($key = array_search($blameGuid, $blameGuids)) !== false) {
217
            unset($blameGuids[$key]);
218
            $this->setBlameGuids($blameGuids);
219
        }
220
        return $this->getBlameGuids();
221
    }
222
223
    /**
224
     * Remove all blames.
225
     */
226 39
    public function removeAllBlames()
227
    {
228 39
        $this->setBlameGuids();
229 39
    }
230
231
    /**
232
     * Count the blames.
233
     * @return integer
234
     */
235 2
    public function getBlamesCount()
236
    {
237 2
        return count($this->getBlameGuids(true));
238
    }
239
240
    /**
241
     * Get the guid array of blames. it may check all guids if valid before return.
242
     * @param boolean $checkValid determines whether checking the blame is valid.
243
     * @return array all guids in json format.
244
     */
245 2
    public function getBlameGuids($checkValid = false)
246
    {
247 2
        $multiBlamesAttribute = $this->multiBlamesAttribute;
248 2
        if ($multiBlamesAttribute === false) {
249
            return [];
250
        }
251 2
        $guids = Number::divide_guid_bin($this->$multiBlamesAttribute);
252 2
        if ($checkValid) {
253 2
            $guids = $this->unsetInvalidBlames($guids);
254
        }
255 2
        return $guids;
256
    }
257
258
    /**
259
     * Event triggered when blames list changed.
260
     * @param MultipleBlameableEvent $event
261
     */
262 39
    public function onBlamesChanged($event)
263
    {
264 39
        $sender = $event->sender;
265 39
        $sender->blamesChanged = $event->blamesChanged;
266 39
    }
267
268
    /**
269
     * Remove invalid blame guid from provided guid array.
270
     * @param array $guids guid array of blames.
271
     * @return array guid array of blames unset invalid.
272
     */
273 39
    protected function unsetInvalidBlames($guids)
274
    {
275 39
        $unchecked = $guids;
276 39
        $multiBlamesClass = $this->multiBlamesClass;
277 39
        $mbi = $multiBlamesClass::buildNoInitModel();
278 39
        foreach ($guids as $key => $guid) {
279 2
            $blame = $multiBlamesClass::find()->where([$mbi->guidAttribute => $guid])->exists();
280 2
            if (!$blame) {
281 2
                unset($guids[$key]);
282
            }
283
        }
284 39
        $diff = array_diff($unchecked, $guids);
285 39
        $eventName = static::$eventMultipleBlamesChanged;
286 39
        $this->trigger($eventName, new MultipleBlameableEvent(['blamesChanged' => !empty($diff)]));
0 ignored issues
show
Bug introduced by
It seems like trigger() 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...
287 39
        return $guids;
288
    }
289
290
    /**
291
     * Set the guid array of blames, it may check all guids if valid.
292
     * @param array $guids guid array of blames.
293
     * @param boolean $checkValid determines whether checking the blame is valid.
294
     * @return false|array all guids.
295
     */
296 39
    public function setBlameGuids($guids = [], $checkValid = true)
297
    {
298 39
        if (!is_array($guids) || $this->multiBlamesAttribute === false) {
299
            return null;
300
        }
301 39
        if ($checkValid) {
302 39
            $guids = $this->unsetInvalidBlames($guids);
303
        }
304 39
        $multiBlamesAttribute = $this->multiBlamesAttribute;
305 39
        $this->$multiBlamesAttribute = Number::composite_guid($guids);
306 39
        return $guids;
307
    }
308
309
    /**
310
     * Get blame.
311
     * @param string $blameGuid
312
     * @return {$this->multiBlamesClass}
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass} could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
313
     */
314 1
    public static function getBlame($blameGuid)
315
    {
316 1
        $self = static::buildNoInitModel();
317 1
        if (empty($blameGuid) || empty($self->multiBlamesClass) || !is_string($self->multiBlamesClass) || $self->multiBlamesAttribute === false) {
318
            return null;
319
        }
320 1
        $mbClass = $self->multiBlamesClass;
321 1
        return $mbClass::findOne($blameGuid);
322
    }
323
    
324
    /**
325
     * Get all blames that owned this relation.
326
     * @return array
327
     */
328 2
    public function getOwnBlames()
329
    {
330 2
        $guids = $this->getBlameGuids(true);
331 2
        $class = $this->multiBlamesClass;
332 2
        return $class::findAll($guids);
333
    }
334
    
335
    /**
336
     * Set blames which would own this relation.
337
     * @param array $blames
338
     */
339
    public function setOwnBlames($blames)
340
    {
341
        $guids = static::compositeGUIDs($blames);
342
        return $this->setBlameGuids($guids, true);
343
    }
344
    
345
    /**
346
     * Check blame owned this.
347
     * @param {$this->multiBlamesClass} $blame
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass} could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
348
     * @return boolean
349
     */
350
    public function BlameOwned($blame)
351
    {
352
        if (!($blame instanceof $this->multiBlamesClass) || $blame->getIsNewRecord() || !static::getBlame($blame->getGUID())) {
353
            return false;
354
        }
355
        return array_search($blame->getGUID(), $this->getBlameGuids(true));
356
    }
357
358
    /**
359
     * Get or create blame.
360
     * @param string|array $blameGuid
361
     * @param BaseUserModel $user
362
     * @return {$this->multiBlamesClass}|null
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass}|null could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
363
     */
364 2
    public static function getOrCreateBlame($blameGuid, $user = null)
365
    {
366 2
        if (is_string($blameGuid)) {
367 1
            return static::getBlame($blameGuid);
368
        }
369 2
        if (is_array($blameGuid)) {
370 2
            return static::createBlame($user, $blameGuid);
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 364 can be null; however, rhosocial\base\models\tr...bleTrait::createBlame() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
371
        }
372 1
        return null;
373
    }
374
375
    /**
376
     * Get all ones to be blamed by `$blame`.
377
     * @param {$this->multiBlamesClass} $blame
0 ignored issues
show
Documentation introduced by
The doc-type {$this->multiBlamesClass} could not be parsed: Unknown type name "{$this-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
378
     * @return array
379
     */
380
    public static function getBlameds($blame)
381
    {
382
        $blameds = static::getBlame($blame->getGUID());
383
        if (empty($blameds)) {
384
            return null;
385
        }
386
        $noInit = static::buildNoInitModel();
387
        /* @var $noInit static */
388
        $createdByAttribute = $noInit->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...
389
        return static::find()->where([$createdByAttribute => $noInit->$createdByAttribute])
390
                ->andWhere(['like', $noInit->multiBlamesAttribute, $blame->getGUID()])->all();
391
    }
392
393
    /**
394
     * Get all the blames of record.
395
     * @param BaseUserModel $user
396
     * @return array all blames.
397
     */
398 1
    public static function getAllBlames($user)
399
    {
400 1
        $noInit = static::buildNoInitModel();
401 1
        if (empty($noInit->multiBlamesClass) ||
402 1
            !is_string($noInit->multiBlamesClass) ||
403 1
            $noInit->multiBlamesAttribute === false) {
404
            return null;
405
        }
406 1
        $multiBlamesClass = $noInit->multiBlamesClass;
407 1
        $createdByAttribute = $noInit->createdByAttribute;
408 1
        return $multiBlamesClass::findAll([$createdByAttribute => $user->getGUID()]);
409
    }
410
411
    /**
412
     * Get all records which without any blames.
413
     * @param BaseUserModel $user
414
     * @return array all non-blameds.
415
     */
416 1
    public static function getNonBlameds($user = null)
417
    {
418 1
        $noInit = static::buildNoInitModel();
419
        /* @var $noInit static */
420 1
        $createdByAttribute = $noInit->createdByAttribute;
421 1
        if (!$user) {
422
            $user = \Yii::$app->user->identity;
423
        }
424
        $cond = [
425 1
            $createdByAttribute => $user->getGUID(),
426 1
            $noInit->multiBlamesAttribute => ''
427
        ];
428 1
        return static::find()->where($cond)->all();
429
    }
430
431
    /**
432
     * Initialize blames limit.
433
     * @param ModelEvent $event
434
     */
435 39
    public function onInitBlamesLimit($event)
436
    {
437 39
        $sender = $event->sender;
438 39
        if (!is_int($sender->blamesLimit) || $sender->blamesLimit < 1 || $sender->blamesLimit > 64) {
439
            $sender->blamesLimit = 10;
440
        }
441 39
    }
442
}
443