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.

MaterializedPathBehavior   F
last analyzed

Complexity

Total Complexity 139

Size/Duplication

Total Lines 908
Duplicated Lines 10.13 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 90.29%

Importance

Changes 0
Metric Value
wmc 139
lcom 1
cbo 12
dl 92
loc 908
ccs 372
cts 412
cp 0.9029
rs 1.692
c 0
b 0
f 0

42 Methods

Rating   Name   Duplication   Size   Complexity  
A getParent() 0 6 1
A getRoot() 0 12 1
A getChildren() 0 4 1
A getPrev() 16 16 2
A getNext() 16 16 2
A getParentPath() 0 4 1
A isChildOf() 0 14 4
A events() 0 11 1
A attach() 0 24 4
B getParents() 0 37 6
A getDescendants() 3 29 4
A getLeaves() 0 18 2
A getSiblings() 3 27 3
B populateTree() 0 32 8
A isRoot() 0 4 1
A isLeaf() 0 4 1
A makeRoot() 0 5 1
A prependTo() 0 6 1
A appendTo() 0 6 1
A insertBefore() 0 6 1
A insertAfter() 0 6 1
A preDeleteWithChildren() 0 4 1
A deleteWithChildren() 0 22 4
A reorderChildren() 0 10 3
B beforeSave() 0 39 10
B afterInsert() 6 35 10
A afterUpdate() 0 6 1
B beforeDelete() 0 24 7
A afterDelete() 0 15 4
A getPrimaryKeyValue() 0 5 1
A checkNode() 0 17 6
A makeRootInternal() 0 18 6
A insertIntoInternal() 24 24 5
A insertNearInternal() 24 24 5
A deleteWithChildrenInternal() 0 10 2
B moveNode() 0 38 10
A getParentPathInternal() 0 9 3
A treeCondition() 0 9 2
B getSortableQuery() 0 26 6
A getLike() 0 4 1
A concatExpression() 0 7 3
A substringExpression() 0 7 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like MaterializedPathBehavior often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use MaterializedPathBehavior, and based on these observations, apply Extract Interface, too.

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