Completed
Push — master ( 5035c9...59342c )
by Pavel
02:43
created

NestedSetsBehavior   F

Complexity

Total Complexity 108

Size/Duplication

Total Lines 688
Duplicated Lines 7.41 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 95.08%

Importance

Changes 0
Metric Value
wmc 108
lcom 1
cbo 8
dl 51
loc 688
ccs 309
cts 325
cp 0.9508
rs 1.912
c 0
b 0
f 0

32 Methods

Rating   Name   Duplication   Size   Complexity  
A events() 0 11 1
A getParents() 3 20 2
A getParent() 9 9 1
A getRoot() 10 10 1
B getDescendants() 3 22 6
A getChildren() 0 4 1
A getLeaves() 0 8 1
A getPrev() 10 10 1
A getNext() 10 10 1
B populateTree() 0 43 9
A isRoot() 0 4 1
A isChildOf() 3 11 4
A isLeaf() 0 4 1
A makeRoot() 0 5 1
A prependTo() 0 6 1
A appendTo() 0 6 1
A insertBefore() 0 6 1
A insertAfter() 0 6 1
A preDeleteWithChildren() 0 4 1
A deleteWithChildren() 0 22 4
B beforeInsert() 0 36 9
A afterInsert() 0 16 5
C beforeUpdate() 0 38 14
B afterUpdate() 0 29 9
A beforeDelete() 0 10 4
A afterDelete() 0 20 3
A deleteWithChildrenInternal() 0 10 2
A insertNode() 0 17 5
A moveNode() 0 48 4
A moveNodeAsRoot() 0 18 2
B shift() 3 18 9
A treeCondition() 0 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 * @link https://github.com/paulzi/yii2-nested-sets
4
 * @copyright Copyright (c) 2015 PaulZi <[email protected]>
5
 * @license MIT (https://github.com/paulzi/yii2-nested-sets/blob/master/LICENSE)
6
 */
7
8
namespace paulzi\nestedsets;
9
10
use Yii;
11
use yii\base\Behavior;
12
use yii\base\Exception;
13
use yii\base\NotSupportedException;
14
use yii\db\ActiveRecord;
15
use yii\db\Expression;
16
17
/**
18
 * Nested Sets Behavior for Yii2
19
 * @author PaulZi <[email protected]>
20
 * @author Alexander Kochetov <https://github.com/creocoder>
21
 *
22
 * @property ActiveRecord $owner
23
 */
24
class NestedSetsBehavior extends Behavior
25
{
26
    const OPERATION_MAKE_ROOT       = 1;
27
    const OPERATION_PREPEND_TO      = 2;
28
    const OPERATION_APPEND_TO       = 3;
29
    const OPERATION_INSERT_BEFORE   = 4;
30
    const OPERATION_INSERT_AFTER    = 5;
31
    const OPERATION_DELETE_ALL      = 6;
32
33
34
    /**
35
     * @var string|null
36
     */
37
    public $treeAttribute;
38
39
    /**
40
     * @var string
41
     */
42
    public $leftAttribute = 'lft';
43
44
    /**
45
     * @var string
46
     */
47
    public $rightAttribute = 'rgt';
48
49
    /**
50
     * @var string
51
     */
52
    public $depthAttribute = 'depth';
53
54
    /**
55
     * @var string|null
56
     */
57
    protected $operation;
58
59
    /**
60
     * @var ActiveRecord|self|null
61
     */
62
    protected $node;
63
64
    /**
65
     * @var string
66
     */
67
    protected $treeChange;
68
69
70
    /**
71
     * @inheritdoc
72
     */
73 201
    public function events()
74
    {
75
        return [
76 201
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeInsert',
77
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterInsert',
78
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeUpdate',
79
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterUpdate',
80
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
81
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
82
        ];
83
    }
84
85
    /**
86
     * @param int|null $depth
87
     * @return \yii\db\ActiveQuery
88
     */
89 6
    public function getParents($depth = null)
90
    {
91 6
        $tableName = $this->owner->tableName();
92
        $condition = [
93 6
            'and',
94 6
            ['<', "{$tableName}.[[{$this->leftAttribute}]]",  $this->owner->getAttribute($this->leftAttribute)],
95 6
            ['>', "{$tableName}.[[{$this->rightAttribute}]]", $this->owner->getAttribute($this->rightAttribute)],
96
        ];
97 6 View Code Duplication
        if ($depth !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
98 6
            $condition[] = ['>=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) - $depth];
99
        }
100
101 6
        $query = $this->owner->find()
102 6
            ->andWhere($condition)
103 6
            ->andWhere($this->treeCondition())
104 6
            ->addOrderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_ASC]);
105 6
        $query->multiple = true;
106
107 6
        return $query;
108
    }
109
110
    /**
111
     * @return \yii\db\ActiveQuery
112
     */
113 3 View Code Duplication
    public function getParent()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114
    {
115 3
        $tableName = $this->owner->tableName();
116 3
        $query = $this->getParents(1)
117 3
            ->orderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_DESC])
118 3
            ->limit(1);
119 3
        $query->multiple = false;
120 3
        return $query;
121
    }
122
123
    /**
124
     * @return \yii\db\ActiveQuery
125
     */
126 3 View Code Duplication
    public function getRoot()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127
    {
128 3
        $tableName = $this->owner->tableName();
129 3
        $query = $this->owner->find()
130 3
            ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => 1])
131 3
            ->andWhere($this->treeCondition())
132 3
            ->limit(1);
133 3
        $query->multiple = false;
134 3
        return $query;
135
    }
136
137
    /**
138
     * @param int|null $depth
139
     * @param bool $andSelf
140
     * @param bool $backOrder
141
     * @return \yii\db\ActiveQuery
142
     */
143 78
    public function getDescendants($depth = null, $andSelf = false, $backOrder = false)
144
    {
145 78
        $tableName = $this->owner->tableName();
146 78
        $attribute = $backOrder ? $this->rightAttribute : $this->leftAttribute;
147
        $condition = [
148 78
            'and',
149 78
            [$andSelf ? '>=' : '>', "{$tableName}.[[{$attribute}]]",  $this->owner->getAttribute($this->leftAttribute)],
150 78
            [$andSelf ? '<=' : '<', "{$tableName}.[[{$attribute}]]",  $this->owner->getAttribute($this->rightAttribute)],
151
        ];
152
153 78 View Code Duplication
        if ($depth !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154 12
            $condition[] = ['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth];
155
        }
156
157 78
        $query = $this->owner->find()
158 78
            ->andWhere($condition)
159 78
            ->andWhere($this->treeCondition())
160 78
            ->addOrderBy(["{$tableName}.[[{$attribute}]]" => $backOrder ? SORT_DESC : SORT_ASC]);
161 78
        $query->multiple = true;
162
163 78
        return $query;
164
    }
165
166
    /**
167
     * @return \yii\db\ActiveQuery
168
     */
169 3
    public function getChildren()
170
    {
171 3
        return $this->getDescendants(1);
172
    }
173
174
    /**
175
     * @param int|null $depth
176
     * @return \yii\db\ActiveQuery
177
     */
178 3
    public function getLeaves($depth = null)
179
    {
180 3
        $tableName = $this->owner->tableName();
181 3
        $query = $this->getDescendants($depth)
182 3
            ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => new Expression("{$tableName}.[[{$this->rightAttribute}]] - 1")]);
183 3
        $query->multiple = true;
184 3
        return $query;
185
    }
186
187
    /**
188
     * @return \yii\db\ActiveQuery
189
     */
190 3 View Code Duplication
    public function getPrev()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
    {
192 3
        $tableName = $this->owner->tableName();
193 3
        $query = $this->owner->find()
194 3
            ->andWhere(["{$tableName}.[[{$this->rightAttribute}]]" => $this->owner->getAttribute($this->leftAttribute) - 1])
195 3
            ->andWhere($this->treeCondition())
196 3
            ->limit(1);
197 3
        $query->multiple = false;
198 3
        return $query;
199
    }
200
201
    /**
202
     * @return \yii\db\ActiveQuery
203
     */
204 3 View Code Duplication
    public function getNext()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
205
    {
206 3
        $tableName = $this->owner->tableName();
207 3
        $query = $this->owner->find()
208 3
            ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => $this->owner->getAttribute($this->rightAttribute) + 1])
209 3
            ->andWhere($this->treeCondition())
210 3
            ->limit(1);
211 3
        $query->multiple = false;
212 3
        return $query;
213
    }
214
215
    /**
216
     * Populate children relations for self and all descendants
217
     * @param int $depth = null
218
     * @return static
219
     */
220 3
    public function populateTree($depth = null)
221
    {
222
        /** @var ActiveRecord[]|static[] $nodes */
223 3
        if ($depth === null) {
224 3
            $nodes = $this->owner->descendants;
225
        } else {
226 3
            $nodes = $this->getDescendants($depth)->all();
227
        }
228
229 3
        $key = $this->owner->getAttribute($this->leftAttribute);
230 3
        $relates = [];
231 3
        $parents = [$key];
232 3
        $prev = $this->owner->getAttribute($this->depthAttribute);
233 3
        foreach($nodes as $node)
234
        {
235 3
            $level = $node->getAttribute($this->depthAttribute);
236 3
            if ($level <= $prev) {
237 3
                $parents = array_slice($parents, 0, $level - $prev - 1);
238
            }
239
240 3
            $key = end($parents);
241 3
            if (!isset($relates[$key])) {
242 3
                $relates[$key] = [];
243
            }
244 3
            $relates[$key][] = $node;
245
246 3
            $parents[] = $node->getAttribute($this->leftAttribute);
247 3
            $prev = $level;
248
        }
249
250 3
        $ownerDepth = $this->owner->getAttribute($this->depthAttribute);
251 3
        $nodes[] = $this->owner;
252 3
        foreach ($nodes as $node) {
253 3
            $key = $node->getAttribute($this->leftAttribute);
254 3
            if (isset($relates[$key])) {
255 3
                $node->populateRelation('children', $relates[$key]);
256 3
            } elseif ($depth === null || $ownerDepth + $depth > $node->getAttribute($this->depthAttribute)) {
257 3
                $node->populateRelation('children', []);
258
            }
259
        }
260
261 3
        return $this->owner;
262
    }
263
264
    /**
265
     * @return bool
266
     */
267 72
    public function isRoot()
268
    {
269 72
        return $this->owner->getAttribute($this->leftAttribute) === 1;
270
    }
271
272
    /**
273
     * @param ActiveRecord $node
274
     * @return bool
275
     */
276 69
    public function isChildOf($node)
277
    {
278 69
        $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute)
279 69
            && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute);
280
281 69 View Code Duplication
        if ($result && $this->treeAttribute !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282 6
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
283
        }
284
285 69
        return $result;
286
    }
287
288
    /**
289
     * @return bool
290
     */
291 6
    public function isLeaf()
292
    {
293 6
        return $this->owner->getAttribute($this->rightAttribute) - $this->owner->getAttribute($this->leftAttribute) === 1;
294
    }
295
296
    /**
297
     * @return ActiveRecord
298
     */
299 12
    public function makeRoot()
300
    {
301 12
        $this->operation = self::OPERATION_MAKE_ROOT;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::OPERATION_MAKE_ROOT of type integer is incompatible with the declared type string|null of property $operation.

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...
302 12
        return $this->owner;
303
    }
304
305
    /**
306
     * @param ActiveRecord $node
307
     * @return ActiveRecord
308
     */
309 33
    public function prependTo($node)
310
    {
311 33
        $this->operation = self::OPERATION_PREPEND_TO;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::OPERATION_PREPEND_TO of type integer is incompatible with the declared type string|null of property $operation.

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...
312 33
        $this->node = $node;
313 33
        return $this->owner;
314
    }
315
316
    /**
317
     * @param ActiveRecord $node
318
     * @return ActiveRecord
319
     */
320 33
    public function appendTo($node)
321
    {
322 33
        $this->operation = self::OPERATION_APPEND_TO;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::OPERATION_APPEND_TO of type integer is incompatible with the declared type string|null of property $operation.

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...
323 33
        $this->node = $node;
324 33
        return $this->owner;
325
    }
326
327
    /**
328
     * @param ActiveRecord $node
329
     * @return ActiveRecord
330
     */
331 27
    public function insertBefore($node)
332
    {
333 27
        $this->operation = self::OPERATION_INSERT_BEFORE;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::OPERATION_INSERT_BEFORE of type integer is incompatible with the declared type string|null of property $operation.

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...
334 27
        $this->node = $node;
335 27
        return $this->owner;
336
    }
337
338
    /**
339
     * @param ActiveRecord $node
340
     * @return ActiveRecord
341
     */
342 30
    public function insertAfter($node)
343
    {
344 30
        $this->operation = self::OPERATION_INSERT_AFTER;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::OPERATION_INSERT_AFTER of type integer is incompatible with the declared type string|null of property $operation.

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...
345 30
        $this->node = $node;
346 30
        return $this->owner;
347
    }
348
349
    /**
350
     * Need for paulzi/auto-tree
351
     */
352
    public function preDeleteWithChildren()
353
    {
354
        $this->operation = self::OPERATION_DELETE_ALL;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::OPERATION_DELETE_ALL of type integer is incompatible with the declared type string|null of property $operation.

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...
355
    }
356
357
    /**
358
     * @return bool|int
359
     * @throws \Exception
360
     * @throws \yii\db\Exception
361
     */
362 9
    public function deleteWithChildren()
363
    {
364 9
        $this->operation = self::OPERATION_DELETE_ALL;
0 ignored issues
show
Documentation Bug introduced by
It seems like self::OPERATION_DELETE_ALL of type integer is incompatible with the declared type string|null of property $operation.

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...
365 9
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
366
            $transaction = $this->owner->getDb()->beginTransaction();
367
            try {
368
                $result = $this->deleteWithChildrenInternal();
369
                if ($result === false) {
370
                    $transaction->rollBack();
371
                } else {
372
                    $transaction->commit();
373
                }
374
                return $result;
375
            } catch (\Exception $e) {
376
                $transaction->rollBack();
377
                throw $e;
378
            }
379
        } else {
380 9
            $result = $this->deleteWithChildrenInternal();
381
        }
382 6
        return $result;
383
    }
384
385
    /**
386
     * @throws Exception
387
     * @throws NotSupportedException
388
     */
389 48
    public function beforeInsert()
390
    {
391 48
        if ($this->node !== null && !$this->node->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsNewRecord does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
392 27
            $this->node->refresh();
0 ignored issues
show
Bug introduced by
The method refresh does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
393
        }
394 48
        switch ($this->operation) {
395 48
            case self::OPERATION_MAKE_ROOT:
396 6
                $condition = array_merge([$this->leftAttribute => 1], $this->treeCondition());
397 6
                if ($this->owner->find()->andWhere($condition)->one() !== null) {
398 3
                    throw new Exception('Can not create more than one root.');
399
                }
400 3
                $this->owner->setAttribute($this->leftAttribute,  1);
401 3
                $this->owner->setAttribute($this->rightAttribute, 2);
402 3
                $this->owner->setAttribute($this->depthAttribute, 0);
403 3
                break;
404
405 42
            case self::OPERATION_PREPEND_TO:
406 9
                $this->insertNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
407 6
                break;
408
409 33
            case self::OPERATION_APPEND_TO:
410 9
                $this->insertNode($this->node->getAttribute($this->rightAttribute), 1);
411 6
                break;
412
413 24
            case self::OPERATION_INSERT_BEFORE:
414 9
                $this->insertNode($this->node->getAttribute($this->leftAttribute), 0);
415 6
                break;
416
417 15
            case self::OPERATION_INSERT_AFTER:
418 12
                $this->insertNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
419 6
                break;
420
421
            default:
422 3
                throw new NotSupportedException('Method "'. $this->owner->className() . '::insert" is not supported for inserting new nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
423
        }
424 27
    }
425
426
    /**
427
     * @throws Exception
428
     */
429 27
    public function afterInsert()
430
    {
431 27
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
432 3
            $id = $this->owner->getPrimaryKey();
433 3
            $this->owner->setAttribute($this->treeAttribute, $id);
434
435 3
            $primaryKey = $this->owner->primaryKey();
436 3
            if (!isset($primaryKey[0])) {
437
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
438
            }
439
440 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
441
        }
442 27
        $this->operation = null;
443 27
        $this->node      = null;
444 27
    }
445
446
    /**
447
     * @throws Exception
448
     */
449 93
    public function beforeUpdate()
450
    {
451 93
        if ($this->node !== null && !$this->node->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsNewRecord does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
452 78
            $this->node->refresh();
0 ignored issues
show
Bug introduced by
The method refresh does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
453
        }
454
455 93
        switch ($this->operation) {
456 93
            case self::OPERATION_MAKE_ROOT:
457 6
                if ($this->treeAttribute === null) {
458
                    throw new Exception('Can not move a node as the root when "treeAttribute" is not set.');
459
                }
460 6
                if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) {
461 3
                    $this->treeChange = $this->owner->getAttribute($this->treeAttribute);
462 3
                    $this->owner->setAttribute($this->treeAttribute, $this->owner->getOldAttribute($this->treeAttribute));
463
                }
464 6
                break;
465
466 87
            case self::OPERATION_INSERT_BEFORE:
467 69
            case self::OPERATION_INSERT_AFTER:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
468 36
                if ($this->node->isRoot()) {
0 ignored issues
show
Bug introduced by
The method isRoot does only exist in paulzi\nestedsets\NestedSetsBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
469
                    throw new Exception('Can not move a node before/after root.');
470
                }
471
472 51
            case self::OPERATION_PREPEND_TO:
473 27
            case self::OPERATION_APPEND_TO:
474 84
                if ($this->node->getIsNewRecord()) {
475 6
                    throw new Exception('Can not move a node when the target node is new record.');
476
                }
477
478 78
                if ($this->owner->equals($this->node)) {
0 ignored issues
show
Bug introduced by
It seems like $this->node can also be of type null or object<paulzi\nestedsets\NestedSetsBehavior>; however, yii\db\ActiveRecord::equals() does only seem to accept object<yii\db\ActiveRecord>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
479 12
                    throw new Exception('Can not move a node when the target node is same.');
480
                }
481
482 66
                if ($this->node->isChildOf($this->owner)) {
0 ignored issues
show
Bug introduced by
The method isChildOf does only exist in paulzi\nestedsets\NestedSetsBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
483 12
                    throw new Exception('Can not move a node when the target node is child.');
484
                }
485
        }
486 63
    }
487
488
    /**
489
     *
490
     */
491 63
    public function afterUpdate()
492
    {
493 63
        switch ($this->operation) {
494 63
            case self::OPERATION_MAKE_ROOT:
495 6
                if ($this->treeChange || !$this->isRoot() || $this->owner->getIsNewRecord()) {
496 3
                    $this->moveNodeAsRoot();
497
                }
498 6
                break;
499
500 57
            case self::OPERATION_PREPEND_TO:
501 15
                $this->moveNode($this->node->getAttribute($this->leftAttribute) + 1, 1);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
502 15
                break;
503
504 42
            case self::OPERATION_APPEND_TO:
505 15
                $this->moveNode($this->node->getAttribute($this->rightAttribute), 1);
506 15
                break;
507
508 27
            case self::OPERATION_INSERT_BEFORE:
509 12
                $this->moveNode($this->node->getAttribute($this->leftAttribute), 0);
510 12
                break;
511
512 15
            case self::OPERATION_INSERT_AFTER:
513 12
                $this->moveNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
514 12
                break;
515
        }
516 63
        $this->operation  = null;
517 63
        $this->node       = null;
518 63
        $this->treeChange = null;
519 63
    }
520
521
    /**
522
     * @throws Exception
523
     */
524 18
    public function beforeDelete()
525
    {
526 18
        if ($this->owner->getIsNewRecord()) {
527 6
            throw new Exception('Can not delete a node when it is new record.');
528
        }
529 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
530 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
531
        }
532 9
        $this->owner->refresh();
533 9
    }
534
535
    /**
536
     *
537
     */
538 9
    public function afterDelete()
539
    {
540 9
        $left  = $this->owner->getAttribute($this->leftAttribute);
541 9
        $right = $this->owner->getAttribute($this->rightAttribute);
542 9
        if ($this->operation === static::OPERATION_DELETE_ALL || $this->isLeaf()) {
543 6
            $this->shift($right + 1, null, $left - $right - 1);
544
        } else {
545 3
            $this->owner->updateAll(
546
                [
547 3
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]] - 1"),
548 3
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]] - 1"),
549 3
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - 1"),
550
                ],
551 3
                $this->getDescendants()->where
552
            );
553 3
            $this->shift($right + 1, null, -2);
554
        }
555 9
        $this->operation = null;
556 9
        $this->node      = null;
557 9
    }
558
559
    /**
560
     * @return int
561
     */
562 9
    protected function deleteWithChildrenInternal()
563
    {
564 9
        if (!$this->owner->beforeDelete()) {
565
            return false;
566
        }
567 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
568 6
        $this->owner->setOldAttributes(null);
569 6
        $this->owner->afterDelete();
570 6
        return $result;
571
    }
572
573
    /**
574
     * @param int $to
575
     * @param int $depth
576
     * @throws Exception
577
     */
578 39
    protected function insertNode($to, $depth = 0)
579
    {
580 39
        if ($this->node->getIsNewRecord()) {
0 ignored issues
show
Bug introduced by
The method getIsNewRecord does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
581 12
            throw new Exception('Can not create a node when the target node is new record.');
582
        }
583
584 27
        if ($depth === 0 && $this->node->isRoot()) {
0 ignored issues
show
Bug introduced by
The method isRoot does only exist in paulzi\nestedsets\NestedSetsBehavior, but not in yii\db\ActiveRecord.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
585 3
            throw new Exception('Can not insert a node before/after root.');
586
        }
587 24
        $this->owner->setAttribute($this->leftAttribute,  $to);
588 24
        $this->owner->setAttribute($this->rightAttribute, $to + 1);
589 24
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + $depth);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
590 24
        if ($this->treeAttribute !== null) {
591 24
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
592
        }
593 24
        $this->shift($to, null, 2);
594 24
    }
595
596
    /**
597
     * @param int $to
598
     * @param int $depth
599
     * @throws Exception
600
     */
601 54
    protected function moveNode($to, $depth = 0)
602
    {
603 54
        $left  = $this->owner->getAttribute($this->leftAttribute);
604 54
        $right = $this->owner->getAttribute($this->rightAttribute);
605 54
        $depth = $this->owner->getAttribute($this->depthAttribute) - $this->node->getAttribute($this->depthAttribute) - $depth;
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedsets\NestedSetsBehavior.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
606 54
        if ($this->treeAttribute === null || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) {
607
            // same root
608 42
            $this->owner->updateAll(
609 42
                [$this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]" . sprintf('%+d', $depth))],
610 42
                $this->getDescendants(null, true)->where
611
            );
612 42
            $delta = $right - $left + 1;
613 42
            if ($left >= $to) {
614 24
                $this->shift($to, $left - 1, $delta);
615 24
                $delta = $to - $left;
616
            } else {
617 18
                $this->shift($right + 1, $to - 1, -$delta);
618 18
                $delta = $to - $right - 1;
619
            }
620 42
            $this->owner->updateAll(
621
                [
622 42
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
623 42
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
624 42
                    $this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]"),
625
                ],
626
                [
627 42
                    'and',
628 42
                    $this->getDescendants(null, true)->where,
629 42
                    ['<', $this->depthAttribute, 0],
630
                ]
631
            );
632
        } else {
633
            // move from other root
634 12
            $tree = $this->node->getAttribute($this->treeAttribute);
635 12
            $this->shift($to, null, $right - $left + 1, $tree);
636 12
            $delta = $to - $left;
637 12
            $this->owner->updateAll(
638
                [
639 12
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
640 12
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
641 12
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
642 12
                    $this->treeAttribute  => $tree,
643
                ],
644 12
                $this->getDescendants(null, true)->where
645
            );
646 12
            $this->shift($right + 1, null, $left - $right - 1);
647
        }
648 54
    }
649
650
    /**
651
     *
652
     */
653 3
    protected function moveNodeAsRoot()
654
    {
655 3
        $left   = $this->owner->getAttribute($this->leftAttribute);
656 3
        $right  = $this->owner->getAttribute($this->rightAttribute);
657 3
        $depth  = $this->owner->getAttribute($this->depthAttribute);
658 3
        $tree   = $this->treeChange ? $this->treeChange : $this->owner->getPrimaryKey();
659
660 3
        $this->owner->updateAll(
661
            [
662 3
                $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', 1 - $left)),
663 3
                $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', 1 - $left)),
664 3
                $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
665 3
                $this->treeAttribute  => $tree,
666
            ],
667 3
            $this->getDescendants(null, true)->where
668
        );
669 3
        $this->shift($right + 1, null, $left - $right - 1);
670 3
    }
671
672
673
674
    /**
675
     * @param int $from
676
     * @param int $to
677
     * @param int $delta
678
     * @param int|null $tree
679
     */
680 90
    protected function shift($from, $to, $delta, $tree = null)
681
    {
682 90
        if ($delta !== 0 && ($to === null || $to >= $from)) {
683 78 View Code Duplication
            if ($this->treeAttribute !== null && $tree === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
684 78
                $tree = $this->owner->getAttribute($this->treeAttribute);
685
            }
686 78
            foreach ([$this->leftAttribute, $this->rightAttribute] as $i => $attribute) {
687 78
                $this->owner->updateAll(
688 78
                    [$attribute => new Expression("[[{$attribute}]]" . sprintf('%+d', $delta))],
689
                    [
690 78
                        'and',
691 78
                        $to === null ? ['>=', $attribute, $from] : ['between', $attribute, $from, $to],
692 78
                        $this->treeAttribute !== null ? [$this->treeAttribute => $tree] : [],
693
                    ]
694
                );
695
            }
696
        }
697 90
    }
698
699
    /**
700
     * @return array
701
     */
702 99
    protected function treeCondition()
703
    {
704 99
        $tableName = $this->owner->tableName();
705 99
        if ($this->treeAttribute === null) {
706 84
            return [];
707
        } else {
708 96
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
709
        }
710
    }
711
}
712