Completed
Push — master ( 7fc3f3...dbb6f1 )
by vistart
05:17
created

SelfBlameableTrait   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 83.87%

Importance

Changes 13
Bugs 0 Features 0
Metric Value
wmc 44
c 13
b 0
f 0
lcom 1
cbo 2
dl 0
loc 290
ccs 104
cts 124
cp 0.8387
rs 8.3396

12 Methods

Rating   Name   Duplication   Size   Complexity  
C onDeleteChildren() 0 25 7
C onUpdateChildren() 0 25 7
A getParent() 0 4 1
A getChildren() 0 4 1
A getOldChildren() 0 4 1
D updateChildren() 0 34 9
C deleteChildren() 0 25 7
A onParentRefIdChanged() 0 7 2
A initSelfBlameableEvents() 0 5 1
B getSelfBlameableRules() 0 16 5
A setSelfBlameableRules() 0 4 1
A bear() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like SelfBlameableTrait 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 SelfBlameableTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 *  _   __ __ _____ _____ ___  ____  _____
5
 * | | / // // ___//_  _//   ||  __||_   _|
6
 * | |/ // /(__  )  / / / /| || |     | |
7
 * |___//_//____/  /_/ /_/ |_||_|     |_|
8
 * @link http://vistart.name/
9
 * @copyright Copyright (c) 2016 vistart
10
 * @license http://vistart.name/license/
11
 */
12
13
namespace vistart\Models\traits;
14
15
/**
16
 * Description of SelfBlameableTrait
17
 *
18
 * @property-read static $parent
19
 * @property-read array $children
20
 * @property-read array $oldChildren
21
 * @property array $selfBlameableRules
22
 * @version 2.0
23
 * @author vistart <[email protected]>
24
 */
25
trait SelfBlameableTrait
26
{
27
28
    /**
29
     * @var false|string attribute name of which store the parent's guid.
30
     */
31
    public $parentAttribute = false;
32
33
    /**
34
     * @var string|array rule name and parameters of parent attribute, as well
35
     * as self referenced ID attribute.
36
     */
37
    public $parentAttributeRule = ['string', 'max' => 36];
38
39
    /**
40
     * @var string self referenced ID attribute.
41
     */
42
    public $refIdAttribute = 'guid';
43
    public static $parentNone = 0;
44
    public static $parentParent = 1;
45
    public static $parentTypes = [
46
        0 => 'none',
47
        1 => 'parent',
48
    ];
49
    public static $onNoAction = 0;
50
    public static $onRestrict = 1;
51
    public static $onCascade = 2;
52
    public static $onSetNull = 3;
53
    public static $onUpdateTypes = [
54
        0 => 'on action',
55
        1 => 'restrict',
56
        2 => 'cascade',
57
        3 => 'set null',
58
    ];
59
60
    /**
61
     * @var integer indicates the on delete type. default to cascade.
62
     */
63
    public $onDeleteType = 2;
64
65
    /**
66
     * @var integer indicates the on update type. default to cascade.
67
     */
68
    public $onUpdateType = 2;
69
70
    /**
71
     * @var boolean indicates whether throw exception or not when restriction occured on updating or deleting operation.
72
     */
73
    public $throwRestrictException = false;
74
    private $localSelfBlameableRules = [];
75
76
    /**
77
     * Get rules associated with self blameable attribute.
78
     * @return array rules.
79
     */
80 6
    public function getSelfBlameableRules()
81
    {
82 6
        if (!is_string($this->parentAttribute)) {
83 5
            return [];
84
        }
85 1
        if (!empty($this->localSelfBlameableRules) && is_array($this->localSelfBlameableRules)) {
86 1
            return $this->localSelfBlameableRules;
87
        }
88 1
        if (is_string($this->parentAttributeRule)) {
89
            $this->parentAttributeRule = [$this->parentAttributeRule];
90
        }
91 1
        $this->localSelfBlameableRules = [
92 1
            array_merge([$this->parentAttribute], $this->parentAttributeRule),
93
        ];
94 1
        return $this->localSelfBlameableRules;
95
    }
96
97
    /**
98
     * Set rules associated with self blameable attribute.
99
     * @param array $rules rules.
100
     */
101 1
    public function setSelfBlameableRules($rules = [])
102
    {
103 1
        $this->localSelfBlameableRules = $rules;
104 1
    }
105
106
    /**
107
     * Bear a child.
108
     * @param array $config
109
     * @return static
110
     */
111 9
    public function bear($config = [])
112
    {
113 9
        if (isset($config['class'])) {
114 9
            unset($config['class']);
115 9
        }
116 9
        $refIdAttribute = $this->refIdAttribute;
117 9
        $config[$this->parentAttribute] = $this->$refIdAttribute;
118 9
        return 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...
119
    }
120
121
    /**
122
     * Event triggered before deleting itself.
123
     * @param \yii\base\ModelEvent $event
124
     * @return boolean true if parentAttribute not specified.
125
     * @throws \yii\db\IntegrityException throw if $throwRestrictException is true when $onDeleteType is on restrict.
126
     */
127 8
    public function onDeleteChildren($event)
128
    {
129 8
        $sender = $event->sender;
130 8
        if (!is_string($sender->parentAttribute)) {
131 4
            return true;
132
        }
133 4
        switch ($sender->onDeleteType) {
134 4
            case static::$onRestrict:
135 1
                $event->isValid = $sender->children === null;
136 1
                if ($this->throwRestrictException) {
137 1
                    throw new \yii\db\IntegrityException('Delete restricted.');
138
                }
139 1
                break;
140 3
            case static::$onCascade:
141 1
                $event->isValid = $sender->deleteChildren();
142 1
                break;
143 2
            case static::$onSetNull:
144 1
                $event->isValid = $sender->updateChildren(null);
145 1
                break;
146 1
            case static::$onNoAction:
147 1
            default:
148 1
                $event->isValid = true;
149 1
                break;
150 4
        }
151 4
    }
152
153
    /**
154
     * Event triggered before updating itself.
155
     * @param \yii\base\ModelEvent $event
156
     * @return boolean true if parentAttribute not specified.
157
     * @throws \yii\db\IntegrityException throw if $throwRestrictException is true when $onUpdateType is on restrict.
158
     */
159 4
    public function onUpdateChildren($event)
160
    {
161 4
        $sender = $event->sender;
162 4
        if (!is_string($sender->parentAttribute)) {
163
            return true;
164
        }
165 4
        switch ($sender->onUpdateType) {
166 4
            case static::$onRestrict:
167 1
                $event->isValid = $sender->getOldChildren() === null;
168 1
                if ($this->throwRestrictException) {
169 1
                    throw new \yii\db\IntegrityException('Update restricted.');
170
                }
171
                break;
172 3
            case static::$onCascade:
173 1
                $event->isValid = $sender->updateChildren();
174 1
                break;
175 2
            case static::$onSetNull:
176 1
                $event->isValid = $sender->updateChildren(null);
177 1
                break;
178 1
            case static::$onNoAction:
179 1
            default:
180 1
                $event->isValid = true;
181 1
                break;
182 3
        }
183 3
    }
184
185
    /**
186
     * Get parent query.
187
     * Or get parent instance if access by magic property.
188
     * @return \yii\db\ActiveQuery
189
     */
190 2
    public function getParent()
191
    {
192 2
        return $this->hasOne(static::className(), [$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...
193
    }
194
195
    /**
196
     * Get children query.
197
     * Or get children instances if access magic property.
198
     * @return \yii\db\ActiveQuery
199
     */
200 3
    public function getChildren()
201
    {
202 3
        return $this->hasMany(static::className(), [$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...
203
    }
204
205
    /**
206
     * Get children which parent attribute point to the my old guid.
207
     * @return static[]
208
     */
209 4
    public function getOldChildren()
210
    {
211 4
        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...
212
    }
213
214
    /**
215
     * Update all children, not grandchildren.
216
     * If onUpdateType is on cascade, the children will be updated automatically.
217
     * @param mixed $value set guid if false, set empty string if empty() return
218
     * true, otherwise set it to $parentAttribute.
219
     * @return \yii\db\IntegrityException|boolean true if all update operations
220
     * succeeded to execute, or false if anyone of them failed. If not production
221
     * environment or enable debug mode, it will return exception.
222
     * @throws \yii\db\IntegrityException throw if anyone update failed.
223
     */
224 3
    public function updateChildren($value = false)
225
    {
226 3
        $children = $this->getOldChildren();
227 3
        if (empty($children)) {
228
            return true;
229
        }
230 3
        $parentAttribute = $this->parentAttribute;
231 3
        $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...
232
        try {
233 3
            foreach ($children as $child) {
234 3
                if ($value === false) {
235 1
                    $refIdAttribute = $this->refIdAttribute;
236 1
                    $child->$parentAttribute = $this->$refIdAttribute;
237 3
                } elseif (empty($value)) {
238 2
                    $child->$parentAttribute = '';
239 2
                } else {
240
                    $child->$parentAttribute = $value;
241
                }
242 3
                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...
243
                    throw new \yii\db\IntegrityException('Update failed:' . $child->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...
244
                }
245 3
            }
246 3
            $transaction->commit();
247 3
        } catch (\yii\db\IntegrityException $ex) {
248
            $transaction->rollBack();
249
            if (YII_DEBUG || YII_ENV !== YII_ENV_PROD) {
250
                Yii::error($ex->errorInfo, static::className() . '\update');
251
                return $ex;
252
            }
253
            Yii::warning($ex->errorInfo, static::className() . '\update');
254
            return false;
255
        }
256 3
        return true;
257
    }
258
259
    /**
260
     * Delete all children, not grandchildren.
261
     * If onDeleteType is on cascade, the children will be deleted automatically.
262
     * If onDeleteType is on restrict and contains children, the deletion will
263
     * be restricted.
264
     * @return \yii\db\IntegrityException|boolean true if all delete operations
265
     * succeeded to execute, or false if anyone of them failed. If not production
266
     * environment or enable debug mode, it will return exception.
267
     * @throws \yii\db\IntegrityException throw if anyone delete failed.
268
     */
269 1
    public function deleteChildren()
270
    {
271 1
        $children = $this->children;
272 1
        if (empty($children)) {
273 1
            return true;
274
        }
275 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...
276
        try {
277 1
            foreach ($children as $child) {
278 1
                if (!$child->delete()) {
279
                    throw new \yii\db\IntegrityException('Delete failed:' . $child->errors);
280
                }
281 1
            }
282 1
            $transaction->commit();
283 1
        } catch (\yii\db\IntegrityException $ex) {
284
            $transaction->rollBack();
285
            if (YII_DEBUG || YII_ENV !== YII_ENV_PROD) {
286
                Yii::error($ex->errorInfo, static::className() . '\delete');
287
                return $ex;
288
            }
289
            Yii::warning($ex->errorInfo, static::className() . '\delete');
290
            return false;
291
        }
292 1
        return true;
293
    }
294
295
    /**
296
     * Update children's parent attribute.
297
     * Event triggered before updating.
298
     * @param \yii\base\ModelEvent $event
299
     * @return boolean
300
     */
301 9
    public function onParentRefIdChanged($event)
302
    {
303 9
        $sender = $event->sender;
304 9
        if ($sender->isAttributeChanged($sender->refIdAttribute)) {
305 4
            return $sender->onUpdateChildren($event);
306
        }
307 7
    }
308
309 23
    protected function initSelfBlameableEvents()
310
    {
311 23
        $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...
312 23
        $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...
313 23
    }
314
}
315