NestedSetsBehavior   F
last analyzed

Complexity

Total Complexity 108

Size/Duplication

Total Lines 689
Duplicated Lines 7.4 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 94.79%

Importance

Changes 0
Metric Value
wmc 108
lcom 1
cbo 8
dl 51
loc 689
ccs 309
cts 326
cp 0.9479
rs 1.911
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
     * @param string|array $with = null
219
     * @return static
220
     */
221 3
    public function populateTree($depth = null, $with = null)
222
    {
223
        /** @var ActiveRecord[]|static[] $nodes */
224 3
        $query = $this->getDescendants($depth);
225 3
        if ($with) {
226
            $query->with($with);
227
        }
228 3
        $nodes = $query->all();
229
230 3
        $key = $this->owner->getAttribute($this->leftAttribute);
231 3
        $relates = [];
232 3
        $parents = [$key];
233 3
        $prev = $this->owner->getAttribute($this->depthAttribute);
234 3
        foreach($nodes as $node)
235
        {
236 3
            $level = $node->getAttribute($this->depthAttribute);
237 3
            if ($level <= $prev) {
238 3
                $parents = array_slice($parents, 0, $level - $prev - 1);
239
            }
240
241 3
            $key = end($parents);
242 3
            if (!isset($relates[$key])) {
243 3
                $relates[$key] = [];
244
            }
245 3
            $relates[$key][] = $node;
246
247 3
            $parents[] = $node->getAttribute($this->leftAttribute);
248 3
            $prev = $level;
249
        }
250
251 3
        $ownerDepth = $this->owner->getAttribute($this->depthAttribute);
252 3
        $nodes[] = $this->owner;
253 3
        foreach ($nodes as $node) {
254 3
            $key = $node->getAttribute($this->leftAttribute);
255 3
            if (isset($relates[$key])) {
256 3
                $node->populateRelation('children', $relates[$key]);
257 3
            } elseif ($depth === null || $ownerDepth + $depth > $node->getAttribute($this->depthAttribute)) {
258 3
                $node->populateRelation('children', []);
259
            }
260
        }
261
262 3
        return $this->owner;
263
    }
264
265
    /**
266
     * @return bool
267
     */
268 72
    public function isRoot()
269
    {
270 72
        return $this->owner->getAttribute($this->leftAttribute) === 1;
271
    }
272
273
    /**
274
     * @param ActiveRecord $node
275
     * @return bool
276
     */
277 69
    public function isChildOf($node)
278
    {
279 69
        $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute)
280 69
            && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute);
281
282 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...
283 6
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
284
        }
285
286 69
        return $result;
287
    }
288
289
    /**
290
     * @return bool
291
     */
292 6
    public function isLeaf()
293
    {
294 6
        return $this->owner->getAttribute($this->rightAttribute) - $this->owner->getAttribute($this->leftAttribute) === 1;
295
    }
296
297
    /**
298
     * @return ActiveRecord
299
     */
300 12
    public function makeRoot()
301
    {
302 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...
303 12
        return $this->owner;
304
    }
305
306
    /**
307
     * @param ActiveRecord $node
308
     * @return ActiveRecord
309
     */
310 33
    public function prependTo($node)
311
    {
312 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...
313 33
        $this->node = $node;
314 33
        return $this->owner;
315
    }
316
317
    /**
318
     * @param ActiveRecord $node
319
     * @return ActiveRecord
320
     */
321 33
    public function appendTo($node)
322
    {
323 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...
324 33
        $this->node = $node;
325 33
        return $this->owner;
326
    }
327
328
    /**
329
     * @param ActiveRecord $node
330
     * @return ActiveRecord
331
     */
332 27
    public function insertBefore($node)
333
    {
334 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...
335 27
        $this->node = $node;
336 27
        return $this->owner;
337
    }
338
339
    /**
340
     * @param ActiveRecord $node
341
     * @return ActiveRecord
342
     */
343 30
    public function insertAfter($node)
344
    {
345 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...
346 30
        $this->node = $node;
347 30
        return $this->owner;
348
    }
349
350
    /**
351
     * Need for paulzi/auto-tree
352
     */
353
    public function preDeleteWithChildren()
354
    {
355
        $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...
356
    }
357
358
    /**
359
     * @return bool|int
360
     * @throws \Exception
361
     * @throws \yii\db\Exception
362
     */
363 9
    public function deleteWithChildren()
364
    {
365 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...
366 9
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
367
            $transaction = $this->owner->getDb()->beginTransaction();
368
            try {
369
                $result = $this->deleteWithChildrenInternal();
370
                if ($result === false) {
371
                    $transaction->rollBack();
372
                } else {
373
                    $transaction->commit();
374
                }
375
                return $result;
376
            } catch (\Exception $e) {
377
                $transaction->rollBack();
378
                throw $e;
379
            }
380
        } else {
381 9
            $result = $this->deleteWithChildrenInternal();
382
        }
383 6
        return $result;
384
    }
385
386
    /**
387
     * @throws Exception
388
     * @throws NotSupportedException
389
     */
390 48
    public function beforeInsert()
391
    {
392 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...
393 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...
394
        }
395 48
        switch ($this->operation) {
396 48
            case self::OPERATION_MAKE_ROOT:
397 6
                $condition = array_merge([$this->leftAttribute => 1], $this->treeCondition());
398 6
                if ($this->owner->find()->andWhere($condition)->one() !== null) {
399 3
                    throw new Exception('Can not create more than one root.');
400
                }
401 3
                $this->owner->setAttribute($this->leftAttribute,  1);
402 3
                $this->owner->setAttribute($this->rightAttribute, 2);
403 3
                $this->owner->setAttribute($this->depthAttribute, 0);
404 3
                break;
405
406 42
            case self::OPERATION_PREPEND_TO:
407 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...
408 6
                break;
409
410 33
            case self::OPERATION_APPEND_TO:
411 9
                $this->insertNode($this->node->getAttribute($this->rightAttribute), 1);
412 6
                break;
413
414 24
            case self::OPERATION_INSERT_BEFORE:
415 9
                $this->insertNode($this->node->getAttribute($this->leftAttribute), 0);
416 6
                break;
417
418 15
            case self::OPERATION_INSERT_AFTER:
419 12
                $this->insertNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
420 6
                break;
421
422
            default:
423 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...
424
        }
425 27
    }
426
427
    /**
428
     * @throws Exception
429
     */
430 27
    public function afterInsert()
431
    {
432 27
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
433 3
            $id = $this->owner->getPrimaryKey();
434 3
            $this->owner->setAttribute($this->treeAttribute, $id);
435
436 3
            $primaryKey = $this->owner->primaryKey();
437 3
            if (!isset($primaryKey[0])) {
438
                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...
439
            }
440
441 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
442
        }
443 27
        $this->operation = null;
444 27
        $this->node      = null;
445 27
    }
446
447
    /**
448
     * @throws Exception
449
     */
450 93
    public function beforeUpdate()
451
    {
452 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...
453 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...
454
        }
455
456 93
        switch ($this->operation) {
457 93
            case self::OPERATION_MAKE_ROOT:
458 6
                if ($this->treeAttribute === null) {
459
                    throw new Exception('Can not move a node as the root when "treeAttribute" is not set.');
460
                }
461 6
                if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) {
462 3
                    $this->treeChange = $this->owner->getAttribute($this->treeAttribute);
463 3
                    $this->owner->setAttribute($this->treeAttribute, $this->owner->getOldAttribute($this->treeAttribute));
464
                }
465 6
                break;
466
467 87
            case self::OPERATION_INSERT_BEFORE:
468 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...
469 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...
470
                    throw new Exception('Can not move a node before/after root.');
471
                }
472
473 51
            case self::OPERATION_PREPEND_TO:
474 27
            case self::OPERATION_APPEND_TO:
475 84
                if ($this->node->getIsNewRecord()) {
476 6
                    throw new Exception('Can not move a node when the target node is new record.');
477
                }
478
479 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...
480 12
                    throw new Exception('Can not move a node when the target node is same.');
481
                }
482
483 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...
484 12
                    throw new Exception('Can not move a node when the target node is child.');
485
                }
486
        }
487 63
    }
488
489
    /**
490
     *
491
     */
492 63
    public function afterUpdate()
493
    {
494 63
        switch ($this->operation) {
495 63
            case self::OPERATION_MAKE_ROOT:
496 6
                if ($this->treeChange || !$this->isRoot() || $this->owner->getIsNewRecord()) {
497 3
                    $this->moveNodeAsRoot();
498
                }
499 6
                break;
500
501 57
            case self::OPERATION_PREPEND_TO:
502 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...
503 15
                break;
504
505 42
            case self::OPERATION_APPEND_TO:
506 15
                $this->moveNode($this->node->getAttribute($this->rightAttribute), 1);
507 15
                break;
508
509 27
            case self::OPERATION_INSERT_BEFORE:
510 12
                $this->moveNode($this->node->getAttribute($this->leftAttribute), 0);
511 12
                break;
512
513 15
            case self::OPERATION_INSERT_AFTER:
514 12
                $this->moveNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
515 12
                break;
516
        }
517 63
        $this->operation  = null;
518 63
        $this->node       = null;
519 63
        $this->treeChange = null;
520 63
    }
521
522
    /**
523
     * @throws Exception
524
     */
525 18
    public function beforeDelete()
526
    {
527 18
        if ($this->owner->getIsNewRecord()) {
528 6
            throw new Exception('Can not delete a node when it is new record.');
529
        }
530 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
531 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...
532
        }
533 9
        $this->owner->refresh();
534 9
    }
535
536
    /**
537
     *
538
     */
539 9
    public function afterDelete()
540
    {
541 9
        $left  = $this->owner->getAttribute($this->leftAttribute);
542 9
        $right = $this->owner->getAttribute($this->rightAttribute);
543 9
        if ($this->operation === static::OPERATION_DELETE_ALL || $this->isLeaf()) {
544 6
            $this->shift($right + 1, null, $left - $right - 1);
545
        } else {
546 3
            $this->owner->updateAll(
547
                [
548 3
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]] - 1"),
549 3
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]] - 1"),
550 3
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - 1"),
551
                ],
552 3
                $this->getDescendants()->where
553
            );
554 3
            $this->shift($right + 1, null, -2);
555
        }
556 9
        $this->operation = null;
557 9
        $this->node      = null;
558 9
    }
559
560
    /**
561
     * @return int
562
     */
563 9
    protected function deleteWithChildrenInternal()
564
    {
565 9
        if (!$this->owner->beforeDelete()) {
566
            return false;
567
        }
568 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
569 6
        $this->owner->setOldAttributes(null);
570 6
        $this->owner->afterDelete();
571 6
        return $result;
572
    }
573
574
    /**
575
     * @param int $to
576
     * @param int $depth
577
     * @throws Exception
578
     */
579 39
    protected function insertNode($to, $depth = 0)
580
    {
581 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...
582 12
            throw new Exception('Can not create a node when the target node is new record.');
583
        }
584
585 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...
586 3
            throw new Exception('Can not insert a node before/after root.');
587
        }
588 24
        $this->owner->setAttribute($this->leftAttribute,  $to);
589 24
        $this->owner->setAttribute($this->rightAttribute, $to + 1);
590 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...
591 24
        if ($this->treeAttribute !== null) {
592 24
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
593
        }
594 24
        $this->shift($to, null, 2);
595 24
    }
596
597
    /**
598
     * @param int $to
599
     * @param int $depth
600
     * @throws Exception
601
     */
602 54
    protected function moveNode($to, $depth = 0)
603
    {
604 54
        $left  = $this->owner->getAttribute($this->leftAttribute);
605 54
        $right = $this->owner->getAttribute($this->rightAttribute);
606 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...
607 54
        if ($this->treeAttribute === null || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) {
608
            // same root
609 42
            $this->owner->updateAll(
610 42
                [$this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]" . sprintf('%+d', $depth))],
611 42
                $this->getDescendants(null, true)->where
612
            );
613 42
            $delta = $right - $left + 1;
614 42
            if ($left >= $to) {
615 24
                $this->shift($to, $left - 1, $delta);
616 24
                $delta = $to - $left;
617
            } else {
618 18
                $this->shift($right + 1, $to - 1, -$delta);
619 18
                $delta = $to - $right - 1;
620
            }
621 42
            $this->owner->updateAll(
622
                [
623 42
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
624 42
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
625 42
                    $this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]"),
626
                ],
627
                [
628 42
                    'and',
629 42
                    $this->getDescendants(null, true)->where,
630 42
                    ['<', $this->depthAttribute, 0],
631
                ]
632
            );
633
        } else {
634
            // move from other root
635 12
            $tree = $this->node->getAttribute($this->treeAttribute);
636 12
            $this->shift($to, null, $right - $left + 1, $tree);
637 12
            $delta = $to - $left;
638 12
            $this->owner->updateAll(
639
                [
640 12
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
641 12
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
642 12
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
643 12
                    $this->treeAttribute  => $tree,
644
                ],
645 12
                $this->getDescendants(null, true)->where
646
            );
647 12
            $this->shift($right + 1, null, $left - $right - 1);
648
        }
649 54
    }
650
651
    /**
652
     *
653
     */
654 3
    protected function moveNodeAsRoot()
655
    {
656 3
        $left   = $this->owner->getAttribute($this->leftAttribute);
657 3
        $right  = $this->owner->getAttribute($this->rightAttribute);
658 3
        $depth  = $this->owner->getAttribute($this->depthAttribute);
659 3
        $tree   = $this->treeChange ? $this->treeChange : $this->owner->getPrimaryKey();
660
661 3
        $this->owner->updateAll(
662
            [
663 3
                $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', 1 - $left)),
664 3
                $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', 1 - $left)),
665 3
                $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
666 3
                $this->treeAttribute  => $tree,
667
            ],
668 3
            $this->getDescendants(null, true)->where
669
        );
670 3
        $this->shift($right + 1, null, $left - $right - 1);
671 3
    }
672
673
674
675
    /**
676
     * @param int $from
677
     * @param int $to
678
     * @param int $delta
679
     * @param int|null $tree
680
     */
681 90
    protected function shift($from, $to, $delta, $tree = null)
682
    {
683 90
        if ($delta !== 0 && ($to === null || $to >= $from)) {
684 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...
685 78
                $tree = $this->owner->getAttribute($this->treeAttribute);
686
            }
687 78
            foreach ([$this->leftAttribute, $this->rightAttribute] as $i => $attribute) {
688 78
                $this->owner->updateAll(
689 78
                    [$attribute => new Expression("[[{$attribute}]]" . sprintf('%+d', $delta))],
690
                    [
691 78
                        'and',
692 78
                        $to === null ? ['>=', $attribute, $from] : ['between', $attribute, $from, $to],
693 78
                        $this->treeAttribute !== null ? [$this->treeAttribute => $tree] : [],
694
                    ]
695
                );
696
            }
697
        }
698 90
    }
699
700
    /**
701
     * @return array
702
     */
703 99
    protected function treeCondition()
704
    {
705 99
        $tableName = $this->owner->tableName();
706 99
        if ($this->treeAttribute === null) {
707 84
            return [];
708
        } else {
709 96
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
710
        }
711
    }
712
}
713