Completed
Push — master ( 2d5aaa...78d1ae )
by Pavel
06:14
created

NestedSetsBehavior::populateTree()   D

Complexity

Conditions 9
Paths 40

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 9

Importance

Changes 0
Metric Value
dl 0
loc 43
ccs 33
cts 33
cp 1
rs 4.909
c 0
b 0
f 0
cc 9
eloc 28
nc 40
nop 1
crap 9
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 198
    public function events()
74
    {
75
        return [
76 198
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeInsert',
77 198
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterInsert',
78 198
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeUpdate',
79 198
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterUpdate',
80 198
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
81 198
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
82 198
        ];
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 6
        ];
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 6
        }
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 78
        ];
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 12
        }
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 3
        } 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 3
            }
239
240 3
            $key = end($parents);
241 3
            if (!isset($relates[$key])) {
242 3
                $relates[$key] = [];
243 3
            }
244 3
            $relates[$key][] = $node;
245
246 3
            $parents[] = $node->getAttribute($this->leftAttribute);
247 3
            $prev = $level;
248 3
        }
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 3
            }
259 3
        }
260
261 3
        return $this->owner;
262
    }
263
264
    /**
265
     * @return bool
266
     */
267 66
    public function isRoot()
268
    {
269 66
        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 6
        }
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 9
    public function makeRoot()
300
    {
301 9
        $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 9
        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 27
        }
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->findOne($condition) !== 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 3
            default:
422 3
                throw new NotSupportedException('Method "'. $this->owner->className() . '::insert" is not supported for inserting new nodes.');
423 30
        }
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.');
438
            }
439
440 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
441 3
        }
442 27
        $this->operation = null;
443 27
        $this->node      = null;
444 27
    }
445
446
    /**
447
     * @throws Exception
448
     */
449 90
    public function beforeUpdate()
450
    {
451 90
        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 78
        }
454
455 90
        switch ($this->operation) {
456 90
            case self::OPERATION_MAKE_ROOT:
457 3
                if ($this->treeAttribute === null) {
458
                    throw new Exception('Can not move a node as the root when "treeAttribute" is not set.');
459
                }
460 3
                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 3
                }
464 3
                break;
465
466 87
            case self::OPERATION_INSERT_BEFORE:
467 87
            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 87
            case self::OPERATION_PREPEND_TO:
473 87
            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 60
        }
486 60
    }
487
488
    /**
489
     *
490
     */
491 60
    public function afterUpdate()
492
    {
493 60
        switch ($this->operation) {
494 60
            case self::OPERATION_MAKE_ROOT:
495 3
                $this->moveNodeAsRoot();
496 3
                break;
497
498 57
            case self::OPERATION_PREPEND_TO:
499 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...
500 15
                break;
501
502 42
            case self::OPERATION_APPEND_TO:
503 15
                $this->moveNode($this->node->getAttribute($this->rightAttribute), 1);
504 15
                break;
505
506 27
            case self::OPERATION_INSERT_BEFORE:
507 12
                $this->moveNode($this->node->getAttribute($this->leftAttribute), 0);
508 12
                break;
509
510 15
            case self::OPERATION_INSERT_AFTER:
511 12
                $this->moveNode($this->node->getAttribute($this->rightAttribute) + 1, 0);
512 12
                break;
513 60
        }
514 60
        $this->operation  = null;
515 60
        $this->node       = null;
516 60
        $this->treeChange = null;
517 60
    }
518
519
    /**
520
     * @throws Exception
521
     */
522 18
    public function beforeDelete()
523
    {
524 18
        if ($this->owner->getIsNewRecord()) {
525 6
            throw new Exception('Can not delete a node when it is new record.');
526
        }
527 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
528 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
529
        }
530 9
        $this->owner->refresh();
531 9
    }
532
533
    /**
534
     *
535
     */
536 9
    public function afterDelete()
537
    {
538 9
        $left  = $this->owner->getAttribute($this->leftAttribute);
539 9
        $right = $this->owner->getAttribute($this->rightAttribute);
540 9
        if ($this->operation === static::OPERATION_DELETE_ALL || $this->isLeaf()) {
541 6
            $this->shift($right + 1, null, $left - $right - 1);
542 6
        } else {
543 3
            $this->owner->updateAll(
544
                [
545 3
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]] - 1"),
546 3
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]] - 1"),
547 3
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - 1"),
548 3
                ],
549 3
                $this->getDescendants()->where
550 3
            );
551 3
            $this->shift($right + 1, null, -2);
552
        }
553 9
        $this->operation = null;
554 9
        $this->node      = null;
555 9
    }
556
557
    /**
558
     * @return int
559
     */
560 9
    protected function deleteWithChildrenInternal()
561
    {
562 9
        if (!$this->owner->beforeDelete()) {
563
            return false;
564
        }
565 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
566 6
        $this->owner->setOldAttributes(null);
567 6
        $this->owner->afterDelete();
568 6
        return $result;
569
    }
570
571
    /**
572
     * @param int $to
573
     * @param int $depth
574
     * @throws Exception
575
     */
576 39
    protected function insertNode($to, $depth = 0)
577
    {
578 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...
579 12
            throw new Exception('Can not create a node when the target node is new record.');
580
        }
581
582 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...
583 3
            throw new Exception('Can not insert a node before/after root.');
584
        }
585 24
        $this->owner->setAttribute($this->leftAttribute,  $to);
586 24
        $this->owner->setAttribute($this->rightAttribute, $to + 1);
587 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...
588 24
        if ($this->treeAttribute !== null) {
589 24
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
590 24
        }
591 24
        $this->shift($to, null, 2);
592 24
    }
593
594
    /**
595
     * @param int $to
596
     * @param int $depth
597
     * @throws Exception
598
     */
599 78
    protected function moveNode($to, $depth = 0)
600
    {
601 54
        $left  = $this->owner->getAttribute($this->leftAttribute);
602 54
        $right = $this->owner->getAttribute($this->rightAttribute);
603 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...
604 54
        if ($this->treeAttribute === null || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) {
605
            // same root
606 42
            $this->owner->updateAll(
607 42
                [$this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]" . sprintf('%+d', $depth))],
608 42
                $this->getDescendants(null, true)->where
609 42
            );
610 42
            $delta = $right - $left + 1;
611 42
            if ($left >= $to) {
612 24
                $this->shift($to, $left - 1, $delta);
613 24
                $delta = $to - $left;
614 24
            } else {
615 18
                $this->shift($right + 1, $to - 1, -$delta);
616 18
                $delta = $to - $right - 1;
617
            }
618 42
            $this->owner->updateAll(
619
                [
620 42
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
621 42
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
622 42
                    $this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]"),
623 42
                ],
624
                [
625 42
                    'and',
626 42
                    $this->getDescendants(null, true)->where,
627 42
                    ['<', $this->depthAttribute, 0],
628
                ]
629 78
            );
630 42
        } else {
631
            // move from other root
632 12
            $tree = $this->node->getAttribute($this->treeAttribute);
633 12
            $this->shift($to, null, $right - $left + 1, $tree);
634 12
            $delta = $to - $left;
635 12
            $this->owner->updateAll(
636
                [
637 12
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
638 12
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
639 12
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
640 12
                    $this->treeAttribute  => $tree,
641 12
                ],
642 12
                $this->getDescendants(null, true)->where
643 12
            );
644 12
            $this->shift($right + 1, null, $left - $right - 1);
645
        }
646 54
    }
647
648
    /**
649
     *
650
     */
651 3
    protected function moveNodeAsRoot()
652
    {
653 3
        $left   = $this->owner->getAttribute($this->leftAttribute);
654 3
        $right  = $this->owner->getAttribute($this->rightAttribute);
655 3
        $depth  = $this->owner->getAttribute($this->depthAttribute);
656 3
        $tree   = $this->treeChange ? $this->treeChange : $this->owner->getPrimaryKey();
657
658 3
        $this->owner->updateAll(
659
            [
660 3
                $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', 1 - $left)),
661 3
                $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', 1 - $left)),
662 3
                $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depth)),
663 3
                $this->treeAttribute  => $tree,
664 3
            ],
665 3
            $this->getDescendants(null, true)->where
666 3
        );
667 3
        $this->shift($right + 1, null, $left - $right - 1);
668 3
    }
669
670
671
672
    /**
673
     * @param int $from
674
     * @param int $to
675
     * @param int $delta
676
     * @param int|null $tree
677
     */
678 90
    protected function shift($from, $to, $delta, $tree = null)
679
    {
680 90
        if ($delta !== 0 && ($to === null || $to >= $from)) {
681 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...
682 78
                $tree = $this->owner->getAttribute($this->treeAttribute);
683 78
            }
684 78
            foreach ([$this->leftAttribute, $this->rightAttribute] as $i => $attribute) {
685 78
                $this->owner->updateAll(
686 78
                    [$attribute => new Expression("[[{$attribute}]]" . sprintf('%+d', $delta))],
687
                    [
688 78
                        'and',
689 78
                        $to === null ? ['>=', $attribute, $from] : ['between', $attribute, $from, $to],
690 78
                        $this->treeAttribute !== null ? [$this->treeAttribute => $tree] : [],
691
                    ]
692 78
                );
693 78
            }
694 78
        }
695 90
    }
696
697
    /**
698
     * @return array
699
     */
700 99
    protected function treeCondition()
701
    {
702 99
        $tableName = $this->owner->tableName();
703 99
        if ($this->treeAttribute === null) {
704 84
            return [];
705
        } else {
706 96
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
707
        }
708
    }
709
}
710