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 ( a54314...1f466c )
by Pavel
04:30
created

MaterializedPathBehavior::beforeDelete()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7.049

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 24
ccs 18
cts 20
cp 0.9
rs 6.7273
cc 7
eloc 17
nc 5
nop 1
crap 7.049
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 paulzi\sortable\SortableBehavior;
11
use Yii;
12
use yii\base\Behavior;
13
use yii\base\Exception;
14
use yii\base\NotSupportedException;
15
use yii\db\ActiveRecord;
16
use yii\db\Expression;
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 $itemAttribute;
48
49
    /**
50
     * @var string|null
51
     */
52
    public $treeAttribute;
53
54
    /**
55
     * @var array|false SortableBehavior config
56
     */
57
    public $sortable = [];
58
59
    /**
60
     * @var string
61
     */
62
    public $delimiter = '/';
63
64
    /**
65
     * @var int Value of $depthAttribute for root node.
66
     */
67
    public $rootDepthValue = 0;
68
    
69
    /**
70
     * @var int|null
71
     */
72
    protected $operation;
73
74
    /**
75
     * @var ActiveRecord|self|null
76
     */
77
    protected $node;
78
79
    /**
80
     * @var SortableBehavior
81
     */
82
    protected $behavior;
83
84
    /**
85
     * @var bool
86
     */
87
    protected $primaryKeyMode = false;
88
89
90
    /**
91
     * @inheritdoc
92
     */
93 201
    public function events()
94
    {
95
        return [
96 201
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeSave',
97 201
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterInsert',
98 201
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
99 201
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterUpdate',
100 201
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
101 201
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
102 201
        ];
103
    }
104
105
    /**
106
     * @param ActiveRecord $owner
107
     * @throws Exception
108
     */
109 201
    public function attach($owner)
110
    {
111 201
        parent::attach($owner);
112 201
        if ($this->itemAttribute === null) {
113 201
            $primaryKey = $owner->primaryKey();
114 201
            if (!isset($primaryKey[0])) {
115
                throw new Exception('"' . $owner->className() . '" must have a primary key.');
116
            }
117 201
            $this->itemAttribute = $primaryKey[0];
118 201
            $this->primaryKeyMode = true;
119 201
        }
120 201
        if ($this->sortable !== false) {
121 201
            $this->behavior = Yii::createObject(array_merge(
122
                [
123 201
                    'class' => SortableBehavior::className(),
124
                    'query' => function () {
125 87
                        return $this->getSortableQuery();
126 201
                    },
127 201
                ],
128 201
                $this->sortable
129 201
            ));
130 201
            $owner->attachBehavior('materialized-path-sortable', $this->behavior);
131 201
        }
132 201
    }
133
134
    /**
135
     * @param int|null $depth
136
     * @return \yii\db\ActiveQuery
137
     */
138 9
    public function getParents($depth = null)
139
    {
140 9
        $path  = $this->getParentPath();
141 9
        if ($path !== null) {
142 9
            $paths = explode($this->delimiter, $path);
143 9
            if (!$this->primaryKeyMode) {
144 9
                $path  = null;
145 9
                $paths = array_map(
146 9
                    function ($value) use (&$path) {
147 9
                        return $path = ($path !== null ? $path . $this->delimiter : '') . $value;
148 9
                    },
149
                    $paths
150 9
                );
151 9
            }
152 9
            if ($depth !== null) {
153 9
                $paths = array_slice($paths, -$depth);
154 9
            }
155 9
        } else {
156 3
            $paths = [];
157
        }
158
159 9
        $tableName = $this->owner->tableName();
160 9
        $condition = ['and'];
161 9
        if ($this->primaryKeyMode) {
162 6
            $condition[] = ["{$tableName}.[[{$this->itemAttribute}]]" => $paths];
163 6
        } else {
164 9
            $condition[] = ["{$tableName}.[[{$this->pathAttribute}]]" => $paths];
165
        }
166
167 9
        $query = $this->owner->find()
168 9
            ->andWhere($condition)
169 9
            ->andWhere($this->treeCondition())
170 9
            ->addOrderBy(["{$tableName}.[[{$this->pathAttribute}]]" => SORT_ASC]);
171 9
        $query->multiple = true;
172
173 9
        return $query;
174
    }
175
176
    /**
177
     * @return \yii\db\ActiveQuery
178
     */
179 6
    public function getParent()
180
    {
181 6
        $query = $this->getParents(1)->limit(1);
182 6
        $query->multiple = false;
183 6
        return $query;
184
    }
185
186
    /**
187
     * @return \yii\db\ActiveQuery
188
     */
189 3
    public function getRoot()
190
    {
191 3
        $path = explode($this->delimiter, $this->owner->getAttribute($this->pathAttribute));
192 3
        $path = array_shift($path);
193 3
        $tableName = $this->owner->tableName();
194 3
        $query = $this->owner->find()
195 3
            ->andWhere(["{$tableName}.[[{$this->pathAttribute}]]" => $path])
196 3
            ->andWhere($this->treeCondition())
197 3
            ->limit(1);
198 3
        $query->multiple = false;
199 3
        return $query;
200
    }
201
202
    /**
203
     * @param int|null $depth
204
     * @param bool $andSelf
205
     * @return \yii\db\ActiveQuery
206
     */
207 27
    public function getDescendants($depth = null, $andSelf = false)
208
    {
209 27
        $tableName = $this->owner->tableName();
210 27
        $path = $this->owner->getAttribute($this->pathAttribute);
211 27
        $query = $this->owner->find()
212 27
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($path), false]);
213
214 27
        if ($andSelf) {
215 9
            $query->orWhere(["{$tableName}.[[{$this->pathAttribute}]]" => $path]);
216 9
        }
217
218 27
        if ($depth !== null) {
219 21
            $query->andWhere(['<=', "{$tableName}.[[{$this->depthAttribute}]]", $this->owner->getAttribute($this->depthAttribute) + $depth]);
220 21
        }
221
222 27
        $orderBy = [];
223 27
        $orderBy["{$tableName}.[[{$this->depthAttribute}]]"] = SORT_ASC;
224 27
        if ($this->sortable !== false) {
225 27
            $orderBy["{$tableName}.[[{$this->behavior->sortAttribute}]]"] = SORT_ASC;
226 27
        }
227 27
        $orderBy["{$tableName}.[[{$this->itemAttribute}]]"]  = SORT_ASC;
228
229
        $query
230 27
            ->andWhere($this->treeCondition())
231 27
            ->addOrderBy($orderBy);
232 27
        $query->multiple = true;
233
234 27
        return $query;
235
    }
236
237
    /**
238
     * @return \yii\db\ActiveQuery
239
     */
240 12
    public function getChildren()
241
    {
242 12
        return $this->getDescendants(1);
243
    }
244
245
    /**
246
     * @param int|null $depth
247
     * @return \yii\db\ActiveQuery
248
     */
249 3
    public function getLeaves($depth = null)
250
    {
251 3
        $tableName = $this->owner->tableName();
252
        $condition = [
253 3
            'and',
254 3
            ['like', "leaves.[[{$this->pathAttribute}]]",  new Expression($this->concatExpression(["{$tableName}.[[{$this->pathAttribute}]]", ':delimiter']), [':delimiter' => $this->delimiter . '%'])],
255 3
        ];
256
257 3
        if ($this->treeAttribute !== null) {
258 3
            $condition[] = ["leaves.[[{$this->treeAttribute}]]" => new Expression("{$tableName}.[[{$this->treeAttribute}]]")];
259 3
        }
260
261 3
        $query = $this->getDescendants($depth)
262 3
            ->leftJoin("{$tableName} leaves", $condition)
263 3
            ->andWhere(["leaves.[[{$this->pathAttribute}]]" => null]);
264 3
        $query->multiple = true;
265 3
        return $query;
266
    }
267
268
    /**
269
     * @return \yii\db\ActiveQuery
270
     * @throws NotSupportedException
271
     */
272 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...
273
    {
274 3
        if ($this->sortable === false) {
275
            throw new NotSupportedException('prev() not allow if not set sortable');
276
        }
277 3
        $tableName = $this->owner->tableName();
278 3
        $query = $this->owner->find()
279 3
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($this->getParentPath()), false])
0 ignored issues
show
Bug introduced by
It seems like $this->getParentPath() targeting paulzi\materializedPath\...havior::getParentPath() can also be of type array or null; however, paulzi\materializedPath\...PathBehavior::getLike() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
280 3
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $this->owner->getAttribute($this->depthAttribute)])
281 3
            ->andWhere(['<', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()])
282 3
            ->andWhere($this->treeCondition())
283 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_DESC])
284 3
            ->limit(1);
285 3
        $query->multiple = false;
286 3
        return $query;
287
    }
288
289
    /**
290
     * @return \yii\db\ActiveQuery
291
     * @throws NotSupportedException
292
     */
293 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...
294
    {
295 6
        if ($this->sortable === false) {
296
            throw new NotSupportedException('prev() not allow if not set sortable');
297
        }
298 6
        $tableName = $this->owner->tableName();
299 6
        $query = $this->owner->find()
300 6
            ->andWhere(['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($this->getParentPath()), false])
0 ignored issues
show
Bug introduced by
It seems like $this->getParentPath() targeting paulzi\materializedPath\...havior::getParentPath() can also be of type array or null; however, paulzi\materializedPath\...PathBehavior::getLike() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
301 6
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $this->owner->getAttribute($this->depthAttribute)])
302 6
            ->andWhere(['>', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()])
303 6
            ->andWhere($this->treeCondition())
304 6
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC])
305 6
            ->limit(1);
306 6
        $query->multiple = false;
307 6
        return $query;
308
    }
309
310
    /**
311
     * @param bool $asArray = false
312
     * @return null|string|array
313
     */
314 66
    public function getParentPath($asArray = false)
315
    {
316 66
        return static::getParentPathInternal($this->owner->getAttribute($this->pathAttribute), $this->delimiter, $asArray);
317
    }
318
319
    /**
320
     * Populate children relations for self and all descendants
321
     *
322
     * @param int $depth = null
323
     * @return static
324
     */
325 3
    public function populateTree($depth = null)
326
    {
327
        /** @var ActiveRecord[]|static[] $nodes */
328 3
        if ($depth === null) {
329 3
            $nodes = $this->owner->descendants;
330 3
        } else {
331 3
            $nodes = $this->getDescendants($depth)->all();
332
        }
333
334 3
        $relates = [];
335 3
        foreach ($nodes as $node) {
336 3
            $path = $node->getParentPath(true);
337 3
            $key = array_pop($path);
338 3
            if (!isset($relates[$key])) {
339 3
                $relates[$key] = [];
340 3
            }
341 3
            $relates[$key][] = $node;
342 3
        }
343
344 3
        $nodes[$this->owner->getAttribute($this->itemAttribute)] = $this->owner;
345 3
        foreach ($nodes as $node) {
346 3
            $key = $node->getAttribute($this->itemAttribute);
347 3
            if (isset($relates[$key])) {
348 3
                $node->populateRelation('children', $relates[$key]);
349 3
            } elseif ($depth === null) {
350 3
                $node->populateRelation('children', []);
351 3
            }
352 3
        }
353
354 3
        return $this->owner;
355
    }
356
357
    /**
358
     * @return bool
359
     */
360 72
    public function isRoot()
361
    {
362 72
        return count(explode($this->delimiter, $this->owner->getAttribute($this->pathAttribute))) === 1;
363
    }
364
365
    /**
366
     * @param ActiveRecord $node
367
     * @return bool
368
     */
369 96
    public function isChildOf($node)
370
    {
371 96
        $nodePath  = $node->getAttribute($this->pathAttribute) . $this->delimiter;
372 96
        $result = substr($this->owner->getAttribute($this->pathAttribute), 0, strlen($nodePath)) === $nodePath;
373
374 96
        if ($result && $this->treeAttribute !== null) {
375 9
            $result = $this->owner->getAttribute($this->treeAttribute) === $node->getAttribute($this->treeAttribute);
376 9
        }
377
378 96
        return $result;
379
    }
380
381
    /**
382
     * @return bool
383
     */
384 3
    public function isLeaf()
385
    {
386 3
        return count($this->owner->children) === 0;
387
    }
388
389
    /**
390
     * @return ActiveRecord
391
     */
392 6
    public function makeRoot()
393
    {
394 6
        $this->operation = self::OPERATION_MAKE_ROOT;
395 6
        return $this->owner;
396
    }
397
398
    /**
399
     * @param ActiveRecord $node
400
     * @return ActiveRecord
401
     */
402 33
    public function prependTo($node)
403
    {
404 33
        $this->operation = self::OPERATION_PREPEND_TO;
405 33
        $this->node = $node;
406 33
        return $this->owner;
407
    }
408
409
    /**
410
     * @param ActiveRecord $node
411
     * @return ActiveRecord
412
     */
413 33
    public function appendTo($node)
414
    {
415 33
        $this->operation = self::OPERATION_APPEND_TO;
416 33
        $this->node = $node;
417 33
        return $this->owner;
418
    }
419
420
    /**
421
     * @param ActiveRecord $node
422
     * @return ActiveRecord
423
     */
424 30
    public function insertBefore($node)
425
    {
426 30
        $this->operation = self::OPERATION_INSERT_BEFORE;
427 30
        $this->node = $node;
428 30
        return $this->owner;
429
    }
430
431
    /**
432
     * @param ActiveRecord $node
433
     * @return ActiveRecord
434
     */
435 30
    public function insertAfter($node)
436
    {
437 30
        $this->operation = self::OPERATION_INSERT_AFTER;
438 30
        $this->node = $node;
439 30
        return $this->owner;
440
    }
441
442
    /**
443
     * @return bool|int
444
     * @throws \Exception
445
     * @throws \yii\db\Exception
446
     */
447 9
    public function deleteWithChildren()
448
    {
449 9
        $this->operation = self::OPERATION_DELETE_ALL;
450 9
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
451
            $transaction = $this->owner->getDb()->beginTransaction();
452
            try {
453
                $result = $this->deleteWithChildrenInternal();
454
                if ($result === false) {
455
                    $transaction->rollBack();
456
                } else {
457
                    $transaction->commit();
458
                }
459
                return $result;
460
            } catch (\Exception $e) {
461
                $transaction->rollBack();
462
                throw $e;
463
            }
464
        } else {
465 9
            $result = $this->deleteWithChildrenInternal();
466
        }
467 6
        return $result;
468
    }
469
470
    /**
471
     * @param bool $middle
472
     * @return int
473
     */
474 3
    public function reorderChildren($middle = true)
475
    {
476
        /** @var ActiveRecord|SortableBehavior $item */
477 3
        $item = $this->owner->children[0];
478 3
        if ($item) {
479 3
            return $item->reorder($middle);
0 ignored issues
show
Bug introduced by
The method reorder does only exist in paulzi\sortable\SortableBehavior, 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...
480
        } else {
481
            return 0;
482
        }
483
    }
484
485
    /**
486
     * @throws Exception
487
     * @throws NotSupportedException
488
     */
489 138
    public function beforeSave()
490
    {
491 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...
492 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...
493 108
        }
494
495 138
        switch ($this->operation) {
496 138
            case self::OPERATION_MAKE_ROOT:
497 6
                $this->makeRootInternal();
498
499 6
                break;
500 132
            case self::OPERATION_PREPEND_TO:
501 33
                $this->insertIntoInternal(false);
502
503 21
                break;
504 99
            case self::OPERATION_APPEND_TO:
505 33
                $this->insertIntoInternal(true);
506
507 21
                break;
508 66
            case self::OPERATION_INSERT_BEFORE:
509 30
                $this->insertNearInternal(false);
510
511 21
                break;
512
513 36
            case self::OPERATION_INSERT_AFTER:
514 30
                $this->insertNearInternal(true);
515
516 18
                break;
517
518 6
            default:
519 6
                if ($this->owner->getIsNewRecord()) {
520 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
521
                }
522
523 3
                $item = $this->owner->getAttribute($this->itemAttribute);
524 3
                $path = $this->getParentPath();
525 3
                $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
526 93
        }
527 90
    }
528
529
    /**
530
     * @throws Exception
531
     */
532 33
    public function afterInsert()
533
    {
534 33
        if ($this->operation === self::OPERATION_MAKE_ROOT && $this->treeAttribute !== null && $this->owner->getAttribute($this->treeAttribute) === null) {
535 3
            $id = $this->owner->getPrimaryKey();
536 3
            $this->owner->setAttribute($this->treeAttribute, $id);
537
538 3
            $primaryKey = $this->owner->primaryKey();
539 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...
540
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
541
            }
542
543 3
            $this->owner->updateAll([$this->treeAttribute => $id], [$primaryKey[0] => $id]);
544 3
        }
545 33
        if ($this->owner->getAttribute($this->pathAttribute) === null) {
546 33
            $primaryKey = $this->owner->primaryKey();
547 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...
548
                throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
549
            }
550 33
            $id = $this->owner->getPrimaryKey();
551 33
            if ($this->operation === self::OPERATION_MAKE_ROOT) {
552 3
                $path = $id;
553 3
            } else {
554 30
                if ($this->operation === self::OPERATION_INSERT_BEFORE || $this->operation === self::OPERATION_INSERT_AFTER) {
555 18
                    $path = $this->node->getParentPath();
0 ignored issues
show
Bug introduced by
The method getParentPath 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...
556 18
                } else {
557 12
                    $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...
558
                }
559 30
                $path = $path . $this->delimiter . $id;
560
            }
561 33
            $this->owner->setAttribute($this->pathAttribute, $path);
562 33
            $this->owner->updateAll([$this->pathAttribute => $path], [$primaryKey[0] => $id]);
563 33
        }
564 33
        $this->operation = null;
565 33
        $this->node      = null;
566 33
    }
567
568
    /**
569
     * @param \yii\db\AfterSaveEvent $event
570
     */
571 57
    public function afterUpdate($event)
572
    {
573 57
        $this->moveNode($event->changedAttributes);
574 57
        $this->operation = null;
575 57
        $this->node      = null;
576 57
    }
577
578
    /**
579
     * @param \yii\base\ModelEvent $event
580
     * @throws Exception
581
     */
582 18
    public function beforeDelete($event)
583
    {
584 18
        if ($this->owner->getIsNewRecord()) {
585 6
            throw new Exception('Can not delete a node when it is new record.');
586
        }
587 12
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
588 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
589
        }
590 9
        $this->owner->refresh();
591 9
        if ($this->operation !== static::OPERATION_DELETE_ALL && !$this->primaryKeyMode) {
592
            /** @var self $parent */
593 3
            $parent =$this->getParent()->one();
594 3
            $slugs1 = $parent->getChildren()
595 3
                ->andWhere(['<>', $this->itemAttribute, $this->owner->getAttribute($this->itemAttribute)])
596 3
                ->select([$this->itemAttribute])
597 3
                ->column();
598 3
            $slugs2 = $this->getChildren()
599 3
                ->select([$this->itemAttribute])
600 3
                ->column();
601 3
            if (array_intersect($slugs1, $slugs2)) {
602
                $event->isValid = false;
603
            }
604 3
        }
605 9
    }
606
607
    /**
608
     *
609
     */
610 9
    public function afterDelete()
611
    {
612 9
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
613 3
            foreach ($this->owner->children as $child) {
614
                /** @var self $child */
615 3
                if ($this->owner->next === null) {
616
                    $child->appendTo($this->owner->parent)->save();
617
                } else {
618 3
                    $child->insertBefore($this->owner->next)->save();
619
                }
620 3
            }
621 3
        }
622 9
        $this->operation = null;
623 9
        $this->node      = null;
624 9
    }
625
    
626
    
627
    
628
    /**
629
     * @param bool $forInsertNear
630
     * @throws Exception
631
     */
632 126
    protected function checkNode($forInsertNear = false)
633
    {
634 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...
635 9
            throw new Exception('Can not move a node before/after root.');
636
        }
637 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...
638 12
            throw new Exception('Can not move a node when the target node is new record.');
639
        }
640
641 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...
642 12
            throw new Exception('Can not move a node when the target node is same.');
643
        }
644
645 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...
646 12
            throw new Exception('Can not move a node when the target node is child.');
647
        }
648 81
    }
649
650
    /**
651
     * Make root operation internal handler
652
     */
653 6
    protected function makeRootInternal()
654
    {
655 6
        $item = $this->owner->getAttribute($this->itemAttribute);
656
657 6
        if ($item !== null) {
658 6
            $this->owner->setAttribute($this->pathAttribute, $item);
659 6
        }
660
661 6
        if ($this->sortable !== false) {
662 6
            $this->owner->setAttribute($this->behavior->sortAttribute, 0);
663 6
        }
664
665 6
        if ($this->treeAttribute !== null) {
666 6
            if ($this->owner->getOldAttribute($this->treeAttribute) !== $this->owner->getAttribute($this->treeAttribute)) {
667 3
                $this->owner->setAttribute($this->treeAttribute, $this->owner->getAttribute($this->treeAttribute));
668 6
            } elseif (!$this->owner->getIsNewRecord()) {
669 3
                $this->owner->setAttribute($this->treeAttribute, $this->owner->getPrimaryKey());
670 3
            }
671 6
        }
672
673 6
        $this->owner->setAttribute($this->depthAttribute, $this->rootDepthValue);
674 6
    }
675
676
    /**
677
     * Append to operation internal handler
678
     * @param bool $append
679
     * @throws Exception
680
     */
681 66 View Code Duplication
    protected function insertIntoInternal($append)
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...
682
    {
683 66
        $this->checkNode(false);
684 42
        $item = $this->owner->getAttribute($this->itemAttribute);
685
686 42
        if ($item !== null) {
687 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...
688 42
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
689 42
        }
690
691 42
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute) + 1);
692
693 42
        if ($this->treeAttribute !== null) {
694 42
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
695 42
        }
696
697 42
        if ($this->sortable !== false) {
698 42
            if ($append) {
699 21
                $this->owner->moveLast();
700 21
            } else {
701 21
                $this->owner->moveFirst();
702
            }
703 42
        }
704 42
    }
705
706
    /**
707
     * Insert operation internal handler
708
     * @param bool $forward
709
     * @throws Exception
710
     */
711 60 View Code Duplication
    protected function insertNearInternal($forward)
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...
712
    {
713 60
        $this->checkNode(true);
714 39
        $item = $this->owner->getAttribute($this->itemAttribute);
715
716 39
        if ($item !== null) {
717 39
            $path = $this->node->getParentPath();
0 ignored issues
show
Bug introduced by
The method getParentPath 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...
718 39
            $this->owner->setAttribute($this->pathAttribute, $path . $this->delimiter . $item);
719 39
        }
720
721 39
        $this->owner->setAttribute($this->depthAttribute, $this->node->getAttribute($this->depthAttribute));
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...
722
723 39
        if ($this->treeAttribute !== null) {
724 39
            $this->owner->setAttribute($this->treeAttribute, $this->node->getAttribute($this->treeAttribute));
725 39
        }
726
727 39
        if ($this->sortable !== false) {
728 39
            if ($forward) {
729 18
                $this->owner->moveAfter($this->node);
730 18
            } else {
731 21
                $this->owner->moveBefore($this->node);
732
            }
733 39
        }
734 39
    }
735
736
    /**
737
     * @return int
738
     */
739 9
    protected function deleteWithChildrenInternal()
740
    {
741 9
        if (!$this->owner->beforeDelete()) {
742
            return false;
743
        }
744 6
        $result = $this->owner->deleteAll($this->getDescendants(null, true)->where);
745 6
        $this->owner->setOldAttributes(null);
746 6
        $this->owner->afterDelete();
747 6
        return $result;
748
    }
749
750
    /**
751
     * @param array $changedAttributes
752
     * @throws Exception
753
     */
754 57
    protected function moveNode($changedAttributes)
755
    {
756 57
        $path = isset($changedAttributes[$this->pathAttribute]) ? $changedAttributes[$this->pathAttribute] : $this->owner->getAttribute($this->pathAttribute);
757 57
        $update = [];
758
        $condition = [
759 57
            'and',
760 57
            ['like', "[[{$this->pathAttribute}]]", $this->getLike($path), false],
761 57
        ];
762 57
        if ($this->treeAttribute !== null) {
763 54
            $tree = isset($changedAttributes[$this->treeAttribute]) ? $changedAttributes[$this->treeAttribute] : $this->owner->getAttribute($this->treeAttribute);
764 54
            $condition[] = [$this->treeAttribute => $tree];
765 54
        }
766 57
        $params = [];
767
768 57
        if (isset($changedAttributes[$this->pathAttribute])) {
769 30
            $update['path']     = new Expression($this->concatExpression([':pathNew', $this->substringExpression('[[path]]', 'LENGTH(:pathOld) + 1', 'LENGTH([[path]]) - LENGTH(:pathOld)')]));
770 30
            $params[':pathOld'] = $path;
771 30
            $params[':pathNew'] = $this->owner->getAttribute($this->pathAttribute);
772 30
        }
773
774 57
        if ($this->treeAttribute !== null && isset($changedAttributes[$this->treeAttribute])) {
775 15
            $update[$this->treeAttribute] = $this->owner->getAttribute($this->treeAttribute);
776 15
        }
777
778 57
        if ($this->depthAttribute !== null && isset($changedAttributes[$this->depthAttribute])) {
779 30
            $delta = $this->owner->getAttribute($this->depthAttribute) - $changedAttributes[$this->depthAttribute];
780 30
            $update[$this->depthAttribute] = new Expression("[[{$this->depthAttribute}]]" . sprintf('%+d', $delta));
781 30
        }
782
783 57
        if (!empty($update)) {
784 30
            $this->owner->updateAll($update, $condition, $params);
785 30
        }
786 57
    }
787
788
    /**
789
     * @param string $path
790
     * @param string $delimiter
791
     * @param bool $asArray = false
792
     * @return null|string|array
793
     */
794 66
    protected static function getParentPathInternal($path, $delimiter, $asArray = false)
795
    {
796 66
        $path = explode($delimiter, $path);
797 66
        array_pop($path);
798 66
        if ($asArray) {
799 6
            return $path;
800
        }
801 63
        return count($path) > 0 ? implode($delimiter, $path) : null;
802
    }
803
804
    /**
805
     * @return array
806
     */
807 123
    protected function treeCondition()
808
    {
809 123
        $tableName = $this->owner->tableName();
810 123
        if ($this->treeAttribute === null) {
811 123
            return [];
812
        } else {
813 123
            return ["{$tableName}.[[{$this->treeAttribute}]]" => $this->owner->getAttribute($this->treeAttribute)];
814
        }
815
    }
816
817
    /**
818
     * @return \yii\db\ActiveQuery
819
     */
820 87
    protected function getSortableQuery()
821
    {
822 87
        switch ($this->operation) {
823 87
            case self::OPERATION_PREPEND_TO:
824 87
            case self::OPERATION_APPEND_TO:
825 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...
826 42
                $depth = $this->node->getAttribute($this->depthAttribute) + 1;
827 42
                break;
828
829 45
            case self::OPERATION_INSERT_BEFORE:
830 45
            case self::OPERATION_INSERT_AFTER:
831 39
                $path  = $this->node->getParentPath();
0 ignored issues
show
Bug introduced by
The method getParentPath 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...
832 39
                $depth = $this->node->getAttribute($this->depthAttribute);
833 39
                break;
834
835 6
            default:
836 6
                $path  = $this->getParentPath();
837 6
                $depth = $this->owner->getAttribute($this->depthAttribute);
838 87
        }
839 87
        $tableName = $this->owner->tableName();
840
841 87
        return $this->owner->find()
842 87
            ->andWhere($this->treeCondition())
843 87
            ->andWhere($path !== null ? ['like', "{$tableName}.[[{$this->pathAttribute}]]", $this->getLike($path), false] : '1=0')
844 87
            ->andWhere(["{$tableName}.[[{$this->depthAttribute}]]" => $depth]);
845
    }
846
847
    /**
848
     * @param string $path
849
     * @return string
850
     */
851 117
    protected function getLike($path)
852
    {
853 117
        return strtr($path . $this->delimiter, ['%' => '\%', '_' => '\_', '\\' => '\\\\']) . '%';
854
    }
855
856
    /**
857
     * @param array $items
858
     * @return string
859
     */
860 33
    protected function concatExpression($items)
861
    {
862 33
        if ($this->owner->getDb()->driverName === 'sqlite' || $this->owner->getDb()->driverName === 'pgsql') {
863 22
            return implode(' || ', $items);
864
        }
865 11
        return 'CONCAT(' . implode(',', $items) . ')';
866
    }
867
868 30
    protected function substringExpression($string, $from, $length)
869
    {
870 30
        if ($this->owner->getDb()->driverName === 'sqlite') {
871 10
            return "SUBSTR({$string}, {$from}, {$length})";
872
        }
873 20
        return "SUBSTRING({$string}, {$from}, {$length})";
874
    }
875
}
876