Completed
Push — master ( 05596c...94bfdc )
by vistart
08:42
created

SelfBlameableTrait::updateChildren()   D

Complexity

Conditions 9
Paths 31

Size

Total Lines 33
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 16.6047

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 33
ccs 12
cts 22
cp 0.5455
rs 4.909
cc 9
eloc 24
nc 31
nop 1
crap 16.6047
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\base\ModelEvent;
16
use yii\base\InvalidConfigException;
17
use yii\base\InvalidParamException;
18
use yii\db\ActiveQuery;
19
use yii\db\IntegrityException;
20
21
/**
22
 * This trait is designed for the model who contains parent.
23
 * The BlameableTrait use this trait by default. If you want to use this trait
24
 * into seperate model, please call the `initSelfBlameableEvents()` method in
25
 * `init()` method, like following:
26
 * ```php
27
 * public function init()
28
 * {
29
 *     $this->initSelfBlameableEvents();  // put it before parent call.
30
 *     parent::init();
31
 * }
32
 * ```
33
 *
34
 * @property static $parent
35
 * @property-read static[] $ancestors
36
 * @property-read string[] $ancestorChain
37
 * @property-read array $ancestorModels
38
 * @property-read static $commonAncestor
39
 * @property-read static[] $children
40
 * @property-read static[] $oldChildren
41
 * @property array $selfBlameableRules
42
 * @version 1.0
43
 * @author vistart <[email protected]>
44
 */
45
trait SelfBlameableTrait
46
{
47
48
    /**
49
     * @var false|string attribute name of which store the parent's guid.
50
     * If you do not want to use self-blameable features, please set it false.
51
     * Or if you access any features of this trait when this parameter is false,
52
     * exception may be thrown.
53
     */
54
    public $parentAttribute = false;
55
56
    /**
57
     * @var string|array rule name and parameters of parent attribute, as well
58
     * as self referenced ID attribute.
59
     */
60
    public $parentAttributeRule = ['string', 'max' => 16];
61
62
    /**
63
     * @var string self referenced ID attribute.
64
     * If you enable self-blameable features, this parameter should be specified,
65
     * otherwise, exception will be thrown.
66
     */
67
    public $refIdAttribute = 'guid';
68
    public static $parentNone = 0;
69
    public static $parentParent = 1;
70
    public static $parentTypes = [
71
        0 => 'none',
72
        1 => 'parent',
73
    ];
74
    public static $onNoAction = 0;
75
    public static $onRestrict = 1;
76
    public static $onCascade = 2;
77
    public static $onSetNull = 3;
78
    public static $onUpdateTypes = [
79
        0 => 'on action',
80
        1 => 'restrict',
81
        2 => 'cascade',
82
        3 => 'set null',
83
    ];
84
85
    /**
86
     * @var integer indicates the on delete type. default to cascade.
87
     */
88
    public $onDeleteType = 2;
89
90
    /**
91
     * @var integer indicates the on update type. default to cascade.
92
     */
93
    public $onUpdateType = 2;
94
95
    /**
96
     * @var boolean indicates whether throw exception or not when restriction occured on updating or deleting operation.
97
     */
98
    public $throwRestrictException = false;
99
    
100
    /**
101
     * @var array store the attribute validation rules.
102
     * If this field is a non-empty array, then it will be given.
103
     */
104
    private $localSelfBlameableRules = [];
105
    public static $eventParentChanged = 'parentChanged';
106
    public static $eventChildAdded = 'childAdded';
107
108
    /**
109
     * @var false|integer Set the limit of ancestor level. False is no limit.
110
     * We strongly recommend you set an unsigned integer which is less than 256.
111
     */
112
    public $ancestorLimit = false;
113
114
    /**
115
     * @var false|integer Set the limit of children (not descendants). False is no limit.
116
     * We strongly recommend you set an unsigned integer which is less than 1024.
117
     */
118
    public $childrenLimit = false;
119
120
    /**
121
     * Get rules associated with self blameable attribute.
122
     * If self-blameable rules has been stored locally, then it will be given,
123
     * or return the parent attribute rule.
124
     * @return array rules.
125
     */
126 114
    public function getSelfBlameableRules()
127
    {
128 114
        if (!is_string($this->parentAttribute)) {
129 114
            return [];
130
        }
131 33
        if (!empty($this->localSelfBlameableRules) && is_array($this->localSelfBlameableRules)) {
132 2
            return $this->localSelfBlameableRules;
133
        }
134 33
        if (is_string($this->parentAttributeRule)) {
135
            $this->parentAttributeRule = [$this->parentAttributeRule];
136
        }
137 33
        $this->localSelfBlameableRules = [
138 33
            array_merge([$this->parentAttribute], $this->parentAttributeRule),
139
        ];
140 33
        return $this->localSelfBlameableRules;
141
    }
142
143
    /**
144
     * Set rules associated with self blameable attribute.
145
     * @param array $rules rules.
146
     */
147 1
    public function setSelfBlameableRules($rules = [])
148
    {
149 1
        $this->localSelfBlameableRules = $rules;
150 1
    }
151
152
    /**
153
     * Check whether this model has reached the ancestor limit.
154
     * If $ancestorLimit is false, it will be regared as no limit(return false).
155
     * If $ancestorLimit is not false and not an unsigned integer, 256 will be taken.
156
     * @return boolean
157
     */
158 43
    public function hasReachedAncestorLimit()
159
    {
160 43
        if ($this->ancestorLimit === false) {
161 43
            return false;
162
        }
163 3
        if (!is_numeric($this->ancestorLimit) || $this->ancestorLimit < 0) {
164 1
            $this->ancestorLimit = 256;
165
        }
166 3
        return count($this->getAncestorChain()) >= $this->ancestorLimit;
167
    }
168
169
    /**
170
     * Check whether this model has reached the children limit.
171
     * If $childrenLimit is false, it will be regarded as no limit(return false).
172
     * If $childrenLimit is not false and not an unsigned integer, 1024 will be taken.
173
     * @return boolean
174
     */
175 4
    public function hasReachedChildrenLimit()
176
    {
177 4
        if ($this->childrenLimit === false) {
178 4
            return false;
179
        }
180 1
        if (!is_numeric($this->childrenLimit) || $this->childrenLimit < 0) {
181 1
            $this->childrenLimit = 1024;
182
        }
183 1
        return ((int) $this->getChildren()->count()) >= $this->childrenLimit;
184
    }
185
186
    /**
187
     * Bear a child.
188
     * The creator of this child is not necessarily the creator of current one.
189
     * For example: Someone commit a comment on another user's comment, these
190
     * two comments are father and son, but do not belong to the same owner.
191
     * Therefore, you need to specify the creator of current model.
192
     * @param array $config
193
     * @return static|null Null if reached the ancestor limit or children limit.
194
     * @throws InvalidConfigException Self reference ID attribute or
195
     * parent attribute not determined.
196
     * @throws InvalidParamException ancestor or children limit reached.
197
     */
198 4
    public function bear($config = [])
199
    {
200 4
        if (!$this->parentAttribute) {
201 1
            throw new InvalidConfigException("Parent Attribute Not Determined.");
202
        }
203 3
        if (!$this->refIdAttribute) {
204
            throw new InvalidConfigException("Self Reference ID Attribute Not Determined.");
205
        }
206 3
        if ($this->hasReachedAncestorLimit()) {
207
            throw new InvalidParamException("Reached Ancestor Limit: " . $this->ancestorLimit);
208
        }
209 3
        if ($this->hasReachedChildrenLimit()) {
210
            throw new InvalidParamException("Reached Children Limit: ". $this->childrenLimit);
211
        }
212 3
        if (isset($config['class'])) {
213
            unset($config['class']);
214
        }
215 3
        $model = new static($config);
0 ignored issues
show
Unused Code introduced by
The call to SelfBlameableTrait::__construct() has too many arguments starting with $config.

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...
216 3
        if ($this->addChild($model) === false) {
217
            return false;
218
        }
219 3
        return $model;
220
    }
221
222
    /**
223
     * Add a child.
224
     * @param static $child
0 ignored issues
show
introduced by
The type SelfBlameableTrait for parameter $child 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...
225
     * @return boolean
226
     */
227 3
    public function addChild($child)
228
    {
229 3
        return $this->hasReachedChildrenLimit() ? false : $child->setParent($this);
230
    }
231
232
    /**
233
     * Event triggered before deleting itself.
234
     * @param ModelEvent $event
235
     * @return boolean true if parentAttribute not specified.
236
     * @throws IntegrityException throw if $throwRestrictException is true when $onDeleteType is on restrict.
237
     */
238 55
    public function onDeleteChildren($event)
239
    {
240 55
        $sender = $event->sender;
241
        /* @var $sender static */
242 55
        if (empty($sender->parentAttribute) || !is_string($sender->parentAttribute)) {
243 49
            return true;
244
        }
245 6
        switch ($sender->onDeleteType) {
246 6
            case static::$onRestrict:
247 1
                $event->isValid = $sender->children === null;
248 1
                if ($this->throwRestrictException) {
249 1
                    throw new IntegrityException('Delete restricted.');
250
                }
251 1
                break;
252 5
            case static::$onCascade:
253 3
                $event->isValid = $sender->deleteChildren();
0 ignored issues
show
Documentation Bug introduced by
It seems like $sender->deleteChildren() can also be of type object<yii\db\IntegrityException>. However, the property $isValid is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
254 3
                break;
255 2
            case static::$onSetNull:
256 1
                $event->isValid = $sender->updateChildren(null);
0 ignored issues
show
Documentation Bug introduced by
It seems like $sender->updateChildren(null) can also be of type object<yii\db\IntegrityException>. However, the property $isValid is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
257 1
                break;
258 1
            case static::$onNoAction:
259
            default:
260 1
                $event->isValid = true;
261 1
                break;
262
        }
263 6
    }
264
265
    /**
266
     * Event triggered before updating itself.
267
     * @param ModelEvent $event
268
     * @return boolean true if parentAttribute not specified.
269
     * @throws IntegrityException throw if $throwRestrictException is true when $onUpdateType is on restrict.
270
     */
271 1
    public function onUpdateChildren($event)
272
    {
273 1
        $sender = $event->sender;
274
        /* @var $sender static */
275 1
        if (empty($sender->parentAttribute) || !is_string($sender->parentAttribute)) {
276
            return true;
277
        }
278 1
        switch ($sender->onUpdateType) {
279 1
            case static::$onRestrict:
280
                $event->isValid = $sender->getOldChildren() === null;
281
                if ($this->throwRestrictException) {
282
                    throw new IntegrityException('Update restricted.');
283
                }
284
                break;
285 1
            case static::$onCascade:
286 1
                $event->isValid = $sender->updateChildren();
0 ignored issues
show
Documentation Bug introduced by
It seems like $sender->updateChildren() can also be of type object<yii\db\IntegrityException>. However, the property $isValid is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
287 1
                break;
288
            case static::$onSetNull:
289
                $event->isValid = $sender->updateChildren(null);
0 ignored issues
show
Documentation Bug introduced by
It seems like $sender->updateChildren(null) can also be of type object<yii\db\IntegrityException>. However, the property $isValid is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
290
                break;
291
            case static::$onNoAction:
292
            default:
293
                $event->isValid = true;
294
                break;
295
        }
296 1
    }
297
298
    /**
299
     * Get parent query.
300
     * Or get parent instance if access by magic property.
301
     * @return ActiveQuery
302
     */
303 43
    public function getParent()
304
    {
305 43
        return $this->hasOne(static::class, [$this->refIdAttribute => $this->parentAttribute]);
0 ignored issues
show
Bug introduced by
It seems like hasOne() 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...
306
    }
307
    
308
    public function getParentId()
309
    {
310
        return (is_string($this->parentAttribute) && !empty($this->parentAttribute)) ? $this->{$this->parentAttribute} : null;
311
    }
312
    
313 1
    public function setParentId($id)
314
    {
315 1
        return (is_string($this->parentAttribute) && !empty($this->parentAttribute)) ? $this->{$this->parentAttribute} = $id : null;
316
    }
317
    
318 43
    public function getRefId()
319
    {
320 43
        if ($this->refIdAttribute == $this->guidAttribute) {
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...
321 43
            return $this->getGUID();
0 ignored issues
show
Bug introduced by
It seems like getGUID() 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...
322
        }
323
        if ($this->refIdAttribute == $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
            return $this->getID();
0 ignored issues
show
Bug introduced by
It seems like getID() 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...
325
        }
326
        return $this->{$this->refIdAttribute};
327
    }
328
    
329
    public function setRefId($id)
330
    {
331
        if ($this->refIdAttribute == $this->guidAttribute) {
332
            return $this->setGUID($id);
0 ignored issues
show
Bug introduced by
It seems like setGUID() 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...
333
        }
334
        if ($this->refIdAttribute == $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...
335
            return $this->setID($id);
0 ignored issues
show
Bug introduced by
It seems like setID() 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...
336
        }
337
        return $this->{$this->refIdAttribute} = $id;
338
    }
339
340
    /**
341
     * Set parent.
342
     * Don't forget save model after setting it.
343
     * @param static $parent
0 ignored issues
show
introduced by
The type SelfBlameableTrait for parameter $parent 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...
344
     * @return false|string False if restriction reached. Otherwise parent's GUID given.
345
     */
346 43
    public function setParent($parent)
347
    {
348 43
        if (empty($parent) || $this->getRefId() == $parent->getRefId() || $parent->hasAncestor($this) || $this->hasReachedAncestorLimit()) {
349 1
            return false;
350
        }
351 43
        unset($this->parent);
352 43
        unset($parent->children);
353 43
        $this->trigger(static::$eventParentChanged);
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...
354 43
        $parent->trigger(static::$eventChildAdded);
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...
355 43
        return $this->{$this->parentAttribute} = $parent->getRefId();
356
    }
357
    
358 1
    public function setNullParent()
359
    {
360 1
        unset($this->parent->children);
361 1
        unset($this->parent);
362 1
        $this->setParentId('');
363 1
    }
364
365
    /**
366
     * Check whether this model has parent.
367
     * @return boolean
368
     */
369 43
    public function hasParent()
370
    {
371 43
        return $this->parent !== null;
372
    }
373
374
    /**
375
     * Check whether if $ancestor is the ancestor of myself.
376
     * Note, Itself will not be regarded as the its ancestor.
377
     * @param static $ancestor
0 ignored issues
show
introduced by
The type SelfBlameableTrait for parameter $ancestor 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...
378
     * @return boolean
379
     */
380 43
    public function hasAncestor($ancestor)
381
    {
382 43
        if (!$this->hasParent()) {
383 43
            return false;
384
        }
385 6
        if ($this->parent->getRefId() == $ancestor->getRefId()) {
386 2
            return true;
387
        }
388 6
        return $this->parent->hasAncestor($ancestor);
389
    }
390
391
    /**
392
     * Get ancestor chain. (Ancestors' GUID Only!)
393
     * If this model has ancestor, the return array consists all the ancestor in order.
394
     * The first element is parent, and the last element is root, otherwise return empty array.
395
     * If you want to get ancestor model, you can simplify instance a query and specify the
396
     * condition with the return value. But it will not return models under the order of ancestor chain.
397
     * @param string[] $ancestor
398
     * @return string[]
399
     */
400 8
    public function getAncestorChain($ancestor = [])
401
    {
402 8
        if (!is_string($this->parentAttribute) || empty($this->parentAttribute)) {
403 1
            return [];
404
        }
405 7
        if (!$this->hasParent()) {
406 7
            return $ancestor;
407
        }
408 7
        $ancestor[] = $this->parent->getRefId();
409 7
        return $this->parent->getAncestorChain($ancestor);
410
    }
411
412
    /**
413
     * Get ancestors with specified ancestor chain.
414
     * @param string[] $ancestor Ancestor chain.
415
     * @return static[]|null
416
     */
417 2
    public static function getAncestorModels($ancestor)
418
    {
419 2
        if (empty($ancestor) || !is_array($ancestor)) {
420 1
            return [];
421
        }
422 2
        $models = [];
423 2
        foreach ($ancestor as $self) {
424 2
            $models[] = static::findOne($self);
425
        }
426 2
        return $models;
427
    }
428
429
    /**
430
     * Get ancestors.
431
     * @return static[]
432
     */
433 1
    public function getAncestors()
434
    {
435 1
        return static::getAncestorModels($this->getAncestorChain());
436
    }
437
438
    /**
439
     * Check whether if this model has common ancestor with $model.
440
     * @param static $model
0 ignored issues
show
introduced by
The type SelfBlameableTrait for parameter $model 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...
441
     * @return boolean
442
     */
443 2
    public function hasCommonAncestor($model)
444
    {
445 2
        return $this->getCommonAncestor($model) !== null;
446
    }
447
448
    /**
449
     * Get common ancestor. If there isn't common ancestor, null will be given.
450
     * @param static $model
0 ignored issues
show
introduced by
The type SelfBlameableTrait for parameter $model 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...
451
     * @return static
452
     */
453 3
    public function getCommonAncestor($model)
454
    {
455 3
        if (empty($this->parentAttribute) || !is_string($this->parentAttribute) || empty($model) || !$model->hasParent()) {
456 1
            return null;
457
        }
458 2
        $ancestor = $this->getAncestorChain();
459 2
        if (in_array($model->parent->getRefId(), $ancestor)) {
460 2
            return $model->parent;
461
        }
462 1
        return $this->getCommonAncestor($model->parent);
463
    }
464
465
    /**
466
     * Get children query.
467
     * Or get children instances if access magic property.
468
     * @return ActiveQuery
469
     */
470 43
    public function getChildren()
471
    {
472 43
        return $this->hasMany(static::class, [$this->parentAttribute => $this->refIdAttribute])->inverseOf('parent');
0 ignored issues
show
Bug introduced by
It seems like hasMany() 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...
473
    }
474
475
    /**
476
     * Get children which parent attribute point to old guid.
477
     * @return static[]
478
     */
479 2
    public function getOldChildren()
480
    {
481 2
        return static::find()->where([$this->parentAttribute => $this->getOldAttribute($this->refIdAttribute)])->all();
0 ignored issues
show
Bug introduced by
It seems like getOldAttribute() 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...
482
    }
483
484
    /**
485
     * Update all children, not grandchildren (descendants).
486
     * If onUpdateType is on cascade, the children will be updated automatically.
487
     * @param mixed $value set guid if false, set empty string if empty() return
488
     * true, otherwise set it to $parentAttribute.
489
     * @return IntegrityException|boolean true if all update operations
490
     * succeeded to execute, or false if anyone of them failed. If not production
491
     * environment or enable debug mode, it will return exception.
492
     * @throws IntegrityException throw if anyone update failed.
493
     * The exception message only contains the first error.
494
     */
495 2
    public function updateChildren($value = false)
496
    {
497 2
        $children = $this->getOldChildren();
498 2
        if (empty($children)) {
499
            return true;
500
        }
501 2
        $transaction = $this->getDb()->beginTransaction();
0 ignored issues
show
Bug introduced by
It seems like getDb() 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...
502
        try {
503 2
            foreach ($children as $child) {
504
                /* @var $child static */
505 2
                if ($value === false) {
506 1
                    $child->setParent($this);
507
                } elseif (empty($value)) {
508 1
                    $child->setNullParent();
509
                } else {
510
                    $child->setParentId($value);
511
                }
512 2
                if (!$child->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...
513 2
                    throw new IntegrityException('Update failed:', $child->getErrors());
0 ignored issues
show
Bug introduced by
It seems like getErrors() 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...
514
                }
515
            }
516 2
            $transaction->commit();
517
        } catch (IntegrityException $ex) {
518
            $transaction->rollBack();
519
            if (YII_DEBUG || YII_ENV !== YII_ENV_PROD) {
520
                Yii::error($ex->errorInfo, static::class . '\update');
521
                return $ex;
522
            }
523
            Yii::warning($ex->errorInfo, static::class . '\update');
524
            return false;
525
        }
526 2
        return true;
527
    }
528
529
    /**
530
     * Delete all children, not grandchildren (descendants).
531
     * If onDeleteType is `on cascade`, the children will be deleted automatically.
532
     * If onDeleteType is `on restrict` and contains children, the deletion will
533
     * be restricted.
534
     * @return IntegrityException|boolean true if all delete operations
535
     * succeeded to execute, or false if anyone of them failed. If not production
536
     * environment or enable debug mode, it will return exception.
537
     * @throws IntegrityException throw if anyone delete failed.
538
     * The exception message only contains the first error.
539
     */
540 3
    public function deleteChildren()
541
    {
542 3
        $children = $this->children;
543 3
        if (empty($children)) {
544 3
            return true;
545
        }
546 1
        $transaction = $this->getDb()->beginTransaction();
0 ignored issues
show
Bug introduced by
It seems like getDb() 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...
547
        try {
548 1
            foreach ($children as $child) {
549
                /* @var $child static */
550 1
                if (!$child->delete()) {
0 ignored issues
show
Bug introduced by
The method delete() does not exist on rhosocial\base\models\traits\SelfBlameableTrait. Did you maybe mean onDeleteChildren()?

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...
551 1
                    throw new IntegrityException('Delete failed:', $child->getErrors());
0 ignored issues
show
Bug introduced by
It seems like getErrors() 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...
552
                }
553
            }
554 1
            $transaction->commit();
555
        } catch (IntegrityException $ex) {
556
            $transaction->rollBack();
557
            if (YII_DEBUG || YII_ENV !== YII_ENV_PROD) {
558
                Yii::error($ex->errorInfo, static::class . '\delete');
559
                return $ex;
560
            }
561
            Yii::warning($ex->errorInfo, static::class . '\delete');
562
            return false;
563
        }
564 1
        return true;
565
    }
566
567
    /**
568
     * Update children's parent attribute.
569
     * Event triggered before updating.
570
     * @param ModelEvent $event
571
     * @return boolean
572
     */
573 13
    public function onParentRefIdChanged($event)
574
    {
575 13
        $sender = $event->sender;
576 13
        if ($sender->isAttributeChanged($sender->refIdAttribute)) {
577 1
            return $sender->onUpdateChildren($event);
578
        }
579 13
    }
580
581
    /**
582
     * Attach events associated with self blameable attribute.
583
     */
584 121
    protected function initSelfBlameableEvents()
585
    {
586 121
        $this->on(static::EVENT_BEFORE_UPDATE, [$this, 'onParentRefIdChanged']);
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...
587 121
        $this->on(static::EVENT_BEFORE_DELETE, [$this, 'onDeleteChildren']);
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...
588 121
    }
589
    
590
    /**
591
     * Clear invalid parent.
592
     * The invalid state depends on which if parent id exists but it's corresponding
593
     * parent cannot be found.
594
     */
595
    public function clearInvalidParent()
596
    {
597
        if ($this->getParentId() !== null && !$this->hasParent()) {
598
            $this->setNullParent();
599
        }
600
    }
601
}
602