Completed
Push — master ( 81e540...5a2aa2 )
by Pavel
15:03
created

NestedIntervalsBehavior::findUnallocatedAll()   B

Complexity

Conditions 10
Paths 32

Size

Total Lines 16
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 10

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 16
ccs 13
cts 13
cp 1
rs 7.2765
cc 10
eloc 13
nc 32
nop 2
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link https://github.com/paulzi/yii2-nested-intervals
4
 * @copyright Copyright (c) 2015 PaulZi <[email protected]>
5
 * @license MIT (https://github.com/paulzi/yii2-nested-intervals/blob/master/LICENSE)
6
 */
7
8
namespace paulzi\nestedintervals;
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
use yii\db\Query;
17
18
/**
19
 * Nested Intervals Behavior for Yii2
20
 * @author PaulZi <[email protected]>
21
 *
22
 * @property ActiveRecord $owner
23
 */
24
class NestedIntervalsBehavior extends Behavior
25
{
26
27
    const OPERATION_MAKE_ROOT       = 1;
28
    const OPERATION_PREPEND_TO      = 2;
29
    const OPERATION_APPEND_TO       = 3;
30
    const OPERATION_INSERT_BEFORE   = 4;
31
    const OPERATION_INSERT_AFTER    = 5;
32
    const OPERATION_DELETE_ALL      = 6;
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 int[]
56
     */
57
    public $range = [0, 2147483647];
58
59
    /**
60
     * @var int|int[] Average amount of children in each level
61
     */
62
    public $amountOptimize = 10;
63
64
    /**
65
     * @var float
66
     */
67
    public $reserveFactor = 1;
68
69
    /**
70
     * @var bool
71
     */
72
    public $noPrepend = false;
73
74
    /**
75
     * @var bool
76
     */
77
    public $noAppend = false;
78
79
    /**
80
     * @var bool
81
     */
82
    public $noInsert = false;
83
84
    /**
85
     * @var string|null
86
     */
87
    protected $operation;
88
89
    /**
90
     * @var ActiveRecord|self|null
91
     */
92
    protected $node;
93
94
    /**
95
     * @var string
96
     */
97
    protected $treeChange;
98
99
100
    /**
101
     * @inheritdoc
102
     */
103 299
    public function events()
104
    {
105
        return [
106 299
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeInsert',
107 299
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterInsert',
108 299
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeUpdate',
109 299
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterUpdate',
110 299
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
111 299
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
112 299
        ];
113
    }
114
115
    /**
116
     * @param int|null $depth
117
     * @return \yii\db\ActiveQuery
118
     */
119 73
    public function getParents($depth = null)
120
    {
121 73
        $tableName = $this->owner->tableName();
122
        $condition = [
123 73
            'and',
124 73
            ['<', "{$tableName}.[[{$this->leftAttribute}]]",  $this->owner->getAttribute($this->leftAttribute)],
125 73
            ['>', "{$tableName}.[[{$this->rightAttribute}]]", $this->owner->getAttribute($this->rightAttribute)],
126 73
        ];
127 73 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...
128 73
            $condition[] = ['>=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) - $depth];
129 73
        }
130
131 73
        $query = $this->owner->find()
132 73
            ->andWhere($condition)
133 73
            ->andWhere($this->treeCondition())
134 73
            ->addOrderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_ASC]);
135 73
        $query->multiple = true;
136
137 73
        return $query;
138
    }
139
140
    /**
141
     * @return \yii\db\ActiveQuery
142
     */
143 68
    public function getParent()
144
    {
145 68
        $tableName = $this->owner->tableName();
146 68
        $query = $this->getParents(1)
147 68
            ->orderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_DESC])
148 68
            ->limit(1);
149 68
        $query->multiple = false;
150 68
        return $query;
151
    }
152
153
    /**
154
     * @return \yii\db\ActiveQuery
155
     */
156 21
    public function getRoot()
157
    {
158 21
        $tableName = $this->owner->tableName();
159 21
        $query = $this->owner->find()
160 21
            ->andWhere(["{$tableName}.[[{$this->leftAttribute}]]" => $this->range[0]])
161 21
            ->andWhere($this->treeCondition())
162 21
            ->limit(1);
163 21
        $query->multiple = false;
164 21
        return $query;
165
    }
166
167
    /**
168
     * @param int|null $depth
169
     * @param bool $andSelf
170
     * @param bool $backOrder
171
     * @return \yii\db\ActiveQuery
172
     */
173 174
    public function getDescendants($depth = null, $andSelf = false, $backOrder = false)
174
    {
175 174
        $tableName = $this->owner->tableName();
176 174
        $attribute = $backOrder ? $this->rightAttribute : $this->leftAttribute;
177
        $condition = [
178 174
            'and',
179 174
            [$andSelf ? '>=' : '>', "{$tableName}.[[{$attribute}]]",  $this->owner->getAttribute($this->leftAttribute)],
180 174
            [$andSelf ? '<=' : '<', "{$tableName}.[[{$attribute}]]",  $this->owner->getAttribute($this->rightAttribute)],
181 174
        ];
182
183 174 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...
184 129
            $condition[] = ['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth];
185 129
        }
186
187 174
        $query = $this->owner->find()
188 174
            ->andWhere($condition)
189 174
            ->andWhere($this->treeCondition())
190 174
            ->addOrderBy(["{$tableName}.[[{$attribute}]]" => $backOrder ? SORT_DESC : SORT_ASC]);
191 174
        $query->multiple = true;
192
193 174
        return $query;
194 1
    }
195
196
    /**
197
     * @return \yii\db\ActiveQuery
198
     */
199 114
    public function getChildren()
200
    {
201 114
        return $this->getDescendants(1);
202
    }
203
204
    /**
205
     * @param int|null $depth
206
     * @return \yii\db\ActiveQuery
207
     */
208 5
    public function getLeaves($depth = null)
209
    {
210 5
        $tableName = $this->owner->tableName();
211
        $condition = [
212 5
            'and',
213 5
            ['>', "leaves.[[{$this->leftAttribute}]]",  new Expression("{$tableName}.[[{$this->leftAttribute}]]")],
214 5
            ['<', "leaves.[[{$this->leftAttribute}]]",  new Expression("{$tableName}.[[{$this->rightAttribute}]]")],
215 5
        ];
216
217 5
        if ($this->treeAttribute !== null) {
218 5
            $condition[] = ["leaves.[[{$this->treeAttribute}]]" => new Expression("{$tableName}.[[{$this->treeAttribute}]]")];
219 5
        }
220
221 5
        $query = $this->getDescendants($depth)
222 5
            ->leftJoin("{$tableName} leaves", $condition)
223 5
            ->andWhere(["leaves.[[{$this->leftAttribute}]]" => null]);
224 5
        $query->multiple = true;
225 5
        return $query;
226
    }
227
228
    /**
229
     * @return \yii\db\ActiveQuery
230
     */
231 30 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...
232
    {
233 30
        $tableName = $this->owner->tableName();
234 30
        $query = $this->owner->find()
235 30
            ->andWhere([
236 30
                'and',
237 30
                ['<', "{$tableName}.[[{$this->rightAttribute}]]", $this->owner->getAttribute($this->leftAttribute)],
238 30
                ['>', "{$tableName}.[[{$this->rightAttribute}]]", $this->getParent()->select([$this->leftAttribute])],
239 30
            ])
240 30
            ->andWhere($this->treeCondition())
241 30
            ->orderBy(["{$tableName}.[[{$this->rightAttribute}]]" => SORT_DESC])
242 30
            ->limit(1);
243 30
        $query->multiple = false;
244 30
        return $query;
245
    }
246
247
    /**
248
     * @return \yii\db\ActiveQuery
249
     */
250 33 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...
251
    {
252 33
        $tableName = $this->owner->tableName();
253 33
        $query = $this->owner->find()
254 33
            ->andWhere([
255 33
                'and',
256 33
                ['>', "{$tableName}.[[{$this->leftAttribute}]]", $this->owner->getAttribute($this->rightAttribute)],
257 33
                ['<', "{$tableName}.[[{$this->leftAttribute}]]", $this->getParent()->select([$this->rightAttribute])],
258 33
            ])
259 33
            ->andWhere($this->treeCondition())
260 33
            ->orderBy(["{$tableName}.[[{$this->leftAttribute}]]" => SORT_ASC])
261 33
            ->limit(1);
262 33
        $query->multiple = false;
263 33
        return $query;
264
    }
265
266
    /**
267
     * Populate children relations for self and all descendants
268
     * @param int $depth = null
269
     * @return static
270
     */
271 5
    public function populateTree($depth = null)
272
    {
273
        /** @var ActiveRecord[]|static[] $nodes */
274 5
        if ($depth === null) {
275 5
            $nodes = $this->owner->descendants;
276 5
        } else {
277 5
            $nodes = $this->getDescendants($depth)->all();
278
        }
279
280 5
        $relates = [];
281 5
        $parents = [$this->owner->getAttribute($this->leftAttribute)];
282 5
        $prev = $this->owner->getAttribute($this->depthAttribute);
283 5
        foreach($nodes as $node)
284
        {
285 5
            $depth = $node->getAttribute($this->depthAttribute);
286 5
            if ($depth <= $prev) {
287 5
                $parents = array_slice($parents, 0, $depth - $prev - 1);
288 5
            }
289
290 5
            $key = end($parents);
291 5
            if (!isset($relates[$key])) {
292 5
                $relates[$key] = [];
293 5
            }
294 5
            $relates[$key][] = $node;
295
296 5
            $parents[] = $node->getAttribute($this->leftAttribute);
297 5
            $prev = $depth;
298 5
        }
299
300 5
        $nodes[] = $this->owner;
301 5
        foreach ($nodes as $node) {
302 5
            $key = $node->getAttribute($this->leftAttribute);
303 5
            if (isset($relates[$key])) {
304 5
                $node->populateRelation('children', $relates[$key]);
305 5
            } elseif ($depth === null) {
306 5
                $node->populateRelation('children', []);
307 5
            }
308 5
        }
309
310 5
        return $this->owner;
311
    }
312
313
    /**
314
     * @return bool
315
     */
316 80
    public function isRoot()
317
    {
318 80
        return $this->owner->getAttribute($this->leftAttribute) == $this->range[0];
319
    }
320
321
    /**
322
     * @param ActiveRecord $node
323
     * @return bool
324
     */
325 83
    public function isChildOf($node)
326
    {
327 83
        $result = $this->owner->getAttribute($this->leftAttribute) > $node->getAttribute($this->leftAttribute)
328 83
            && $this->owner->getAttribute($this->rightAttribute) < $node->getAttribute($this->rightAttribute);
329
330 83
        if ($result && $this->treeAttribute !== null) {
331 11
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
332 11
        }
333
334 83
        return $result;
335
    }
336
337
    /**
338
     * @return bool
339
     */
340 5
    public function isLeaf()
341
    {
342 5
        return count($this->owner->children) === 0;
343
    }
344
345
    /**
346
     * @return ActiveRecord
347
     */
348 13
    public function makeRoot()
349
    {
350 13
        $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...
351 13
        return $this->owner;
352
    }
353
354
    /**
355
     * @param ActiveRecord $node
356
     * @return ActiveRecord
357
     */
358 61
    public function prependTo($node)
359
    {
360 61
        $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...
361 61
        $this->node = $node;
362 61
        return $this->owner;
363
    }
364
365
    /**
366
     * @param ActiveRecord $node
367
     * @return ActiveRecord
368
     */
369 61
    public function appendTo($node)
370
    {
371 61
        $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...
372 61
        $this->node = $node;
373 61
        return $this->owner;
374
    }
375
376
    /**
377
     * @param ActiveRecord $node
378
     * @return ActiveRecord
379
     */
380 31
    public function insertBefore($node)
381
    {
382 31
        $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...
383 31
        $this->node = $node;
384 31
        return $this->owner;
385
    }
386
387
    /**
388
     * @param ActiveRecord $node
389
     * @return ActiveRecord
390
     */
391 34
    public function insertAfter($node)
392
    {
393 34
        $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...
394 34
        $this->node = $node;
395 34
        return $this->owner;
396
    }
397
398
    /**
399
     * Need for paulzi/auto-tree
400
     */
401
    public function preDeleteWithChildren()
402
    {
403
        $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...
404
    }
405
406
    /**
407
     * @return bool|int
408
     * @throws \Exception
409
     * @throws \yii\db\Exception
410
     */
411 11
    public function deleteWithChildren()
412
    {
413 11
        $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...
414 11
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
415
            $transaction = $this->owner->getDb()->beginTransaction();
416
            try {
417
                $result = $this->deleteWithChildrenInternal();
418
                if ($result === false) {
419
                    $transaction->rollBack();
420
                } else {
421
                    $transaction->commit();
422
                }
423
                return $result;
424
            } catch (\Exception $e) {
425
                $transaction->rollBack();
426
                throw $e;
427
            }
428
        } else {
429 11
            $result = $this->deleteWithChildrenInternal();
430
        }
431 8
        return $result;
432
    }
433
434
    /**
435
     * @return ActiveRecord
436
     * @throws \Exception
437
     * @throws \yii\db\Exception
438
     */
439 5
    public function optimize()
440
    {
441 5
        if (!$this->owner->isTransactional(ActiveRecord::OP_UPDATE)) {
442
            $transaction = $this->owner->getDb()->beginTransaction();
443
            try {
444
                $this->optimizeInternal();
445
                $transaction->commit();
446
            } catch (\Exception $e) {
447
                $transaction->rollBack();
448
                throw $e;
449
            }
450
        } else {
451 5
            $this->optimizeInternal();
452
        }
453 5
        return $this->owner;
454
    }
455
456
    /**
457
     * @throws Exception
458
     * @throws NotSupportedException
459
     */
460 102
    public function beforeInsert()
461
    {
462 102
        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\nestedintervals\NestedIntervalsBehavior.

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...
463 79
            $this->node->refresh();
0 ignored issues
show
Bug introduced by
The method refresh does only exist in yii\db\ActiveRecord, but not in paulzi\nestedintervals\NestedIntervalsBehavior.

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...
464 79
        }
465 102
        switch ($this->operation) {
466 102
            case self::OPERATION_MAKE_ROOT:
467 8
                $condition = array_merge([$this->leftAttribute => $this->range[0]], $this->treeCondition());
468 8
                if ($this->owner->findOne($condition) !== null) {
469 3
                    throw new Exception('Can not create more than one root.');
470
                }
471 5
                $this->owner->setAttribute($this->leftAttribute,  $this->range[0]);
472 5
                $this->owner->setAttribute($this->rightAttribute, $this->range[1]);
473 5
                $this->owner->setAttribute($this->depthAttribute, 0);
474 5
                break;
475
476 94
            case self::OPERATION_PREPEND_TO:
477 33
                $this->findPrependRange($left, $right);
478 33
                $this->insertNode($left, $right, 1, false);
479 30
                break;
480
481 61
            case self::OPERATION_APPEND_TO:
482 33
                $this->findAppendRange($left, $right);
483 33
                $this->insertNode($left, $right, 1, true);
484 30
                break;
485
486 28
            case self::OPERATION_INSERT_BEFORE:
487 11
                $parent = $this->findInsertBeforeRange($left, $right);
488 11
                $this->insertNode($left, $right, 0, false, $parent);
489 8
                break;
490
491 17
            case self::OPERATION_INSERT_AFTER:
492 14
                $parent = $this->findInsertAfterRange($left, $right);
493 14
                $this->insertNode($left, $right, 0, true, $parent);
494 8
                break;
495
496 3
            default:
497 3
                throw new NotSupportedException('Method "'. $this->owner->className() . '::insert" is not supported for inserting new nodes.');
498 84
        }
499 81
    }
500
501
    /**
502
     * @throws Exception
503
     */
504 81
    public function afterInsert()
505
    {
506 81
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
507 5
            $id = $this->owner->getPrimaryKey();
508 5
            $this->owner->setAttribute($this->treeAttribute, $id);
509 5
            $this->owner->updateAll([$this->treeAttribute => $id], [$this->getPrimaryKey() => $id]);
510 5
        }
511 81
        $this->operation = null;
512 81
        $this->node      = null;
513 81
    }
514
515
    /**
516
     * @throws Exception
517
     */
518 104
    public function beforeUpdate()
519
    {
520 104
        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\nestedintervals\NestedIntervalsBehavior.

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...
521 90
            $this->node->refresh();
0 ignored issues
show
Bug introduced by
The method refresh does only exist in yii\db\ActiveRecord, but not in paulzi\nestedintervals\NestedIntervalsBehavior.

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...
522 90
        }
523
524 104
        switch ($this->operation) {
525 104
            case self::OPERATION_MAKE_ROOT:
526 5
                if ($this->treeAttribute === null) {
527
                    throw new Exception('Can not move a node as the root when "treeAttribute" is not set.');
528
                }
529 5
                if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) {
530 5
                    $this->treeChange = $this->owner->getAttribute($this->treeAttribute);
531 5
                    $this->owner->setAttribute($this->treeAttribute, $this->owner->getOldAttribute($this->treeAttribute));
532 5
                }
533 5
                break;
534
535 99
            case self::OPERATION_INSERT_BEFORE:
536 99
            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...
537 40
                if ($this->node->isRoot()) {
0 ignored issues
show
Bug introduced by
The method isRoot does only exist in paulzi\nestedintervals\NestedIntervalsBehavior, 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...
538
                    throw new Exception('Can not move a node before/after root.');
539
                }
540
541 99
            case self::OPERATION_PREPEND_TO:
542 99
            case self::OPERATION_APPEND_TO:
543 96
                if ($this->node->getIsNewRecord()) {
544 6
                    throw new Exception('Can not move a node when the target node is new record.');
545
                }
546
547 90
                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\nestedinte...estedIntervalsBehavior>; 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...
548 12
                    throw new Exception('Can not move a node when the target node is same.');
549
                }
550
551 78
                if ($this->node->isChildOf($this->owner)) {
0 ignored issues
show
Bug introduced by
The method isChildOf does only exist in paulzi\nestedintervals\NestedIntervalsBehavior, 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...
552 12
                    throw new Exception('Can not move a node when the target node is child.');
553
                }
554 74
        }
555 74
    }
556
557
    /**
558
     *
559
     */
560 74
    public function afterUpdate()
561
    {
562 74
        switch ($this->operation) {
563 74
            case self::OPERATION_MAKE_ROOT:
564 5
                $this->moveNodeAsRoot();
565 5
                break;
566
567 69
            case self::OPERATION_PREPEND_TO:
568 19
                $this->findPrependRange($left, $right);
569 19
                if ($right !== $this->owner->getAttribute($this->leftAttribute)) {
570 16
                    $this->moveNode($left, $right, 1);
571 16
                }
572 19
                break;
573
574 50 View Code Duplication
            case self::OPERATION_APPEND_TO:
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...
575 19
                $this->findAppendRange($left, $right);
576 19
                if ($left !== $this->owner->getAttribute($this->rightAttribute)) {
577 16
                    $this->moveNode($left, $right, 1);
578 16
                }
579 19
                break;
580
581 31 View Code Duplication
            case self::OPERATION_INSERT_BEFORE:
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...
582 14
                $this->findInsertBeforeRange($left, $right);
583 14
                if ($left !== $this->owner->getAttribute($this->rightAttribute)) {
584 11
                    $this->moveNode($left, $right);
585 11
                }
586 14
                break;
587
588 17
            case self::OPERATION_INSERT_AFTER:
589 14
                $this->findInsertAfterRange($left, $right);
590 14
                if ($right !== $this->owner->getAttribute($this->leftAttribute)) {
591 11
                    $this->moveNode($left, $right);
592 11
                }
593 14
                break;
594 74
        }
595 74
        $this->operation = null;
596 74
        $this->node      = null;
597 74
    }
598
599
    /**
600
     * @throws Exception
601
     */
602 22
    public function beforeDelete()
603
    {
604 22
        if ($this->owner->getIsNewRecord()) {
605 6
            throw new Exception('Can not delete a node when it is new record.');
606
        }
607 16
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
608 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
609
        }
610 13
        $this->owner->refresh();
611 13
    }
612
613
    /**
614
     *
615
     */
616 13
    public function afterDelete()
617
    {
618 13
        if ($this->operation !== self::OPERATION_DELETE_ALL) {
619 5
            $this->owner->updateAll([$this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - 1")], $this->getDescendants()->where);
620 5
        }
621 13
        $this->operation = null;
622 13
        $this->node      = null;
623 13
    }
624
625
    /**
626
     * @return mixed
627
     * @throws Exception
628
     */
629 31
    protected function getPrimaryKey()
630
    {
631 31
        $primaryKey = $this->owner->primaryKey();
632 31
        if (!isset($primaryKey[0])) {
633
            throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
634
        }
635 31
        return $primaryKey[0];
636
    }
637
638
    /**
639
     * @return self
640
     */
641 157
    public function getNodeBehavior()
642
    {
643 157
        foreach ($this->node->behaviors as $behavior) {
644 157
            if ($behavior instanceof NestedIntervalsBehavior) {
645 157
                return $behavior;
646
            }
647
        }
648
        return null;
649
    }
650
651
    /**
652
     * @return int
653
     */
654 11
    protected function deleteWithChildrenInternal()
655
    {
656 11
        if (!$this->owner->beforeDelete()) {
657
            return false;
658
        }
659 8
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
660 8
        $this->owner->setOldAttributes(null);
661 8
        $this->owner->afterDelete();
662 8
        return $result;
663
    }
664
665
    /**
666
     * @param int $left
667
     * @param int $right
668
     * @param int $depth
669
     * @param bool $forward
670
     * @param array|null $parent
671
     * @throws Exception
672
     */
673 91
    protected function insertNode($left, $right, $depth = 0, $forward = true, $parent = null)
674
    {
675 91
        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\nestedintervals\NestedIntervalsBehavior.

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...
676 12
            throw new Exception('Can not create a node when the target node is new record.');
677
        }
678
679 79
        if ($depth === 0 && $this->getNodeBehavior()->isRoot()) {
680 3
            throw new Exception('Can not insert a node before/after root.');
681
        }
682 76
        $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\nestedintervals\NestedIntervalsBehavior.

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...
683 76
        if ($this->treeAttribute !== null) {
684 76
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
685 76
        }
686 76
        if ($right - $left < 3) {
687 32
            for ($i = $right - $left; $i < 3; $i++) {
688 32
                $unallocated = $this->findUnallocatedAll($left, $right);
689 32
                if ($unallocated < $left) {
690 5
                    $this->shift($unallocated, $left, -1);
0 ignored issues
show
Bug introduced by
It seems like $unallocated defined by $this->findUnallocatedAll($left, $right) on line 688 can also be of type double or false; however, paulzi\nestedintervals\N...ervalsBehavior::shift() does only seem to accept integer, 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...
691 5
                    $left--;
692 5
                } else {
693 30
                    $this->shift($right, $unallocated, 1);
0 ignored issues
show
Bug introduced by
It seems like $unallocated defined by $this->findUnallocatedAll($left, $right) on line 688 can also be of type double or false; however, paulzi\nestedintervals\N...ervalsBehavior::shift() does only seem to accept integer, 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...
694 30
                    $right++;
695
                }
696 32
            }
697 32
            $this->owner->setAttribute($this->leftAttribute,  $left  + 1);
698 32
            $this->owner->setAttribute($this->rightAttribute, $right - 1);
699 32
        } else {
700 76
            $left++;
701 76
            $right--;
702
703 76
            $isPadding = false;
704 76
            if ($depth === 1 || $parent !== null) {
705
                // prepending/appending
706 66
                if (is_array($this->amountOptimize)) {
707
                    if (isset($this->amountOptimize[$depth - 1])) {
708
                        $amountOptimize = $this->amountOptimize[$depth - 1];
709
                    } else {
710
                        $amountOptimize = $this->amountOptimize[count($this->amountOptimize) - 1];
711
                    }
712
                } else {
713 66
                    $amountOptimize = $this->amountOptimize;
714
                }
715 66
                $pLeft     = $parent !== null ? (int)$parent[$this->leftAttribute]  : $this->node->getAttribute($this->leftAttribute);
716 66
                $pRight    = $parent !== null ? (int)$parent[$this->rightAttribute] : $this->node->getAttribute($this->rightAttribute);
717 66
                $isCenter  = !$this->noAppend && !$this->noPrepend;
718 66
                $isFirst   = $left === $pLeft + 1 && $right === $pRight - 1;
719 66
                $isPadding = !$this->noInsert || ($isFirst && ($forward ? !$this->noPrepend : !$this->noAppend));
720 66
                $step      = $amountOptimize + $this->reserveFactor * ($this->noInsert ? ($isCenter ? 2 : 1) : $amountOptimize + 1);
721 66
                $step      = ($pRight - $pLeft - 1) / $step;
722 66
                $stepGap   = $step * $this->reserveFactor;
723 66
                $padding   = $isPadding ? $stepGap : 0;
724
725 66
                if ($forward) {
726 33
                    $pLeft  = $left + (int)floor($padding);
727 33
                    $pRight = $left + (int)floor($padding + $step) - 1;
728 33
                } else {
729 33
                    $pLeft  = $right - (int)floor($padding + $step) + 1;
730 33
                    $pRight = $right - (int)floor($padding);
731
                }
732 66
                if ($isFirst && $isCenter) {
733 20
                    $initPosition = (int)floor(($amountOptimize - 1) / 2) * (int)floor($step + ($this->noInsert ? 0 : $stepGap)) + ($this->noInsert ? 1 : 0);
734 20
                    $pLeft  += $forward ? $initPosition : -$initPosition;
735 20
                    $pRight += $forward ? $initPosition : -$initPosition;
736 20
                }
737 66
                if ($pLeft < $pRight && $pRight <= $right && $left <= $pLeft && ($forward || $left < $pLeft) && (!$forward || $pRight < $right)) {
738 64
                    $this->owner->setAttribute($this->leftAttribute,  $pLeft);
739 64
                    $this->owner->setAttribute($this->rightAttribute, $pRight);
740 64
                    return;
741
                }
742 42
            }
743
744 52
            $isPadding = $isPadding || !$this->noInsert;
745 52
            $step = (int)floor(($right - $left) / ($isPadding ? 3 : 2));
746 52
            $this->owner->setAttribute($this->leftAttribute,  $left  + ($forward  && !$isPadding ? 0 : $step));
747 52
            $this->owner->setAttribute($this->rightAttribute, $right - (!$forward && !$isPadding ? 0 : $step));
748
        }
749 66
    }
750
751
    /**
752
     * @param int $left
753
     * @param int $right
754
     * @param int $depth
755
     * @throws Exception
756
     */
757 54
    protected function moveNode($left, $right, $depth = 0)
758
    {
759 54
        $targetDepth = $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\nestedintervals\NestedIntervalsBehavior.

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...
760 54
        $depthDelta = $this->owner->getAttribute($this->depthAttribute) - $targetDepth;
761 54
        if ($this->treeAttribute === null || $this->owner->getAttribute($this->treeAttribute) === $this->node->getAttribute($this->treeAttribute)) {
762
            // same root
763 38
            $sLeft = $this->owner->getAttribute($this->leftAttribute);
764 38
            $sRight = $this->owner->getAttribute($this->rightAttribute);
765 38
            $this->owner->updateAll(
766 38
                [$this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]" . sprintf('%+d', $depthDelta))],
767 38
                $this->getDescendants(null, true)->where
768 38
            );
769 38
            $sDelta = $sRight - $sLeft + 1;
770 38
            if ($sLeft >= $right) {
771 22
                $this->shift($right, $sLeft - 1, $sDelta);
772 22
                $delta = $right - $sLeft;
773 22
            } else {
774 16
                $this->shift($sRight + 1, $left, -$sDelta);
775 16
                $delta = $left - $sRight;
776
            }
777 38
            $this->owner->updateAll(
778
                [
779 38
                    $this->leftAttribute  => new Expression("[[{$this->leftAttribute}]]"  . sprintf('%+d', $delta)),
780 38
                    $this->rightAttribute => new Expression("[[{$this->rightAttribute}]]" . sprintf('%+d', $delta)),
781 38
                    $this->depthAttribute => new Expression("-[[{$this->depthAttribute}]]"),
782 38
                ],
783
                [
784 38
                    'and',
785 38
                    $this->getDescendants(null, true)->where,
786 38
                    ['<', $this->depthAttribute, 0],
787
                ]
788 38
            );
789 38
        } else {
790
            // move from other root (slow!)
791
            /** @var ActiveRecord|self $root */
792 16
            $root      = $this->getNodeBehavior()->getRoot()->one();
793 16
            $countTo   = (int)$root->getDescendants()->orderBy(null)->count();
0 ignored issues
show
Bug introduced by
The method getDescendants does only exist in paulzi\nestedintervals\NestedIntervalsBehavior, 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...
794 16
            $countFrom = (int)$this->getDescendants(null, true)->orderBy(null)->count();
795 16
            $size  = (int)floor(($this->range[1] - $this->range[0]) / (($countFrom + $countTo) * 2 + 1));
796
797 16
            $leftIdx  = $this->optimizeAttribute($root->getDescendants(null, false, false), $this->leftAttribute,  $this->range[0], $size,  0, $left,  $countFrom, $targetDepth);
798 16
            $rightIdx = $this->optimizeAttribute($root->getDescendants(null, false, true),  $this->rightAttribute, $this->range[1], $size, 0,  $right, $countFrom, $targetDepth);
799 16
            if ($leftIdx !== null && $rightIdx !== null) {
800 16
                $this->optimizeAttribute($this->getDescendants(null, true, false), $this->leftAttribute,  $this->range[0], $size, $leftIdx);
801 16
                $this->optimizeAttribute($this->getDescendants(null, true, true), $this->rightAttribute, $this->range[1],  $size, $rightIdx, null, 0,  null, [
802 16
                    $this->treeAttribute  => $this->node->getAttribute($this->treeAttribute),
803 16
                    $this->depthAttribute => new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', -$depthDelta)),
804 16
                ]);
805 16
            } else {
806
                throw new Exception('Error move a node from other tree');
807
            }
808
        }
809 54
    }
810
811
    /**
812
     *
813
     */
814 5
    protected function moveNodeAsRoot()
815
    {
816 5
        $left   = $this->owner->getAttribute($this->leftAttribute);
817 5
        $right  = $this->owner->getAttribute($this->rightAttribute);
818 5
        $depth  = $this->owner->getAttribute($this->depthAttribute);
819 5
        $tree   = $this->treeChange ? $this->treeChange : $this->owner->getPrimaryKey();
820
821 5
        $factor = ($this->range[1] - $this->range[0]) / ($right - $left);
822 5
        $formula = sprintf('ROUND(([[%%s]] * 1.0 %+d) * %d %+d)', -$left, (int)$factor, $this->range[0]);
823 5
        $this->owner->updateAll(
824
            [
825 5
                $this->leftAttribute  => new Expression(sprintf($formula, $this->leftAttribute)),
826 5
                $this->rightAttribute => new Expression(sprintf($formula, $this->rightAttribute)),
827 5
                $this->depthAttribute => new Expression("[[{$this->depthAttribute}]] - {$depth}"),
828 5
                $this->treeAttribute  => $tree,
829 5
            ],
830 5
            $this->getDescendants()->where
831 5
        );
832 5
        $this->owner->updateAll(
833
            [
834 5
                $this->leftAttribute  => $this->range[0],
835 5
                $this->rightAttribute => $this->range[1],
836 5
                $this->depthAttribute => 0,
837 5
                $this->treeAttribute  => $tree,
838 5
            ],
839 5
            [$this->getPrimaryKey() => $this->owner->getPrimaryKey()]
840 5
        );
841 5
        $this->owner->refresh();
842 5
    }
843
844
    /**
845
     * @throws Exception
846
     */
847 5
    protected function optimizeInternal()
848
    {
849 5
        $left  = $this->owner->getAttribute($this->leftAttribute);
850 5
        $right = $this->owner->getAttribute($this->rightAttribute);
851
852 5
        $count = $this->getDescendants()->orderBy(null)->count() * 2 + 1;
853 5
        $size  = (int)floor(($right - $left) / $count);
854
855 5
        $this->optimizeAttribute($this->getDescendants(null, false, false), $this->leftAttribute,  $left,  $size);
856 5
        $this->optimizeAttribute($this->getDescendants(null, false, true),  $this->rightAttribute, $right, $size);
857 5
    }
858
859
    /**
860
     * @param \yii\db\ActiveQuery $query
861
     * @param string $attribute
862
     * @param int $from
863
     * @param int $size
864
     * @param int $offset
865
     * @param int|null $freeFrom
866
     * @param int $freeSize
867
     * @param int|null $targetDepth
868
     * @param array $additional
869
     * @return int|null
870
     * @throws Exception
871
     * @throws \yii\db\Exception
872
     */
873 21
    protected function optimizeAttribute($query, $attribute, $from, $size, $offset = 0, $freeFrom = null, $freeSize = 0, $targetDepth = null, $additional = [])
874
    {
875 21
        $primaryKey = $this->getPrimaryKey();
876 21
        $result     = null;
877 21
        $isForward  = $attribute === $this->leftAttribute;
878
879
        // @todo: pgsql and mssql optimization
880 21
        if (in_array($this->owner->getDb()->driverName, ['mysql', 'mysqli'])) {
881
            // mysql optimization
882 8
            $tableName = $this->owner->tableName();
883 8
            $additionalString = null;
884 8
            $additionalParams = [];
885 8
            foreach ($additional as $name => $value) {
886 6
                $additionalString .= ", [[{$name}]] = ";
887 6
                if ($value instanceof Expression) {
888 6
                    $additionalString .= $value->expression;
889 6
                    foreach ($value->params as $n => $v) {
890
                        $additionalParams[$n] = $v;
891 6
                    }
892 6
                } else {
893 6
                    $paramName = ':nestedIntervals' . count($additionalParams);
894 6
                    $additionalString .= $paramName;
895 6
                    $additionalParams[$paramName] = $value;
896
                }
897 8
            }
898
899
            $command = $query
900 8
                ->select([$primaryKey, $attribute, $this->depthAttribute])
901 8
                ->orderBy([$attribute => $isForward ? SORT_ASC : SORT_DESC])
902 8
                ->createCommand();
903 8
            $this->owner->getDb()->createCommand("
904
                UPDATE
905 8
                    {$tableName} u,
906
                    (SELECT
907 8
                        [[{$primaryKey}]],
908
                        IF (@i := @i + 1, 0, 0)
909 8
                        + IF ([[{$attribute}]] " . ($isForward ? '>' : '<') . " @freeFrom,
910
                            IF (
911
                                (@result := @i)
912
                                + IF (@depth - :targetDepth > 0, @result := @result + @depth - :targetDepth, 0)
913
                                + (@i := @i + :freeSize * 2)
914
                                + (@freeFrom := NULL), 0, 0),
915
                            0)
916 8
                        + IF (@depth - [[{$this->depthAttribute}]] >= 0,
917 8
                            IF (@i := @i + @depth - [[{$this->depthAttribute}]] + 1, 0, 0),
918
                            0)
919 8
                        + (:from " . ($isForward ? '+' : '-') . " (CAST(@i AS UNSIGNED INTEGER) + :offset) * :size)
920 8
                        + IF ([[{$attribute}]] = @freeFrom,
921
                            IF ((@result := @i) + (@i := @i + :freeSize * 2) + (@freeFrom := NULL), 0, 0),
922
                            0)
923 8
                        + IF (@depth := [[{$this->depthAttribute}]], 0, 0)
924
                        as 'new'
925
                    FROM
926
                        (SELECT @i := 0, @depth := -1, @freeFrom := :freeFrom, @result := NULL) v,
927 8
                        (" . $command->sql . ") t
928
                    ) tmp
929 8
                SET u.[[{$attribute}]]=tmp.[[new]] {$additionalString}
930 8
                WHERE tmp.[[{$primaryKey}]]=u.[[{$primaryKey}]]")
931 8
                ->bindValues($additionalParams)
932 8
                ->bindValues($command->params)
933 8
                ->bindValues([
934 8
                    ':from'        => $from,
935 8
                    ':size'        => $size,
936 8
                    ':offset'      => $offset,
937 8
                    ':freeFrom'    => $freeFrom,
938 8
                    ':freeSize'    => $freeSize,
939 8
                    ':targetDepth' => $targetDepth,
940 8
                ])
941 8
                ->execute();
942 8
            if ($freeFrom !== null) {
943 6
                $result = $this->owner->getDb()->createCommand("SELECT IFNULL(@result, @i + 1 + IF (@depth - :targetDepth > 0, @depth - :targetDepth, 0))")
944 6
                    ->bindValue(':targetDepth', $targetDepth)
945 6
                    ->queryScalar();
946 6
                $result = $result === null ? null : (int)$result;
947 6
            }
948 8
            return $result;
949
        } else {
950
            // generic algorithm (very slow!)
951
            $query
952 13
                ->select([$primaryKey, $attribute, $this->depthAttribute])
953 13
                ->asArray()
954 13
                ->orderBy([$attribute => $isForward ? SORT_ASC : SORT_DESC]);
955
956 13
            $prevDepth = -1;
957 13
            $i = 0;
958 13
            foreach ($query->each() as $data) {
959 13
                $i++;
960 13
                if ($freeFrom !== null && $freeFrom !== (int)$data[$attribute] && ($freeFrom > (int)$data[$attribute] xor $isForward)) {
961 7
                    $result = $i;
962 7
                    $depthDiff = $prevDepth - $targetDepth;
963 7
                    if ($depthDiff > 0) {
964
                        $result += $depthDiff;
965
                    }
966 7
                    $i += $freeSize * 2;
967 7
                    $freeFrom = null;
968 7
                }
969 13
                $depthDiff = $prevDepth - $data[$this->depthAttribute];
970 13
                if ($depthDiff >= 0) {
971 13
                    $i += $depthDiff + 1;
972 13
                }
973 13
                $this->owner->updateAll(
974 13
                    array_merge($additional, [$attribute  => $isForward ? $from + ($i + $offset) * $size : $from - ($i + $offset) * $size]),
975 13
                    [$primaryKey => $data[$primaryKey]]
976 13
                );
977 13
                if ($freeFrom !== null && $freeFrom === (int)$data[$attribute]) {
978 6
                    $result = $i;
979 6
                    $i += $freeSize * 2;
980 6
                    $freeFrom = null;
981 6
                }
982 13
                $prevDepth = $data[$this->depthAttribute];
983 13
            }
984 13
            if ($freeFrom !== null) {
985 3
                $result = $i + 1;
986 3
                $depthDiff = $prevDepth - $targetDepth;
987 3
                if ($depthDiff > 0) {
988 3
                    $result += $depthDiff;
989 3
                }
990 3
            }
991 13
            return $result;
992
        }
993
    }
994
995
    /**
996
     * @param int $left
997
     * @param int $right
998
     */
999 52 View Code Duplication
    protected function findPrependRange(&$left, &$right)
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...
1000
    {
1001 52
        $left  = $this->node->getAttribute($this->leftAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedintervals\NestedIntervalsBehavior.

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...
1002 52
        $right = $this->node->getAttribute($this->rightAttribute);
1003 52
        $child = $this->getNodeBehavior()->getChildren()
1004 52
            ->select($this->leftAttribute)
1005 52
            ->orderBy([$this->leftAttribute => SORT_ASC])
1006 52
            ->limit(1)
1007 52
            ->scalar();
1008 52
        if ($child !== false) {
1009 26
            $right = (int)$child;
1010 26
        }
1011 52
    }
1012
1013
    /**
1014
     * @param int $left
1015
     * @param int $right
1016
     */
1017 52 View Code Duplication
    protected function findAppendRange(&$left, &$right)
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...
1018
    {
1019 52
        $left  = $this->node->getAttribute($this->leftAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedintervals\NestedIntervalsBehavior.

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...
1020 52
        $right = $this->node->getAttribute($this->rightAttribute);
1021 52
        $child = $this->getNodeBehavior()->getChildren()
1022 52
            ->select($this->rightAttribute)
1023 52
            ->orderBy([$this->rightAttribute => SORT_DESC])
1024 52
            ->limit(1)
1025 52
            ->scalar();
1026 52
        if ($child !== false) {
1027 26
            $left = (int)$child;
1028 26
        }
1029 52
    }
1030
1031
    /**
1032
     * @param int $left
1033
     * @param int $right
1034
     * @return array|null
1035
     */
1036 25 View Code Duplication
    protected function findInsertBeforeRange(&$left, &$right)
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...
1037
    {
1038 25
        $result = null;
1039 25
        $right = $this->node->getAttribute($this->leftAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedintervals\NestedIntervalsBehavior.

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...
1040 25
        $left  = $this->getNodeBehavior()->getPrev()
1041 25
            ->select($this->rightAttribute)
1042 25
            ->scalar();
1043 25
        if ($left === false) {
1044 9
            $result = $this->getNodeBehavior()->getParent()
1045 9
                ->select([$this->leftAttribute, $this->rightAttribute])
1046 9
                ->createCommand()
1047 9
                ->queryOne();
1048 9
            $left = $result[$this->leftAttribute];
1049 9
        }
1050 25
        $left = (int)$left;
1051 25
        return $result;
1052
    }
1053
1054
    /**
1055
     * @param int $left
1056
     * @param int $right
1057
     * @return array|null
1058
     */
1059 28 View Code Duplication
    protected function findInsertAfterRange(&$left, &$right)
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...
1060
    {
1061 28
        $result = null;
1062 28
        $left  = $this->node->getAttribute($this->rightAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\nestedintervals\NestedIntervalsBehavior.

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...
1063 28
        $right = $this->getNodeBehavior()->getNext()
1064 28
            ->select($this->leftAttribute)
1065 28
            ->scalar();
1066 28
        if ($right === false) {
1067 15
            $result = $this->getNodeBehavior()->getParent()
1068 15
                ->select([$this->leftAttribute, $this->rightAttribute])
1069 15
                ->createCommand()
1070 15
                ->queryOne();
1071 15
            $right = $result[$this->rightAttribute];
1072 15
        }
1073 28
        $right = (int)$right;
1074 28
        return $result;
1075
    }
1076
1077
    /**
1078
     * @param int $value
1079
     * @param string $attribute
1080
     * @param bool $forward
1081
     * @return bool|int
1082
     */
1083 32
    protected function findUnallocated($value, $attribute, $forward = true)
1084
    {
1085 32
        $tableName = $this->owner->tableName();
1086 32
        $leftCondition  = "l.[[{$this->leftAttribute}]]  = {$tableName}.[[{$attribute}]] " . ($forward ? '+' : '-') . " 1";
1087 32
        $rightCondition = "r.[[{$this->rightAttribute}]] = {$tableName}.[[{$attribute}]] " . ($forward ? '+' : '-') . " 1";
1088 32
        if ($this->treeAttribute !== null) {
1089 26
            $leftCondition  = ['and', $leftCondition,  "l.[[{$this->treeAttribute}]] = {$tableName}.[[{$this->treeAttribute}]]"];
1090 26
            $rightCondition = ['and', $rightCondition, "r.[[{$this->treeAttribute}]] = {$tableName}.[[{$this->treeAttribute}]]"];
1091 26
        }
1092 32
        $result = (new Query())
1093 32
            ->select("{$tableName}.[[{$attribute}]]")
1094 32
            ->from("{$tableName}")
1095 32
            ->leftJoin("{$tableName} l", $leftCondition)
1096 32
            ->leftJoin("{$tableName} r", $rightCondition)
1097 32
            ->where([
1098 32
                'and',
1099 32
                [$forward ? '>=' : '<=', "{$tableName}.[[{$attribute}]]", $value],
1100 32
                [$forward ? '<'  : '>',  "{$tableName}.[[{$attribute}]]", $this->range[$forward ? 1 : 0]],
1101
                [
1102 32
                    "l.[[{$this->leftAttribute}]]"  => null,
1103 32
                    "r.[[{$this->rightAttribute}]]" => null,
1104 32
                ],
1105 32
            ])
1106 32
            ->andWhere($this->treeCondition())
1107 32
            ->orderBy(["{$tableName}.[[{$attribute}]]" => $forward ? SORT_ASC : SORT_DESC])
1108 32
            ->limit(1)
1109 32
            ->scalar($this->owner->getDb());
1110 32
        if ($result !== false) {
1111 32
            $result += $forward ? 1 : -1;
1112 32
        }
1113 32
        return $result;
1114
    }
1115
1116
    /**
1117
     * @param int $left
1118
     * @param int $right
1119
     * @return bool|int
1120
     */
1121 32
    protected function findUnallocatedAll($left, $right)
1122
    {
1123 32
        $unallocated = false;
1124 32
        if ($right < (($this->range[1] - $this->range[0])>>1)) {
1125 30
            $unallocated = $unallocated ?: $this->findUnallocated($right, $this->rightAttribute, true);
1126 30
            $unallocated = $unallocated ?: $this->findUnallocated($right, $this->leftAttribute,  true);
1127 30
            $unallocated = $unallocated ?: $this->findUnallocated($left,  $this->leftAttribute,  false);
1128 30
            $unallocated = $unallocated ?: $this->findUnallocated($left,  $this->rightAttribute, false);
1129 30
        } else {
1130 5
            $unallocated = $unallocated ?: $this->findUnallocated($left,  $this->leftAttribute,  false);
1131 5
            $unallocated = $unallocated ?: $this->findUnallocated($left,  $this->rightAttribute, false);
1132 5
            $unallocated = $unallocated ?: $this->findUnallocated($right, $this->rightAttribute, true);
1133 5
            $unallocated = $unallocated ?: $this->findUnallocated($right, $this->leftAttribute,  true);
1134
        }
1135 32
        return $unallocated;
1136
    }
1137
1138
    /**
1139
     * @param int $from
1140
     * @param int $to
1141
     * @param int $delta
1142
     */
1143 70
    protected function shift($from, $to, $delta)
1144
    {
1145 70
        if ($to >= $from && $delta !== 0) {
1146 70
            foreach ([$this->leftAttribute, $this->rightAttribute] as $i => $attribute) {
1147 70
                $this->owner->updateAll(
1148 70
                    [$attribute => new Expression("[[{$attribute}]]" . sprintf('%+d', $delta))],
1149
                    [
1150 70
                        'and',
1151 70
                        ['between', $attribute, $from, $to],
1152 70
                        $this->treeCondition()
1153 70
                    ]
1154 70
                );
1155 70
            }
1156 70
        }
1157 70
    }
1158
1159
    /**
1160
     * @return array
1161
     */
1162 238
    protected function treeCondition()
1163
    {
1164 238
        $tableName = $this->owner->tableName();
1165 238
        if ($this->treeAttribute === null) {
1166 153
            return [];
1167
        } else {
1168 220
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
1169
        }
1170
    }
1171
}
1172