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 ( d89e96...ec54c5 )
by Pavel
03:45
created

MaterializedPathBehavior::insertAfter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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