GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( c33231...af10c0 )
by Pavel
18:25 queued 12:21
created

MaterializedPathBehavior::afterInsert()   D

Complexity

Conditions 10
Paths 11

Size

Total Lines 34
Code Lines 24

Duplication

Lines 6
Ratio 17.65 %

Code Coverage

Tests 26
CRAP Score 10.0364

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 6
loc 34
ccs 26
cts 28
cp 0.9286
rs 4.8197
cc 10
eloc 24
nc 11
nop 0
crap 10.0364

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-materialized-path
4
 * @copyright Copyright (c) 2015 PaulZi <[email protected]>
5
 * @license MIT (https://github.com/paulzi/yii2-materialized-path/blob/master/LICENSE)
6
 */
7
8
namespace paulzi\materializedpath;
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
 * Materialized Path Behavior for Yii2
20
 * @author PaulZi <[email protected]>
21
 *
22
 * @property ActiveRecord $owner
23
 */
24
class MaterializedPathBehavior 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
36
     */
37
    public $pathAttribute = 'path';
38
39
    /**
40
     * @var string
41
     */
42
    public $depthAttribute = 'depth';
43
44
    /**
45
     * @var string
46
     */
47
    public $sortAttribute = 'sort';
48
49
    /**
50
     * @var string
51
     */
52
    public $itemAttribute;
53
54
    /**
55
     * @var string|null
56
     */
57
    public $treeAttribute;
58
59
    /**
60
     * @var string
61
     */
62
    public $delimiter = '/';
63
64
    /**
65
     * @var int
66
     */
67
    public $step = 100;
68
69
    /**
70
     * @var int Value of $depthAttribute for root node.
71
     */
72
    public $rootDepthValue = 0;
73
    
74
    /**
75
     * @var int|null
76
     */
77
    protected $operation;
78
79
    /**
80
     * @var ActiveRecord|self|null
81
     */
82
    protected $node;
83
84
    /**
85
     * @var bool
86
     */
87
    protected $primaryKeyMode = false;
88
89
90
    /**
91
     * @inheritdoc
92
     */
93 192
    public function events()
94
    {
95
        return [
96 192
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeSave',
97 192
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterInsert',
98 192
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
99 192
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterUpdate',
100 192
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
101 192
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
102 192
        ];
103
    }
104
105
    /**
106
     * @param ActiveRecord $owner
107
     * @throws Exception
108
     */
109 192
    public function attach($owner)
110
    {
111 192
        if ($this->itemAttribute === null) {
112 192
            $primaryKey = $owner->primaryKey();
113 192
            if (!isset($primaryKey[0])) {
114
                throw new Exception('"' . $owner->className() . '" must have a primary key.');
115
            }
116 192
            $this->itemAttribute = $primaryKey[0];
117 192
            $this->primaryKeyMode = true;
118 192
        }
119 192
        parent::attach($owner);
120 192
    }
121
122
    /**
123
     * @param int|null $depth
124
     * @return \yii\db\ActiveQuery
125
     */
126 9
    public function getParents($depth = null)
127
    {
128 9
        $path  = $this->getParentPath();
129 9
        if ($path !== null) {
130 9
            $paths = explode($this->delimiter, $path);
131 9
            if (!$this->primaryKeyMode) {
132 9
                $path  = null;
133 9
                $paths = array_map(
134
                    function ($value) use (&$path) {
135 9
                        return $path = ($path !== null ? $path . $this->delimiter : '') . $value;
136 9
                    },
137
                    $paths
138 9
                );
139 9
            }
140 9
            if ($depth !== null) {
141 9
                $paths = array_slice($paths, -$depth);
142 9
            }
143 9
        } else {
144 3
            $paths = [];
145
        }
146
147 9
        $tableName = $this->owner->tableName();
148 9
        $condition = ['and'];
149 9
        if ($this->primaryKeyMode) {
150 6
            $condition[] = ["{$tableName}.[[{$this->itemAttribute}]]" => $paths];
151 6
        } else {
152 9
            $condition[] = ["{$tableName}.[[{$this->pathAttribute}]]" => $paths];
153
        }
154
155 9
        $query = $this->owner->find()
156 9
            ->andWhere($condition)
157 9
            ->andWhere($this->treeCondition())
158 9
            ->addOrderBy(["{$tableName}.[[{$this->pathAttribute}]]" => SORT_ASC]);
159 9
        $query->multiple = true;
160
161 9
        return $query;
162
    }
163
164
    /**
165
     * @return \yii\db\ActiveQuery
166
     */
167 6
    public function getParent()
168
    {
169 6
        $query = $this->getParents(1)->limit(1);
170 6
        $query->multiple = false;
171 6
        return $query;
172
    }
173
174
    /**
175
     * @return \yii\db\ActiveQuery
176
     */
177 3
    public function getRoot()
178
    {
179 3
        $path = explode($this->delimiter, $this->owner->getAttribute($this->pathAttribute));
180 3
        $path = array_shift($path);
181 3
        $tableName = $this->owner->tableName();
182 3
        $query = $this->owner->find()
183 3
            ->andWhere(["{$tableName}.[[{$this->pathAttribute}]]" => $path])
184 3
            ->andWhere($this->treeCondition())
185 3
            ->limit(1);
186 3
        $query->multiple = false;
187 3
        return $query;
188
    }
189
190
    /**
191
     * @param int|null $depth
192
     * @param bool $andSelf
193
     * @return \yii\db\ActiveQuery
194
     */
195 63
    public function getDescendants($depth = null, $andSelf = false)
196
    {
197 63
        $tableName = $this->owner->tableName();
198 63
        $path = $this->owner->getAttribute($this->pathAttribute);
199 63
        $like = strtr($path . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']);
200
201 63
        $query = $this->owner->find()
202 63
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $like . '%', false]);
203
204 63
        if ($andSelf) {
205 9
            $query->orWhere(["{$tableName}.[[{$this->pathAttribute}]]" => $path]);
206 9
        }
207
208 63
        if ($depth !== null) {
209 57
            $query->andWhere(['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth]);
210 57
        }
211
212
        $query
213 63
            ->andWhere($this->treeCondition())
214 63
            ->addOrderBy([
215 63
                "{$tableName}.[[{$this->depthAttribute}]]" => SORT_ASC,
216 63
                "{$tableName}.[[{$this->sortAttribute}]]"  => SORT_ASC,
217 63
                "{$tableName}.[[{$this->itemAttribute}]]"  => SORT_ASC,
218 63
            ]);
219 63
        $query->multiple = true;
220
221 63
        return $query;
222
    }
223
224
    /**
225
     * @return \yii\db\ActiveQuery
226
     */
227 51
    public function getChildren()
228
    {
229 51
        return $this->getDescendants(1);
230
    }
231
232
    /**
233
     * @param int|null $depth
234
     * @return \yii\db\ActiveQuery
235
     */
236 3
    public function getLeaves($depth = null)
237
    {
238 3
        $tableName = $this->owner->tableName();
239
        $condition = [
240 3
            'and',
241 3
            ['like', "leaves.[[{$this->pathAttribute}]]",  new Expression($this->concatExpression(["{$tableName}.[[{$this->pathAttribute}]]", ':delimiter']), [':delimiter' => $this->delimiter . '%'])],
242 3
        ];
243
244 3
        if ($this->treeAttribute !== null) {
245 3
            $condition[] = ["leaves.[[{$this->treeAttribute}]]" => new Expression("{$tableName}.[[{$this->treeAttribute}]]")];
246 3
        }
247
248 3
        $query = $this->getDescendants($depth)
249 3
            ->leftJoin("{$tableName} leaves", $condition)
250 3
            ->andWhere(["leaves.[[{$this->pathAttribute}]]" => null]);
251 3
        $query->multiple = true;
252 3
        return $query;
253
    }
254
255
    /**
256
     * @return \yii\db\ActiveQuery
257
     */
258 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...
259
    {
260 3
        $tableName = $this->owner->tableName();
261 3
        $like = strtr($this->getParentPath() . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']);
262 3
        $query = $this->owner->find()
263 3
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $like . '%', false])
264 3
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $this->owner->getAttribute($this->depthAttribute)])
265 3
            ->andWhere(['<', "{$tableName}.[[{$this->sortAttribute}]]", $this->owner->getAttribute($this->sortAttribute)])
266 3
            ->andWhere($this->treeCondition())
267 3
            ->orderBy(["{$tableName}.[[{$this->sortAttribute}]]" => SORT_DESC])
268 3
            ->limit(1);
269 3
        $query->multiple = false;
270 3
        return $query;
271
    }
272
273
    /**
274
     * @return \yii\db\ActiveQuery
275
     */
276 6 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...
277
    {
278 6
        $tableName = $this->owner->tableName();
279 6
        $like = strtr($this->getParentPath() . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']);
280 6
        $query = $this->owner->find()
281 6
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $like . '%', false])
282 6
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $this->owner->getAttribute($this->depthAttribute)])
283 6
            ->andWhere(['>', "{$tableName}.[[{$this->sortAttribute}]]", $this->owner->getAttribute($this->sortAttribute)])
284 6
            ->andWhere($this->treeCondition())
285 6
            ->orderBy(["{$tableName}.[[{$this->sortAttribute}]]" => SORT_ASC])
286 6
            ->limit(1);
287 6
        $query->multiple = false;
288 6
        return $query;
289
    }
290
291
    /**
292
     * @return bool
293
     */
294 72
    public function isRoot()
295
    {
296 72
        return count(explode($this->delimiter, $this->owner->getAttribute($this->pathAttribute))) === 1;
297
    }
298
299
    /**
300
     * @param ActiveRecord $node
301
     * @return bool
302
     */
303 96
    public function isChildOf($node)
304
    {
305 96
        $nodePath  = $node->getAttribute($this->pathAttribute) . $this->delimiter;
306 96
        $result = substr($this->owner->getAttribute($this->pathAttribute), 0, strlen($nodePath)) === $nodePath;
307
308 96
        if ($result && $this->treeAttribute !== null) {
309 9
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
310 9
        }
311
312 96
        return $result;
313
    }
314
315
    /**
316
     * @return bool
317
     */
318 3
    public function isLeaf()
319
    {
320 3
        return count($this->owner->children) === 0;
321
    }
322
323
    /**
324
     * @return ActiveRecord
325
     */
326 6
    public function makeRoot()
327
    {
328 6
        $this->operation = self::OPERATION_MAKE_ROOT;
329 6
        return $this->owner;
330
    }
331
332
    /**
333
     * @param ActiveRecord $node
334
     * @return ActiveRecord
335
     */
336 33
    public function prependTo($node)
337
    {
338 33
        $this->operation = self::OPERATION_PREPEND_TO;
339 33
        $this->node = $node;
340 33
        return $this->owner;
341
    }
342
343
    /**
344
     * @param ActiveRecord $node
345
     * @return ActiveRecord
346
     */
347 33
    public function appendTo($node)
348
    {
349 33
        $this->operation = self::OPERATION_APPEND_TO;
350 33
        $this->node = $node;
351 33
        return $this->owner;
352
    }
353
354
    /**
355
     * @param ActiveRecord $node
356
     * @return ActiveRecord
357
     */
358 30
    public function insertBefore($node)
359
    {
360 30
        $this->operation = self::OPERATION_INSERT_BEFORE;
361 30
        $this->node = $node;
362 30
        return $this->owner;
363
    }
364
365
    /**
366
     * @param ActiveRecord $node
367
     * @return ActiveRecord
368
     */
369 30
    public function insertAfter($node)
370
    {
371 30
        $this->operation = self::OPERATION_INSERT_AFTER;
372 30
        $this->node = $node;
373 30
        return $this->owner;
374
    }
375
376
    /**
377
     * @return bool|int
378
     * @throws \Exception
379
     * @throws \yii\db\Exception
380
     */
381 9
    public function deleteWithChildren()
382
    {
383 9
        $this->operation = self::OPERATION_DELETE_ALL;
384 9
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
385
            $transaction = $this->owner->getDb()->beginTransaction();
386
            try {
387
                $result = $this->deleteWithChildrenInternal();
388
                if ($result === false) {
389
                    $transaction->rollBack();
390
                } else {
391
                    $transaction->commit();
392
                }
393
                return $result;
394
            } catch (\Exception $e) {
395
                $transaction->rollBack();
396
                throw $e;
397
            }
398
        } else {
399 9
            $result = $this->deleteWithChildrenInternal();
400
        }
401 6
        return $result;
402
    }
403
404
    /**
405
     * @throws Exception
406
     * @throws NotSupportedException
407
     */
408 138
    public function beforeSave()
409
    {
410 138
        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\materializedpath\MaterializedPathBehavior.

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

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...
412 108
        }
413
414 138
        switch ($this->operation) {
415 138
            case self::OPERATION_MAKE_ROOT:
416 6
                $this->makeRootInternal();
417
418 6
                break;
419 132
            case self::OPERATION_PREPEND_TO:
420 33
                $this->insertIntoInternal(false);
421
422 21
                break;
423 99
            case self::OPERATION_APPEND_TO:
424 33
                $this->insertIntoInternal(true);
425
426 21
                break;
427 66
            case self::OPERATION_INSERT_BEFORE:
428 30
                $this->insertNearInternal(false);
429
430 21
                break;
431
432 36
            case self::OPERATION_INSERT_AFTER:
433 30
                $this->insertNearInternal(true);
434
435 18
                break;
436
437 6
            default:
438 6
                if ($this->owner->getIsNewRecord()) {
439 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
440
                }
441
442 3
                $item = $this->owner->getAttribute($this->itemAttribute);
443 3
                $path = $this->getParentPath($this->owner->getAttribute($this->pathAttribute));
444 3
                $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
445 93
        }
446 90
    }
447
448
    /**
449
     * @throws Exception
450
     */
451 33
    public function afterInsert()
452
    {
453 33
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
454 3
            $id = $this->owner->getPrimaryKey();
455 3
            $this->owner->setAttribute($this->treeAttribute, $id);
456
457 3
            $primaryKey = $this->owner->primaryKey();
458 3 View Code Duplication
            if (!isset($primaryKey[0])) {
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...
459
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
460
            }
461
462 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
463 3
        }
464 33
        if ($this->owner->getAttribute($this->pathAttribute) === null) {
465 33
            $primaryKey = $this->owner->primaryKey();
466 33 View Code Duplication
            if (!isset($primaryKey[0])) {
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...
467
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
468
            }
469 33
            $id = $this->owner->getPrimaryKey();
470 33
            if ($this->operation === self::OPERATION_MAKE_ROOT) {
471 3
                $path = $id;
472 3
            } else {
473 30
                $path = $this->node->getAttribute($this->pathAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedpath\MaterializedPathBehavior.

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...
474 30
                if ($this->operation === self::OPERATION_INSERT_BEFORE || $this->operation === self::OPERATION_INSERT_AFTER) {
475 18
                    $path = $this->getParentPath($path);
476 18
                }
477 30
                $path = $path . $this->delimiter . $id;
478
            }
479 33
            $this->owner->setAttribute($this->pathAttribute, $path);
480 33
            $this->owner->updateAll([$this->pathAttribute => $path], [$primaryKey[0] => $id]);
481 33
        }
482 33
        $this->operation = null;
483 33
        $this->node      = null;
484 33
    }
485
486
    /**
487
     * @param \yii\db\AfterSaveEvent $event
488
     */
489 57
    public function afterUpdate($event)
490
    {
491 57
        $this->moveNode($event->changedAttributes);
492 57
        $this->operation = null;
493 57
        $this->node      = null;
494 57
    }
495
496
    /**
497
     * @param \yii\base\ModelEvent $event
498
     * @throws Exception
499
     */
500 18
    public function beforeDelete($event)
501
    {
502 18
        if ($this->owner->getIsNewRecord()) {
503 6
            throw new Exception('Can not delete a node when it is new record.');
504
        }
505 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
506 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
507
        }
508 9
        $this->owner->refresh();
509 9
        if ($this->operation !== static::OPERATION_DELETE_ALL && !$this->primaryKeyMode) {
510
            /** @var self $parent */
511 3
            $parent =$this->getParent()->one();
512 3
            $slugs1 = $parent->getChildren()
513 3
                ->andWhere(['<>', $this->itemAttribute, $this->owner->getAttribute($this->itemAttribute)])
514 3
                ->select([$this->itemAttribute])
515 3
                ->column();
516 3
            $slugs2 = $this->getChildren()
517 3
                ->select([$this->itemAttribute])
518 3
                ->column();
519 3
            if (array_intersect($slugs1, $slugs2)) {
520
                $event->isValid = false;
521
            }
522 3
        }
523 9
    }
524
525
    /**
526
     *
527
     */
528 9
    public function afterDelete()
529
    {
530 9
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
531 3
            foreach ($this->owner->children as $child) {
532
                /** @var self $child */
533 3
                if ($this->owner->next === null) {
534
                    $child->appendTo($this->owner->parent)->save();
535
                } else {
536 3
                    $child->insertBefore($this->owner->next)->save();
537
                }
538 3
            }
539 3
        }
540 9
        $this->operation = null;
541 9
        $this->node      = null;
542 9
    }
543
    
544
    /**
545
     * Reorders children with values  of sortAttribute begin from zero.
546
     * @throws \Exception
547
     */
548
    public function reorderChildren()
549
    {
550
        \Yii::$app->getDb()->transaction(function () {
551
            foreach ($this->getChildren()->each() as $i => $child) {
552
                $child->{$this->sortAttribute} = ($i - 1) * $this->step;
553
                $child->save();
554
            }
555
        });
556
    }
557
558
    /**
559
     * @param bool $forInsertNear
560
     * @throws Exception
561
     */
562 126
    protected function checkNode($forInsertNear = false)
563
    {
564 126
        if ($forInsertNear && $this->node->isRoot()) {
0 ignored issues
show
Bug introduced by
The method isRoot does only exist in paulzi\materializedpath\MaterializedPathBehavior, 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...
565 9
            throw new Exception('Can not move a node before/after root.');
566
        }
567 117
        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\materializedpath\MaterializedPathBehavior.

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...
568 12
            throw new Exception('Can not move a node when the target node is new record.');
569
        }
570
571 105
        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\materializ...terializedPathBehavior>; 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...
572 12
            throw new Exception('Can not move a node when the target node is same.');
573
        }
574
575 93
        if ($this->node->isChildOf($this->owner)) {
0 ignored issues
show
Bug introduced by
The method isChildOf does only exist in paulzi\materializedpath\MaterializedPathBehavior, 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...
576 12
            throw new Exception('Can not move a node when the target node is child.');
577
        }
578 81
    }
579
580
    /**
581
     * @param int $to
582
     * @param bool $forward
583
     */
584 105
    protected function moveTo($to, $forward)
585
    {
586 39
        $this->owner->setAttribute($this->sortAttribute, $to + ($forward ? 1 : -1));
587
588 39
        $tableName = $this->owner->tableName();
589 39
        $path = $this->getParentPath($this->node->getAttribute($this->pathAttribute));
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedpath\MaterializedPathBehavior.

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

    function someFunction(B $x) { /** ... */ }
    
Loading history...
590 39
        $like = strtr($path . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']);
591
592
        $joinCondition = [
593 39
            'and',
594 39
            ['like', "n.[[{$this->pathAttribute}]]", $like . '%', false],
595
            [
596 39
                "n.[[{$this->depthAttribute}]]" => $this->node->getAttribute($this->depthAttribute),
597 39
                "n.[[{$this->sortAttribute}]]"  => new Expression("{$tableName}.[[{$this->sortAttribute}]] " . ($forward ? '+' : '-') . " 1"),
598 39
            ],
599 39
        ];
600 39
        if (!$this->owner->getIsNewRecord()) {
601 21
            $joinCondition[] = ['<>', "n.[[{$this->pathAttribute}]]", $this->owner->getAttribute($this->pathAttribute)];
602 21
        }
603 39
        if ($this->treeAttribute !== null) {
604 39
            $joinCondition[] = ["n.[[{$this->treeAttribute}]]" => new Expression("{$tableName}.[[{$this->treeAttribute}]]")];
605 39
        }
606
607 39
        $unallocated = (new Query())
608 39
            ->select("{$tableName}.[[{$this->sortAttribute}]]")
609 39
            ->from("{$tableName}")
610 39
            ->leftJoin("{$tableName} n", $joinCondition)
611 39
            ->where([
612 39
                'and',
613 39
                ['like', "{$tableName}.[[{$this->pathAttribute}]]", $like . '%', false],
614 39
                $this->treeCondition(),
615 39
                [$forward ? '>=' : '<=', "{$tableName}.[[{$this->sortAttribute}]]", $to],
616
                [
617 39
                    "{$tableName}.[[{$this->depthAttribute}]]" => $this->node->getAttribute($this->depthAttribute),
618 39
                    "n.[[{$this->sortAttribute}]]"             => null,
619 39
                ],
620 39
            ])
621 39
            ->orderBy(["{$tableName}.[[{$this->sortAttribute}]]" => $forward ? SORT_ASC : SORT_DESC])
622 39
            ->limit(1)
623 39
            ->scalar($this->owner->getDb());
624
625 39
        $this->owner->updateAll(
626 105
            [$this->sortAttribute => new Expression("[[{$this->sortAttribute}]] " . ($forward ? '+' : '-') . " 1")],
627
            [
628 39
                'and',
629 39
                ['like', "[[{$this->pathAttribute}]]", $like . '%', false],
630 39
                $this->treeCondition(),
631 39
                ["[[{$this->depthAttribute}]]" => $this->node->getAttribute($this->depthAttribute)],
632 39
                ['between', $this->sortAttribute, $forward ? $to + 1 : $unallocated, $forward ? $unallocated : $to - 1],
633
            ]
634 39
        );
635 39
    }
636
637
    /**
638
     * Make root operation internal handler
639
     */
640 6
    protected function makeRootInternal()
641
    {
642 6
        $item = $this->owner->getAttribute($this->itemAttribute);
643
644 6
        if ($item !== null) {
645 6
            $this->owner->setAttribute($this->pathAttribute, $item);
646 6
        }
647
648 6
        if ($this->sortAttribute !== null) {
649 6
            $this->owner->setAttribute($this->sortAttribute, 0);
650 6
        }
651
652 6
        if ($this->treeAttribute !== null) {
653 6
            if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) {
654 3
                $this->owner->setAttribute($this->treeAttribute, $this->owner->getAttribute($this->treeAttribute));
655 6
            } elseif (!$this->owner->getIsNewRecord()) {
656 3
                $this->owner->setAttribute($this->treeAttribute, $this->owner->getPrimaryKey());
657 3
            }
658 6
        }
659
660 6
        $this->owner->setAttribute($this->depthAttribute, $this->rootDepthValue);
661 6
    }
662
663
    /**
664
     * Append to operation internal handler
665
     * @param bool $append
666
     * @throws Exception
667
     */
668 66
    protected function insertIntoInternal($append)
669
    {
670 66
        $this->checkNode(false);
671 42
        $item = $this->owner->getAttribute($this->itemAttribute);
672
673 42 View Code Duplication
        if ($item !== 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...
674 42
            $path = $this->node->getAttribute($this->pathAttribute);
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedpath\MaterializedPathBehavior.

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...
675 42
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
676 42
        }
677
678 42
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + 1);
679
680 42 View Code Duplication
        if ($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...
681 42
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
682 42
        }
683
684 42
        if ($this->sortAttribute !== null) {
685 42
            $to = $this->node->getChildren()->orderBy(null);
0 ignored issues
show
Bug introduced by
The method getChildren does only exist in paulzi\materializedpath\MaterializedPathBehavior, 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...
686 42
            $to = $append ? $to->max($this->sortAttribute) : $to->min($this->sortAttribute);
687
            if (
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
688 42
                !$this->owner->getIsNewRecord() && (int)$to === $this->owner->getAttribute($this->sortAttribute)
689 42
                && !$this->owner->getDirtyAttributes([$this->pathAttribute])
690 42
            ) {
691 42
            } elseif ($to !== null) {
692 24
                $to += $append ? $this->step : -$this->step;
693 24
            } else {
694 12
                $to = 0;
695
            }
696 42
            $this->owner->setAttribute($this->sortAttribute, $to);
697 42
        }
698 42
    }
699
700
    /**
701
     * Insert operation internal handler
702
     * @param bool $forward
703
     * @throws Exception
704
     */
705 60
    protected function insertNearInternal($forward)
706
    {
707 60
        $this->checkNode(true);
708 39
        $item = $this->owner->getAttribute($this->itemAttribute);
709
710 39 View Code Duplication
        if ($item !== 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...
711 39
            $path = $this->getParentPath($this->node->getAttribute($this->pathAttribute));
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\materializedpath\MaterializedPathBehavior.

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...
712 39
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
713 39
        }
714
715 39
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute));
716
717 39 View Code Duplication
        if ($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...
718 39
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
719 39
        }
720
721 39
        if ($this->sortAttribute !== null) {
722 39
            $this->moveTo($this->node->getAttribute($this->sortAttribute), $forward);
723 39
        }
724 39
    }
725
726
    /**
727
     * @return int
728
     */
729 9
    protected function deleteWithChildrenInternal()
730
    {
731 9
        if (!$this->owner->beforeDelete()) {
732
            return false;
733
        }
734 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
735 6
        $this->owner->setOldAttributes(null);
736 6
        $this->owner->afterDelete();
737 6
        return $result;
738
    }
739
740
    /**
741
     * @param array $changedAttributes
742
     * @throws Exception
743
     */
744 57
    protected function moveNode($changedAttributes)
745
    {
746 57
        $path = isset($changedAttributes[$this->pathAttribute]) ? $changedAttributes[$this->pathAttribute] : $this->owner->getAttribute($this->pathAttribute);
747 57
        $like = strtr($path . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']);
748 57
        $update = [];
749
        $condition = [
750 57
            'and',
751 57
            ['like', "[[{$this->pathAttribute}]]", $like . '%', false],
752 57
        ];
753 57
        if ($this->treeAttribute !== null) {
754 54
            $tree = isset($changedAttributes[$this->treeAttribute]) ? $changedAttributes[$this->treeAttribute] : $this->owner->getAttribute($this->treeAttribute);
755 54
            $condition[] = [$this->treeAttribute => $tree];
756 54
        }
757 57
        $params = [];
758
759 57
        if (isset($changedAttributes[$this->pathAttribute])) {
760 30
            $update['path']     = new Expression($this->concatExpression([':pathNew', $this->substringExpression('[[path]]', 'LENGTH(:pathOld) + 1', 'LENGTH([[path]]) - LENGTH(:pathOld)')]));
761 30
            $params[':pathOld'] = $path;
762 30
            $params[':pathNew'] = $this->owner->getAttribute($this->pathAttribute);
763 30
        }
764
765 57
        if ($this->treeAttribute !== null && isset($changedAttributes[$this->treeAttribute])) {
766 15
            $update[$this->treeAttribute] = $this->owner->getAttribute($this->treeAttribute);
767 15
        }
768
769 57
        if ($this->depthAttribute !== null && isset($changedAttributes[$this->depthAttribute])) {
770 30
            $delta = $this->owner->getAttribute($this->depthAttribute) - $changedAttributes[$this->depthAttribute];
771 30
            $update[$this->depthAttribute] = new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', $delta));
772 30
        }
773
774 57
        if (!empty($update)) {
775 30
            $this->owner->updateAll($update, $condition, $params);
776 30
        }
777 57
    }
778
779
    /**
780
     * @param string|bool $path
781
     * @return null|string
782
     */
783 54
    protected function getParentPath($path = false)
784
    {
785 54
        if ($path === false) {
786 15
            $path = $this->owner->getAttribute($this->pathAttribute);
787 15
        }
788 54
        $path = explode($this->delimiter, $path);
789 54
        array_pop($path);
790 54
        return count($path) > 0 ? implode($this->delimiter, $path) : null;
791
    }
792
793
    /**
794
     * @return array
795
     */
796 114
    protected function treeCondition()
797
    {
798 114
        $tableName = $this->owner->tableName();
799 114
        if ($this->treeAttribute === null) {
800 114
            return [];
801
        } else {
802 114
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
803
        }
804
    }
805
806
    /**
807
     * @param array $items
808
     * @return string
809
     */
810 33
    protected function concatExpression($items)
811
    {
812 33
        if ($this->owner->getDb()->driverName === 'sqlite' || $this->owner->getDb()->driverName === 'pgsql') {
813 22
            return implode(' || ', $items);
814
        }
815 11
        return 'CONCAT(' . implode(',', $items) . ')';
816
    }
817
818 30
    protected function substringExpression($string, $from, $length)
819
    {
820 30
        if ($this->owner->getDb()->driverName === 'sqlite') {
821 10
            return "SUBSTR({$string}, {$from}, {$length})";
822
        }
823 20
        return "SUBSTRING({$string}, {$from}, {$length})";
824
    }
825
}
826