Completed
Push — master ( 76fead...d8350a )
by Pavel
08:18
created

NestedSetsBehavior::preDeleteWithChildren()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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