Completed
Push — master ( a859d8...30587f )
by vistart
08:26
created

EntityTrait::behaviors()   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
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
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 Yii;
16
use yii\base\ModelEvent;
17
use yii\caching\Cache;
18
use yii\caching\TagDependency;
19
20
/**
21
 * This trait must be used in class extended from ActiveRecord. The ActiveRecord
22
 * supports [[\yii\db\ActiveRecord]], [[\yii\mongodb\ActiveRecord]], [[\yii\redis\ActiveRecord]].
23
 * @property array $entityRules
24
 * @property array $entityBehaviors
25
 * @version 1.0
26
 * @author vistart <[email protected]>
27
 */
28
trait EntityTrait
29
{
30
    use GUIDTrait, IDTrait, IPTrait, TimestampTrait, SubsidiaryTrait;
31
    
32
    private $entityLocalRules = [];
33
    private $entityLocalBehaviors = [];
34
35
    /**
36
     * @var string cache key and tag prefix. the prefix is usually set to full
37
     * qualified class name.
38
     */
39
    public $cachePrefix = '';
40
    public static $eventNewRecordCreated = 'newRecordCreated';
41
    public static $cacheKeyEntityRules = 'entity_rules';
42
    public static $cacheTagEntityRules = 'tag_entity_rules';
43
    public static $cacheKeyEntityBehaviors = 'entity_behaviors';
44
    public static $cacheTagEntityBehaviors = 'tag_entity_behaviors';
45
    
46
    /**
47
     * @var string cache component id.
48
     */
49
    public $cacheId = 'cache';
50
    
51
    /**
52
     * @var boolean Determines to skip initialization.
53
     */
54
    public $skipInit = false;
55
    
56
    /**
57
     * @var string the name of query class or sub-class.
58
     */
59
    public $queryClass;
60
    
61
    /**
62
     * @return \static New self without any initializations.
63
     */
64 360
    public static function buildNoInitModel()
65
    {
66 360
        return new static(['skipInit' => true]);
0 ignored issues
show
Unused Code introduced by
The call to EntityTrait::__construct() has too many arguments starting with array('skipInit' => true).

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...
67
    }
68
    
69
    /**
70
     * Populate and return the entity rules.
71
     * You should call this function in your extended class and merge the result
72
     * with your rules, instead of overriding it, unless you know the
73
     * consequences.
74
     * The classical rules are like following:
75
     * [
76
     *     ['guid', 'required'],
77
     *     ['guid', 'unique'],
78
     *     ['guid', 'string', 'max' => 36],
79
     *
80
     *     ['id', 'required'],
81
     *     ['id', 'unique'],
82
     *     ['id', 'string', 'max' => 4],
83
     *
84
     *     ['created_at', 'safe'],
85
     *     ['updated_at', 'safe'],
86
     *
87
     *     ['ip_type', 'in', 'range' => [4, 6]],
88
     *     ['ip', 'number', 'integerOnly' => true, 'min' => 0],
89
     * ]
90
     * @return array
91
     */
92 346
    public function rules()
93
    {
94 346
        return $this->getEntityRules();
95
    }
96
    
97
    /**
98
     * Populate and return the entity behaviors.
99
     * You should call this function in your extended class and merge the result
100
     * with your behaviors, instead of overriding it, unless you know the
101
     * consequences.
102
     * @return array
103
     */
104 371
    public function behaviors()
105
    {
106 371
        return $this->getEntityBehaviors();
107
    }
108
    
109
    /**
110
     * Get cache component. If cache component is not configured, Yii::$app->cache
111
     * will be given.
112
     * @return Cache cache component.
113
     */
114 371
    protected function getCache()
115
    {
116 371
        $cacheId = $this->cacheId;
117 371
        return empty($cacheId) ? Yii::$app->cache : Yii::$app->$cacheId;
118
    }
119
    
120
    /**
121
     * Get entity rules cache key.
122
     * @return string cache key.
123
     */
124 346
    public function getEntityRulesCacheKey()
125
    {
126 346
        return static::class . $this->cachePrefix . static::$cacheKeyEntityRules;
127
    }
128
    
129
    /**
130
     * Get entity rules cache tag.
131
     * @return string cache tag.
132
     */
133 346
    public function getEntityRulesCacheTag()
134
    {
135 346
        return static::class . $this->cachePrefix . static::$cacheTagEntityRules;
136
    }
137
    
138
    /**
139
     * Get entity rules.
140
     * @return array rules.
141
     */
142 346
    public function getEntityRules()
143
    {
144 346
        $cache = $this->getCache();
145 346
        if ($cache) {
146 346
            $this->entityLocalRules = $cache->get($this->getEntityRulesCacheKey());
0 ignored issues
show
Documentation Bug introduced by
It seems like $cache->get($this->getEntityRulesCacheKey()) of type * is incompatible with the declared type array of property $entityLocalRules.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
147
        }
148 346
        if (empty($this->entityLocalRules) || !is_array($this->entityLocalRules)) {
149 346
            $rules = array_merge($this->getGuidRules(), $this->getIdRules(), $this->getCreatedAtRules(), $this->getUpdatedAtRules(), $this->getExpiredAfterRules(), $this->getIpRules());
150 346
            $this->setEntityRules($rules);
151
        }
152 346
        return $this->entityLocalRules;
153
    }
154
    
155
    /**
156
     * Set entity rules.
157
     * @param array $rules
158
     */
159 346
    protected function setEntityRules($rules = [])
160
    {
161 346
        $this->entityLocalRules = $rules;
162 346
        $cache = $this->getCache();
163 346
        if ($cache) {
164 346
            $tagDependency = new TagDependency(
165 346
                ['tags' => [$this->getEntityRulesCacheTag()]]
166
            );
167 346
            $cache->set($this->getEntityRulesCacheKey(), $rules, 0, $tagDependency);
168
        }
169 346
    }
170
    
171
    /**
172
     * Get entity behaviors cache key.
173
     * @return string cache key.
174
     */
175 371
    public function getEntityBehaviorsCacheKey()
176
    {
177 371
        return static::class . $this->cachePrefix . static::$cacheKeyEntityBehaviors;
178
    }
179
    
180
    /**
181
     * Get entity behaviors cache tag.
182
     * @return string cache tag.
183
     */
184 371
    public function getEntityBehaviorsCacheTag()
185
    {
186 371
        return static::class . $this->cachePrefix . static::$cacheTagEntityBehaviors;
187
    }
188
    
189
    /**
190
     * Get the entity behaviors.
191
     * @return array
192
     */
193 371
    public function getEntityBehaviors()
194
    {
195 371
        $cache = $this->getCache();
196 371
        if ($cache) {
197 371
            $this->entityLocalBehaviors = $cache->get($this->getEntityBehaviorsCacheKey());
0 ignored issues
show
Documentation Bug introduced by
It seems like $cache->get($this->getEntityBehaviorsCacheKey()) of type * is incompatible with the declared type array of property $entityLocalBehaviors.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
198
        }
199 371
        if (empty($this->entityLocalBehaviors) || !is_array($this->entityLocalBehaviors)) {
200 371
            $this->setEntityBehaviors($this->getTimestampBehaviors());
201
        }
202 371
        return $this->entityLocalBehaviors;
203
    }
204
    
205
    /**
206
     * Set the entity behaviors.
207
     * @param array $behaviors
208
     */
209 371
    protected function setEntityBehaviors($behaviors)
210
    {
211 371
        $this->entityLocalBehaviors = $behaviors;
212 371
        $cache = $this->getCache();
213 371
        if ($cache) {
214 371
            $tagDependencyConfig = ['tags' => [$this->getEntityBehaviorsCacheTag()]];
215 371
            $tagDependency = new TagDependency($tagDependencyConfig);
216 371
            $cache->set($this->getEntityBehaviorsCacheKey(), $behaviors, 0, $tagDependency);
217
        }
218 371
    }
219
    
220
    /**
221
     * Reset cache key.
222
     * @param string $cacheKey
223
     * @param mixed $value
224
     * @return boolean whether the value is successfully stored into cache. if
225
     * cache component was not configured, then return false directly.
226
     */
227 1
    public function resetCacheKey($cacheKey, $value = false)
228
    {
229 1
        $cache = $this->getCache();
230 1
        if ($cache) {
231 1
            return $this->getCache()->set($cacheKey, $value);
232
        }
233
        return false;
234
    }
235
    
236
    /**
237
     * Attach events associated with entity model.
238
     */
239 371
    protected function initEntityEvents()
240
    {
241 371
        $this->on(static::EVENT_INIT, [$this, 'onInitCache']);
0 ignored issues
show
Documentation Bug introduced by
The method on does not exist on object<rhosocial\base\models\traits\EntityTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
242 371
        $this->attachInitGUIDEvent(static::$eventNewRecordCreated);
243 371
        $this->attachInitIDEvent(static::$eventNewRecordCreated);
244 371
        $this->attachInitIPEvent(static::$eventNewRecordCreated);
245 371
        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...
246 371
            $this->trigger(static::$eventNewRecordCreated);
0 ignored issues
show
Documentation Bug introduced by
The method trigger does not exist on object<rhosocial\base\models\traits\EntityTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
247
        }
248 371
        $this->on(static::EVENT_AFTER_FIND, [$this, 'onRemoveExpired']);
0 ignored issues
show
Documentation Bug introduced by
The method on does not exist on object<rhosocial\base\models\traits\EntityTrait>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
249 371
    }
250
    
251
    /**
252
     * Initialize the cache prefix.
253
     * @param ModelEvent $event
254
     */
255 371
    public function onInitCache($event)
256
    {
257 371
        $sender = $event->sender;
258 371
        $data = $event->data;
259 371
        if (isset($data['prefix'])) {
260
            $sender->cachePrefix = $data['prefix'];
261
        } else {
262 371
            $sender->cachePrefix = $sender::className();
263
        }
264 371
    }
265
    
266
    /**
267
     * Record warnings.
268
     */
269
    protected function recordWarnings()
270
    {
271
        if (YII_ENV !== YII_ENV_PROD || YII_DEBUG) {
272
            Yii::warning($this->errors);
0 ignored issues
show
Bug introduced by
The property errors 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...
273
        }
274
    }
275
    
276
    /**
277
     * Get guid or id. if neither disabled, return null.
278
     * @return string
279
     */
280 20
    public function __toString()
281
    {
282 20
        if (is_string($this->guidAttribute) && !empty($this->guidAttribute)) {
283 17
            return $this->getGUID();
284
        }
285 3
        if (is_string($this->idAttribute) && !empty($this->idAttribute)) {
286 3
            return $this->getId();
287
        }
288
        return parent::__toString();
289
    }
290
    
291
    /**
292
     * @inheritdoc
293
     * -------------
294
     * if enable `$idAttribute` and $row[$idAttribute] set, the `idPreassigned`
295
     * will be assigned to true.
296
     */
297 222
    public static function instantiate($row)
298
    {
299 222
        $self = static::buildNoInitModel();
300 222
        if (isset($self->idAttribute) && isset($row[$self->idAttribute])) {
301 222
            $model = new static(['idPreassigned' => true]);
0 ignored issues
show
Unused Code introduced by
The call to EntityTrait::__construct() has too many arguments starting with array('idPreassigned' => true).

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...
302
        } else {
303
            $model = new static;
304
        }
305 222
        return $model;
306
    }
307
    
308
    /**
309
     * unset entity attributes.
310
     * @return array result.
311
     */
312 1
    public function unsetSelfFields()
313
    {
314 1
        return static::unsetFields($this->attributes, $this->enabledFields());
0 ignored issues
show
Bug introduced by
The property attributes does not seem to exist. Did you mean idAttributeSafe?

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...
315
    }
316
    
317
    /**
318
     * unset fields of array.
319
     * @param array $array
320
     * @param array $fields
321
     * @return array
322
     */
323 1
    public static function unsetFields($array, $fields = null)
324
    {
325 1
        if (!is_array($array)) {
326
            $fields = [];
327
        }
328 1
        foreach ($array as $key => $value) {
329 1
            if (is_string($key) && in_array($key, $fields)) {
330 1
                unset($array[$key]);
331
            }
332
        }
333 1
        return $array;
334
    }
335
    
336
    /**
337
     * Get enabled fields.
338
     * @return string[]
339
     */
340 133
    public function enabledFields()
341
    {
342 133
        return array_merge(
343 133
            (is_string($this->guidAttribute) && !empty($this->guidAttribute)) ? [$this->guidAttribute] : [],
344 133
            (is_string($this->idAttribute) && !empty($this->idAttribute)) ? [$this->idAttribute] : [],
345 133
            $this->enabledTimestampFields(),
346 133
            $this->enabledIPFields()
347
        );
348
    }
349
}
350