Test Failed
Push — master ( 5196a6...aaaa1a )
by vistart
08:44
created

BlameableTrait   D

Complexity

Total Complexity 121

Size/Duplication

Total Lines 806
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 91.92%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 121
c 1
b 0
f 1
lcom 1
cbo 9
dl 0
loc 806
ccs 239
cts 260
cp 0.9192
rs 4.4444

39 Methods

Rating   Name   Duplication   Size   Complexity  
A rules() 0 4 1
A behaviors() 0 4 1
A countOfOwner() 0 5 1
A getContent() 0 15 4
A setContent() 0 14 4
A getBlameableRulesCacheKey() 0 4 1
A getBlameableRulesCacheTag() 0 4 1
B getBlameableRules() 0 26 5
A getContentCanBeEdited() 0 7 2
D getBlameableAttributeRules() 0 32 9
A getDescriptionRules() 0 16 3
C getContentRules() 0 31 8
A setBlameableRules() 0 9 2
A getBlameableBehaviorsCacheKey() 0 4 1
A getBlameableBehaviorsCacheTag() 0 4 1
B getBlameableBehaviors() 0 22 5
A setBlameableBehaviors() 0 10 2
A getDescription() 0 5 2
A setDescription() 0 5 2
A getUser() 0 4 1
hasOne() 0 1 ?
A getHost() 0 6 1
B setHost() 0 13 6
A setUser() 0 4 1
A getUpdater() 0 10 3
B setUpdater() 0 16 8
A onContentChanged() 0 6 1
A onGetCurrentUserGuid() 0 13 3
A onInitDescription() 0 12 4
on() 0 1 ?
off() 0 1 ?
B initBlameableEvents() 0 15 7
D enabledFields() 0 27 11
A findAllByIdentityInBatch() 0 7 2
A findOneById() 0 12 4
A countByIdentity() 0 4 1
A getPagination() 0 9 2
B getIdRules() 0 9 5
B onInitContentType() 0 14 6

How to fix   Complexity   

Complex Class

Complex classes like BlameableTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use BlameableTrait, and based on these observations, apply Extract Interface, too.

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\queries\BaseUserQuery;
17
use yii\base\InvalidParamException;
18
use yii\base\ModelEvent;
19
use yii\base\NotSupportedException;
20
use yii\behaviors\BlameableBehavior;
21
use yii\caching\TagDependency;
22
use yii\data\Pagination;
23
24
/**
25
 * This trait is used for building blameable model. It contains following features:
26
 * 1.Single-column(field) content;
27
 * 2.Content type;
28
 * 3.Content rules(generated automatically);
29
 * 4.Creator(owner)'s GUID;
30
 * 5.Updater's GUID;
31
 * 6.Confirmation features, provided by [[ConfirmationTrait]];
32
 * 7.Self referenced features, provided by [[SelfBlameableTrait]];
33
 * @property-read array $blameableAttributeRules Get all rules associated with
34
 * blameable.
35
 * @property array $blameableRules Get or set all the rules associated with
36
 * creator, updater, content and its ID, as well as all the inherited rules.
37
 * @property array $blameableBehaviors Get or set all the behaviors assoriated
38
 * with creator and updater, as well as all the inherited behaviors.
39
 * @property-read array $descriptionRules Get description property rules.
40
 * @property-read mixed $content Content.
41
 * @property-read boolean $contentCanBeEdited Whether this content could be edited.
42
 * @property-read array $contentRules Get content rules.
43
 * @property BserUserModel $host The owner of this model.
44
 * @property BaseUserModel $user The owner of this model(the same as $host).
45
 * @property BaseUserModel $updater The updater who updated this model latest.
46
 * @version 1.0
47
 * @author vistart <[email protected]>
48
 */
49
trait BlameableTrait
50
{
51
    use ConfirmationTrait,
52
        SelfBlameableTrait;
53
54
    private $blameableLocalRules = [];
55
    private $blameableLocalBehaviors = [];
56
57
    /**
58
     * @var boolean|string|array Specify the attribute(s) name of content(s). If
59
     * there is only one content attribute, you can assign its name. Or there
60
     * is multiple attributes associated with contents, you can assign their
61
     * names in array. If you don't want to use this feature, please assign
62
     * false.
63
     * For example:
64
     * ```php
65
     * public $contentAttribute = 'comment'; // only one field named as 'comment'.
66
     * ```
67
     * or
68
     * ```php
69
     * public $contentAttribute = ['year', 'month', 'day']; // multiple fields.
70
     * ```
71
     * or
72
     * ```php
73
     * public $contentAttribute = false; // no need of this feature.
74
     * ```
75
     * If you don't need this feature, you should add rules corresponding with
76
     * `content` in `rules()` method of your user model by yourself.
77
     */
78
    public $contentAttribute = 'content';
79
80
    /**
81
     * @var array built-in validator name or validatation method name and
82
     * additional parameters.
83
     */
84
    public $contentAttributeRule = ['string', 'max' => 255];
85
86
    /**
87
     * @var boolean|string Specify the field which stores the type of content.
88
     */
89
    public $contentTypeAttribute = false;
90
91
    /**
92
     * @var boolean|array Specify the logic type of content, not data type. If
93
     * your content doesn't need this feature. please specify false. If the
94
     * $contentAttribute is specified to false, this attribute will be skipped.
95
     * ```php
96
     * public $contentTypes = [
97
     *     'public',
98
     *     'private',
99
     *     'friend',
100
     * ];
101
     * ```
102
     */
103
    public $contentTypes = false;
104
105
    /**
106
     * @var boolean|string This attribute speicfy the name of description
107
     * attribute. If this attribute is assigned to false, this feature will be
108
     * skipped.
109
     */
110
    public $descriptionAttribute = false;
111
112
    /**
113
     * @var string
114
     */
115
    public $initDescription = '';
116
117
    /**
118
     * @var string the attribute that will receive current user ID value. This
119
     * attribute must be assigned.
120
     */
121
    public $createdByAttribute = "user_guid";
122
123
    /**
124
     * @var string the attribute that will receive current user ID value.
125
     * Set this property to false if you do not want to record the updater ID.
126
     */
127
    public $updatedByAttribute = "user_guid";
128
129
    /**
130
     * @var boolean Add combinated unique rule if assigned to true.
131
     */
132
    public $idCreatorCombinatedUnique = true;
133
134
    /**
135
     * @var boolean|string The name of user class which own the current entity.
136
     * If this attribute is assigned to false, this feature will be skipped, and
137
     * when you use create() method of UserTrait, it will be assigned with
138
     * current user class.
139
     */
140
    //public $userClass;
141
    
142
    /**
143
     * @var boolean|string Host class.
144
     */
145
    public $hostClass;
146
    public static $cacheKeyBlameableRules = 'blameable_rules';
147
    public static $cacheTagBlameableRules = 'tag_blameable_rules';
148
    public static $cacheKeyBlameableBehaviors = 'blameable_behaviors';
149
    public static $cacheTagBlameableBehaviors = 'tag_blameable_behaviors';
150
151
    /**
152
     * @inheritdoc
153
     * ------------
154
     * The classical rules is like following:
155
     * [
156
     *     ['guid', 'required'],
157
     *     ['guid', 'unique'],
158
     *     ['guid', 'string', 'max' => 36],
159
     *
160
     *     ['id', 'required'],
161
     *     ['id', 'unique'],
162
     *     ['id', 'string', 'max' => 4],
163
     *
164
     *     ['created_at', 'safe'],
165
     *     ['updated_at', 'safe'],
166
     *
167
     *     ['ip_type', 'in', 'range' => [4, 6]],
168
     *     ['ip', 'number', 'integerOnly' => true, 'min' => 0],
169
     * ]
170
     * @return array
171
     */
172 199
    public function rules()
173
    {
174 199
        return $this->getBlameableRules();
175
    }
176
177
    /**
178
     * @inheritdoc
179
     */
180 207
    public function behaviors()
181
    {
182 207
        return $this->getBlameableBehaviors();
183
    }
184
185
    /**
186
     * Get total of contents which owned by their owner.
187
     * @return integer
188
     */
189 1
    public function countOfOwner()
190
    {
191 1
        $createdByAttribute = $this->createdByAttribute;
192 1
        return static::find()->where([$createdByAttribute => $this->$createdByAttribute])->count();
193
    }
194
195
    /**
196
     * Get content.
197
     * @return mixed
198
     */
199 7
    public function getContent()
200
    {
201 7
        $contentAttribute = $this->contentAttribute;
202 7
        if ($contentAttribute === false) {
203
            return null;
204
        }
205 7
        if (is_array($contentAttribute)) {
206
            $content = [];
207
            foreach ($contentAttribute as $key => $value) {
208
                $content[$key] = $this->$value;
209
            }
210
            return $content;
211
        }
212 7
        return $this->$contentAttribute;
213
    }
214
215
    /**
216
     * Set content.
217
     * @param mixed $content
218
     */
219 65
    public function setContent($content)
220
    {
221 65
        $contentAttribute = $this->contentAttribute;
222 65
        if ($contentAttribute === false) {
223
            return;
224
        }
225 65
        if (is_array($contentAttribute)) {
226
            foreach ($contentAttribute as $key => $value) {
227
                $this->$value = $content[$key];
228
            }
229
            return;
230
        }
231 65
        $this->$contentAttribute = $content;
232 65
    }
233
234
    /**
235
     * Determines whether content could be edited. Your should implement this
236
     * method by yourself.
237
     * @return boolean
238
     * @throws NotSupportedException
239
     */
240 1
    public function getContentCanBeEdited()
241
    {
242 1
        if ($this->contentAttribute === false) {
243
            return false;
244
        }
245 1
        throw new NotSupportedException("This method is not implemented.");
246
    }
247
248
    /**
249
     * Get blameable rules cache key.
250
     * @return string cache key.
251
     */
252 199
    public function getBlameableRulesCacheKey()
253
    {
254 199
        return static::class . $this->cachePrefix . static::$cacheKeyBlameableRules;
0 ignored issues
show
Bug introduced by
The property cachePrefix 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...
255
    }
256
257
    /**
258
     * Get blameable rules cache tag.
259
     * @return string cache tag
260
     */
261 199
    public function getBlameableRulesCacheTag()
262
    {
263 199
        return static::class . $this->cachePrefix . static::$cacheTagBlameableRules;
264
    }
265
266
    /**
267
     * Get the rules associated with content to be blamed.
268
     * @return array rules.
269
     */
270 199
    public function getBlameableRules()
271
    {
272 199
        $cache = $this->getCache();
0 ignored issues
show
Bug introduced by
It seems like getCache() 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...
273 199
        if ($cache) {
274 199
            $this->blameableLocalRules = $cache->get($this->getBlameableRulesCacheKey());
275
        }
276
        // 若当前规则不为空,且是数组,则认为是规则数组,直接返回。
277 199
        if (!empty($this->blameableLocalRules) && is_array($this->blameableLocalRules)) {
278 78
            return $this->blameableLocalRules;
279
        }
280
281
        // 父类规则与确认规则合并。
282 199
        if ($cache) {
283 199
            TagDependency::invalidate($cache, [$this->getEntityRulesCacheTag()]);
0 ignored issues
show
Bug introduced by
It seems like getEntityRulesCacheTag() 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...
284
        }
285 199
        $rules = array_merge(
286 199
            parent::rules(),
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (rules() instead of getBlameableRules()). Are you sure this is correct? If so, you might want to change this to $this->rules().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
287 199
            $this->getConfirmationRules(),
288 199
            $this->getBlameableAttributeRules(),
289 199
            $this->getDescriptionRules(),
290 199
            $this->getContentRules(),
291 199
            $this->getSelfBlameableRules()
292
        );
293 199
        $this->setBlameableRules($rules);
294 199
        return $this->blameableLocalRules;
295
    }
296
297
    /**
298
     * Get the rules associated with `createdByAttribute`, `updatedByAttribute`
299
     * and `idAttribute`-`createdByAttribute` combination unique.
300
     * @return array rules.
301
     */
302 199
    public function getBlameableAttributeRules()
303
    {
304 199
        $rules = [];
305
        // 创建者和上次修改者由 BlameableBehavior 负责,因此标记为安全。
306 199
        if (!is_string($this->createdByAttribute) || empty($this->createdByAttribute)) {
307
            throw new NotSupportedException('You must assign the creator.');
308
        }
309 199
        if ($this->guidAttribute != $this->createdByAttribute) {
0 ignored issues
show
Bug introduced by
The property guidAttribute 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...
310 199
            $rules[] = [
311 199
                [$this->createdByAttribute],
312 199
                'safe',
313
            ];
314
        }
315
316 199
        if (is_string($this->updatedByAttribute) && $this->guidAttribute != $this->updatedByAttribute && !empty($this->updatedByAttribute)) {
317 111
            $rules[] = [
318 111
                [$this->updatedByAttribute],
319 111
                'safe',
320
            ];
321
        }
322
323 199
        if ($this->idCreatorCombinatedUnique && is_string($this->idAttribute)) {
0 ignored issues
show
Bug introduced by
The property idAttribute does not seem to exist. Did you mean refIdAttribute?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
324 197
            $rules ['id'] = [
325 197
                [$this->idAttribute,
0 ignored issues
show
Bug introduced by
The property idAttribute does not seem to exist. Did you mean refIdAttribute?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
326 197
                    $this->createdByAttribute],
327 197
                'unique',
328 197
                'targetAttribute' => [$this->idAttribute,
0 ignored issues
show
Bug introduced by
The property idAttribute does not seem to exist. Did you mean refIdAttribute?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
329 197
                    $this->createdByAttribute],
330
            ];
331
        }
332 199
        return $rules;
333
    }
334
    
335 199
    public function getIdRules()
336
    {
337 199
        if (is_string($this->idAttribute) && !empty($this->idAttribute) && $this->idCreatorCombinatedUnique && $this->idAttributeType !== static::$idTypeAutoIncrement) {
0 ignored issues
show
Bug introduced by
The property idAttribute does not seem to exist. Did you mean refIdAttribute?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
Bug introduced by
The property idAttributeType 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...
338
            return [
339 139
                [[$this->idAttribute], 'required'],
0 ignored issues
show
Bug introduced by
The property idAttribute does not seem to exist. Did you mean refIdAttribute?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
340
            ];
341
        }
342 106
        return parent::getIdRules();
343
    }
344
345
    /**
346
     * Get the rules associated with `description` attribute.
347
     * @return array rules.
348
     */
349 199
    public function getDescriptionRules()
350
    {
351 199
        $rules = [];
352 199
        if (is_string($this->descriptionAttribute) && !empty($this->descriptionAttribute)) {
353 66
            $rules[] = [
354 66
                [$this->descriptionAttribute],
355 66
                'string'
356
            ];
357 66
            $rules[] = [
358 66
                [$this->descriptionAttribute],
359 66
                'default',
360 66
                'value' => $this->initDescription,
361
            ];
362
        }
363 199
        return $rules;
364
    }
365
366
    /**
367
     * Get the rules associated with `content` and `contentType` attributes.
368
     * @return array rules.
369
     */
370 199
    public function getContentRules()
371
    {
372 199
        if (!$this->contentAttribute) {
373 46
            return [];
374
        }
375 163
        $rules = [];
376 163
        $rules[] = [$this->contentAttribute, 'required'];
377 163
        if ($this->contentAttributeRule) {
378 163
            if (is_string($this->contentAttributeRule)) {
379
                $this->contentAttributeRule = [$this->contentAttributeRule];
380
            }
381 163
            if (is_array($this->contentAttributeRule)) {
382 163
                $rules[] = array_merge([$this->contentAttribute], $this->contentAttributeRule);
383
            }
384
        }
385
386 163
        if (!$this->contentTypeAttribute) {
387 143
            return $rules;
388
        }
389
390 20
        if (is_array($this->contentTypes) && !empty($this->contentTypes)) {
391 20
            $rules[] = [[
392 20
                $this->contentTypeAttribute],
393 20
                'required'];
394 20
            $rules[] = [[
395 20
                $this->contentTypeAttribute],
396 20
                'in',
397 20
                'range' => array_keys($this->contentTypes)];
398
        }
399 20
        return $rules;
400
    }
401
402
    /**
403
     * Set blameable rules.
404
     * @param array $rules
405
     */
406 199
    protected function setBlameableRules($rules = [])
407
    {
408 199
        $this->blameableLocalRules = $rules;
409 199
        $cache = $this->getCache();
0 ignored issues
show
Bug introduced by
It seems like getCache() 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...
410 199
        if ($cache) {
411 199
            $tagDependency = new TagDependency(['tags' => [$this->getBlameableRulesCacheTag()]]);
412 199
            $cache->set($this->getBlameableRulesCacheKey(), $rules, 0, $tagDependency);
413
        }
414 199
    }
415
416
    /**
417
     * Get blameable behaviors cache key.
418
     * @return string cache key.
419
     */
420 207
    public function getBlameableBehaviorsCacheKey()
421
    {
422 207
        return static::class . $this->cachePrefix . static::$cacheKeyBlameableBehaviors;
423
    }
424
425
    /**
426
     * Get blameable behaviors cache tag.
427
     * @return string cache tag.
428
     */
429 207
    public function getBlameableBehaviorsCacheTag()
430
    {
431 207
        return static::class . $this->cachePrefix . static::$cacheTagBlameableBehaviors;
432
    }
433
434
    /**
435
     * Get blameable behaviors. If current behaviors array is empty, the init
436
     * array will be given.
437
     * @return array
438
     */
439 207
    public function getBlameableBehaviors()
440
    {
441 207
        $cache = $this->getCache();
0 ignored issues
show
Bug introduced by
It seems like getCache() 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...
442 207
        if ($cache) {
443 207
            $this->blameableLocalBehaviors = $cache->get($this->getBlameableBehaviorsCacheKey());
444
        }
445 207
        if (empty($this->blameableLocalBehaviors) || !is_array($this->blameableLocalBehaviors)) {
446 207
            if ($cache) {
447 207
                TagDependency::invalidate($cache, [$this->getEntityBehaviorsCacheTag()]);
0 ignored issues
show
Bug introduced by
The method getEntityBehaviorsCacheTag() does not exist on rhosocial\base\models\traits\BlameableTrait. Did you maybe mean behaviors()?

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...
448
            }
449 207
            $behaviors = parent::behaviors();
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (behaviors() instead of getBlameableBehaviors()). Are you sure this is correct? If so, you might want to change this to $this->behaviors().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
450 207
            $behaviors['blameable'] = [
451 207
                'class' => BlameableBehavior::class,
452 207
                'createdByAttribute' => $this->createdByAttribute,
453 207
                'updatedByAttribute' => $this->updatedByAttribute,
454 207
                'value' => [$this,
455 207
                    'onGetCurrentUserGuid'],
456
            ];
457 207
            $this->setBlameableBehaviors($behaviors);
458
        }
459 207
        return $this->blameableLocalBehaviors;
460
    }
461
462
    /**
463
     * Set blameable behaviors.
464
     * @param array $behaviors
465
     */
466 207
    protected function setBlameableBehaviors($behaviors = [])
467
    {
468 207
        $this->blameableLocalBehaviors = $behaviors;
469 207
        $cache = $this->getCache();
0 ignored issues
show
Bug introduced by
It seems like getCache() 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...
470 207
        if ($cache) {
471 207
            $tagDependencyConfig = ['tags' => [$this->getBlameableBehaviorsCacheTag()]];
472 207
            $tagDependency = new TagDependency($tagDependencyConfig);
473 207
            $cache->set($this->getBlameableBehaviorsCacheKey(), $behaviors, 0, $tagDependency);
474
        }
475 207
    }
476
477
    /**
478
     * Set description.
479
     * @return string description.
480
     */
481 1
    public function getDescription()
482
    {
483 1
        $descAttribute = $this->descriptionAttribute;
484 1
        return is_string($descAttribute) ? $this->$descAttribute : null;
485
    }
486
487
    /**
488
     * Get description.
489
     * @param string $desc description.
490
     * @return string|null description if enabled, or null if disabled.
491
     */
492 1
    public function setDescription($desc)
493
    {
494 1
        $descAttribute = $this->descriptionAttribute;
495 1
        return is_string($descAttribute) ? $this->$descAttribute = $desc : null;
496
    }
497
498
    /**
499
     * Get blame who owned this blameable model.
500
     * NOTICE! This method will not check whether `$hostClass` exists. You should
501
     * specify it in `init()` method.
502
     * @return BaseUserQuery user.
503
     */
504 18
    public function getUser()
505
    {
506 18
        return $this->getHost();
507
    }
508
509
    /**
510
     * Declares a `has-one` relation.
511
     * The declaration is returned in terms of a relational [[\yii\db\ActiveQuery]] instance
512
     * through which the related record can be queried and retrieved back.
513
     *
514
     * A `has-one` relation means that there is at most one related record matching
515
     * the criteria set by this relation, e.g., a customer has one country.
516
     *
517
     * For example, to declare the `country` relation for `Customer` class, we can write
518
     * the following code in the `Customer` class:
519
     *
520
     * ```php
521
     * public function getCountry()
522
     * {
523
     *     return $this->hasOne(Country::className(), ['id' => 'country_id']);
524
     * }
525
     * ```
526
     *
527
     * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
528
     * in the related class `Country`, while the 'country_id' value refers to an attribute name
529
     * in the current AR class.
530
     *
531
     * Call methods declared in [[\yii\db\ActiveQuery]] to further customize the relation.
532
     *
533
     * This method is provided by [[\yii\db\BaseActiveRecord]].
534
     * @param string $class the class name of the related record
535
     * @param array $link the primary-foreign key constraint. The keys of the array refer to
536
     * the attributes of the record associated with the `$class` model, while the values of the
537
     * array refer to the corresponding attributes in **this** AR class.
538
     * @return \yii\dbActiveQueryInterface the relational query object.
539
     */
540
    public abstract function hasOne($class, $link);
541
    
542
    /**
543
     * Get host of this model.
544
     * @return BaseUserQuery
545
     */
546 35
    public function getHost()
547
    {
548 35
        $hostClass = $this->hostClass;
549 35
        $model = $hostClass::buildNoInitModel();
550 35
        return $this->hasOne($hostClass::className(), [$model->guidAttribute => $this->createdByAttribute]);
551
    }
552
    
553
    /**
554
     * Set host of this model.
555
     * @param string $host
556
     * @return type
557
     */
558 148
    public function setHost($host)
559
    {
560 148
        if ($host instanceof $this->hostClass || $host instanceof \yii\web\IdentityInterface) {
561 109
            return $this->{$this->createdByAttribute} = $host->getGUID();
562
        }
563 50
        if (is_string($host) && preg_match(Number::GUID_REGEX, $host)) {
564 1
            return $this->{$this->createdByAttribute} = Number::guid_bin($host);
565
        }
566 50
        if (strlen($host) == 16) {
567 49
            return $this->{$this->createdByAttribute} = $host;
568
        }
569 1
        return false;
570
    }
571
    
572
    /**
573
     *
574
     * @param BaseUserModel|string $user
575
     * @return boolean
576
     */
577 4
    public function setUser($user)
578
    {
579 4
        return $this->setHost($user);
580
    }
581
582
    /**
583
     * Get updater who updated this blameable model recently.
584
     * NOTICE! This method will not check whether `$hostClass` exists. You should
585
     * specify it in `init()` method.
586
     * @return BaseUserQuery user.
587
     */
588 6
    public function getUpdater()
589
    {
590 6
        if (!is_string($this->updatedByAttribute) || empty($this->updatedByAttribute)) {
591 1
            return null;
592
        }
593 5
        $hostClass = $this->hostClass;
594 5
        $model = $hostClass::buildNoInitModel();
595
        /* @var $model BaseUserModel */
596 5
        return $this->hasOne($hostClass::className(), [$model->guidAttribute => $this->updatedByAttribute]);
597
    }
598
    
599
    /**
600
     *
601
     * @param BaseUserModel|string $user
0 ignored issues
show
Bug introduced by
There is no parameter named $user. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
602
     * @return boolean
603
     */
604 5
    public function setUpdater($updater)
605
    {
606 5
        if (!is_string($this->updatedByAttribute) || empty($this->updatedByAttribute)) {
607 1
            return false;
608
        }
609 4
        if ($updater instanceof $this->hostClass || $updater instanceof \yii\web\IdentityInterface) {
610 1
            return $this->{$this->updatedByAttribute} = $updater->getGUID();
611
        }
612 3
        if (is_string($updater) && preg_match(Number::GUID_REGEX, $updater)) {
613 1
            return $this->{$this->updatedByAttribute} = Number::guid_bin($updater);
614
        }
615 2
        if (strlen($updater) == 16) {
616 1
            return $this->{$this->updatedByAttribute} = $updater;
617
        }
618 1
        return false;
619
    }
620
621
    /**
622
     * This event is triggered before the model update.
623
     * This method is ONLY used for being triggered by event. DO NOT call,
624
     * override or modify it directly, unless you know the consequences.
625
     * @param ModelEvent $event
626
     */
627 58
    public function onContentChanged($event)
628
    {
629 58
        $sender = $event->sender;
630
        /* @var $sender static */
631 58
        return $sender->resetConfirmation();
632
    }
633
634
    /**
635
     * Return the current user's GUID if current model doesn't specify the owner
636
     * yet, or return the owner's GUID if current model has been specified.
637
     * This method is ONLY used for being triggered by event. DO NOT call,
638
     * override or modify it directly, unless you know the consequences.
639
     * @param ModelEvent $event
640
     * @return string the GUID of current user or the owner.
641
     */
642 141
    public function onGetCurrentUserGuid($event)
643
    {
644 141
        $sender = $event->sender;
645
        /* @var $sender static */
646 141
        if (isset($sender->attributes[$sender->createdByAttribute])) {
647 141
            return $sender->attributes[$sender->createdByAttribute];
0 ignored issues
show
Bug introduced by
The property attributes 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...
648
        }
649
        $identity = \Yii::$app->user->identity;
650
        /* @var $identity BaseUserModel */
651
        if ($identity) {
652
            return $identity->getGUID();
653
        }
654
    }
655
656
    /**
657
     * Initialize type of content. the first of element[index is 0] of
658
     * $contentTypes will be used.
659
     * @param ModelEvent $event
660
     */
661 24
    public function onInitContentType($event)
662
    {
663 24
        $sender = $event->sender;
664
        /* @var $sender static */
665 24
        if (!is_string($sender->contentTypeAttribute) || empty($sender->contentTypeAttribute)) {
666
            return;
667
        }
668 24
        $contentTypeAttribute = $sender->contentTypeAttribute;
669 24
        if (!isset($sender->$contentTypeAttribute) &&
670
            !empty($sender->contentTypes) &&
671 24
            is_array($sender->contentTypes)) {
672 24
            $sender->$contentTypeAttribute = $sender->contentTypes[0];
673
        }
674 24
    }
675
676
    /**
677
     * Initialize description property with $initDescription.
678
     * @param ModelEvent $event
679
     */
680 73
    public function onInitDescription($event)
681
    {
682 73
        $sender = $event->sender;
683
        /* @var $sender static */
684 73
        if (!is_string($sender->descriptionAttribute) || empty($sender->descriptionAttribute)) {
685
            return;
686
        }
687 73
        $descriptionAttribute = $sender->descriptionAttribute;
688 73
        if (empty($sender->$descriptionAttribute)) {
689 73
            $sender->$descriptionAttribute = $sender->initDescription;
690
        }
691 73
    }
692
693
    /**
694
     * Attaches an event handler to an event.
695
     *
696
     * The event handler must be a valid PHP callback. The following are
697
     * some examples:
698
     *
699
     * ```
700
     * function ($event) { ... }         // anonymous function
701
     * [$object, 'handleClick']          // $object->handleClick()
702
     * ['Page', 'handleClick']           // Page::handleClick()
703
     * 'handleClick'                     // global function handleClick()
704
     * ```
705
     *
706
     * The event handler must be defined with the following signature,
707
     *
708
     * ```
709
     * function ($event)
710
     * ```
711
     *
712
     * where `$event` is an [[Event]] object which includes parameters associated with the event.
713
     *
714
     * This method is provided by [[\yii\base\Component]].
715
     * @param string $name the event name
716
     * @param callable $handler the event handler
717
     * @param mixed $data the data to be passed to the event handler when the event is triggered.
718
     * When the event handler is invoked, this data can be accessed via [[Event::data]].
719
     * @param boolean $append whether to append new event handler to the end of the existing
720
     * handler list. If false, the new handler will be inserted at the beginning of the existing
721
     * handler list.
722
     * @see off()
723
     */
724
    public abstract function on($name, $handler, $data = null, $append = true);
725
726
    /**
727
     * Detaches an existing event handler from this component.
728
     * This method is the opposite of [[on()]].
729
     * This method is provided by [[\yii\base\Component]]
730
     * @param string $name event name
731
     * @param callable $handler the event handler to be removed.
732
     * If it is null, all handlers attached to the named event will be removed.
733
     * @return boolean if a handler is found and detached
734
     * @see on()
735
     */
736
    public abstract function off($name, $handler = null);
737
738
    /**
739
     * Attach events associated with blameable model.
740
     */
741 207
    public function initBlameableEvents()
742
    {
743 207
        $this->on(static::$eventConfirmationChanged, [$this, "onConfirmationChanged"]);
744 207
        $this->on(static::$eventNewRecordCreated, [$this, "onInitConfirmation"]);
745 207
        $contentTypeAttribute = $this->contentTypeAttribute;
746 207
        if (is_string($contentTypeAttribute) && !empty($contentTypeAttribute) && !isset($this->$contentTypeAttribute)) {
747 24
            $this->on(static::$eventNewRecordCreated, [$this, "onInitContentType"]);
748
        }
749 207
        $descriptionAttribute = $this->descriptionAttribute;
750 207
        if (is_string($descriptionAttribute) && !empty($descriptionAttribute) && !isset($this->$descriptionAttribute)) {
751 73
            $this->on(static::$eventNewRecordCreated, [$this, 'onInitDescription']);
752
        }
753 207
        $this->on(static::EVENT_BEFORE_UPDATE, [$this, "onContentChanged"]);
754 207
        $this->initSelfBlameableEvents();
755 207
    }
756
757
    /**
758
     * @inheritdoc
759
     */
760 86
    public function enabledFields()
761
    {
762 86
        $fields = parent::enabledFields();
763 86
        if (is_string($this->createdByAttribute) && !empty($this->createdByAttribute)) {
764 86
            $fields[] = $this->createdByAttribute;
765
        }
766 86
        if (is_string($this->updatedByAttribute) && !empty($this->updatedByAttribute) &&
767 65
            $this->createdByAttribute != $this->updatedByAttribute) {
768
            $fields[] = $this->updatedByAttribute;
769
        }
770 86
        if (is_string($this->contentAttribute)) {
771 86
            $fields[] = $this->contentAttribute;
772
        }
773 86
        if (is_array($this->contentAttribute)) {
774
            $fields = array_merge($fields, $this->contentAttribute);
775
        }
776 86
        if (is_string($this->descriptionAttribute)) {
777 1
            $fields[] = $this->descriptionAttribute;
778
        }
779 86
        if (is_string($this->confirmationAttribute)) {
780 1
            $fields[] = $this->confirmationAttribute;
781
        }
782 86
        if (is_string($this->parentAttribute)) {
783 1
            $fields[] = $this->parentAttribute;
784
        }
785 86
        return $fields;
786
    }
787
788
    /**
789
     * Find all follows by specified identity. If `$identity` is null, the logged-in
790
     * identity will be taken.
791
     * @param string|integer $pageSize If it is 'all`, then will find all follows,
792
     * the `$currentPage` parameter will be skipped. If it is integer, it will be
793
     * regarded as sum of models in one page.
794
     * @param integer $currentPage The current page number, begun with 0.
795
     * @param mixed $identity It's type depends on {$this->hostClass}.
796
     * @return static[] If no follows, null will be given, or return follow array.
797
     */
798 1
    public static function findAllByIdentityInBatch($pageSize = 'all', $currentPage = 0, $identity = null)
799
    {
800 1
        if ($pageSize === 'all') {
801 1
            return static::findByIdentity($identity)->all();
802
        }
803 1
        return static::findByIdentity($identity)->page($pageSize, $currentPage)->all();
804
    }
805
806
    /**
807
     * Find one follow by specified identity. If `$identity` is null, the logged-in
808
     * identity will be taken. If $identity doesn't has the follower, null will
809
     * be given.
810
     * @param integer $id user id.
811
     * @param boolean $throwException
812
     * @param mixed $identity It's type depends on {$this->hostClass}.
813
     * @return static
814
     * @throws InvalidParamException
815
     */
816 1
    public static function findOneById($id, $throwException = true, $identity = null)
817
    {
818 1
        $query = static::findByIdentity($identity);
819 1
        if (!empty($id)) {
820 1
            $query = $query->id($id);
821
        }
822 1
        $model = $query->one();
823 1
        if (!$model && $throwException) {
824 1
            throw new InvalidParamException('Model Not Found.');
825
        }
826 1
        return $model;
827
    }
828
829
    /**
830
     * Get total of follows of specified identity.
831
     * @param mixed $identity It's type depends on {$this->hostClass}.
832
     * @return integer total.
833
     */
834 3
    public static function countByIdentity($identity = null)
835
    {
836 3
        return (int)(static::findByIdentity($identity)->count());
837
    }
838
839
    /**
840
     * Get pagination, used for building contents page by page.
841
     * @param integer $limit
842
     * @param mixed $identity It's type depends on {$this->hostClass}.
843
     * @return Pagination
844
     */
845 2
    public static function getPagination($limit = 10, $identity = null)
846
    {
847 2
        $limit = (int) $limit;
848 2
        $count = static::countByIdentity($identity);
849 2
        if ($limit > $count) {
850 2
            $limit = $count;
851
        }
852 2
        return new Pagination(['totalCount' => $count, 'pageSize' => $limit]);
853
    }
854
}
855