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.

AdjacencyListBehavior   F
last analyzed

Complexity

Total Complexity 129

Size/Duplication

Total Lines 752
Duplicated Lines 7.71 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 93.55%

Importance

Changes 0
Metric Value
wmc 129
lcom 1
cbo 10
dl 58
loc 752
ccs 319
cts 341
cp 0.9355
rs 1.848
c 0
b 0
f 0

35 Methods

Rating   Name   Duplication   Size   Complexity  
A events() 0 11 1
A attach() 0 14 2
A getParents() 0 9 1
B getParentsOrdered() 9 22 6
A getParent() 0 4 1
A getRoot() 0 10 2
A getDescendants() 0 12 2
A getDescendantsOrdered() 9 20 4
A getChildren() 0 8 2
A getLeaves() 0 14 1
A getPrev() 17 17 2
A getNext() 17 17 2
C getParentsIds() 0 45 17
F getDescendantsIds() 6 69 25
C populateTree() 0 39 12
A isRoot() 0 4 1
A isChildOf() 0 5 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 2
B beforeSave() 0 35 10
A afterSave() 0 5 1
A beforeDelete() 0 10 4
A afterDelete() 0 10 2
A getPrimaryKey() 0 8 2
B checkNode() 0 17 7
A insertIntoInternal() 0 12 3
A insertNearInternal() 0 12 3
A deleteWithChildrenInternal() 0 12 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 AdjacencyListBehavior 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 AdjacencyListBehavior, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link https://github.com/paulzi/yii2-adjacency-list
4
 * @copyright Copyright (c) 2015 PaulZi <[email protected]>
5
 * @license MIT (https://github.com/paulzi/yii2-adjacency-list/blob/master/LICENSE)
6
 */
7
8
namespace paulzi\adjacencyList;
9
10
use Yii;
11
use yii\base\Behavior;
12
use yii\base\Exception;
13
use yii\base\NotSupportedException;
14
use yii\db\ActiveRecord;
15
use yii\db\Query;
16
use paulzi\sortable\SortableBehavior;
17
18
19
/**
20
 * Adjacency List Behavior for Yii2
21
 * @author PaulZi <[email protected]>
22
 *
23
 * @property ActiveRecord $owner
24
 */
25
class AdjacencyListBehavior extends Behavior
26
{
27
    const OPERATION_MAKE_ROOT       = 1;
28
    const OPERATION_PREPEND_TO      = 2;
29
    const OPERATION_APPEND_TO       = 3;
30
    const OPERATION_INSERT_BEFORE   = 4;
31
    const OPERATION_INSERT_AFTER    = 5;
32
    const OPERATION_DELETE_ALL      = 6;
33
34
    /**
35
     * @var string
36
     */
37
    public $parentAttribute = 'parent_id';
38
39
    /**
40
     * @var array|false SortableBehavior config
41
     */
42
    public $sortable = [];
43
44
    /**
45
     * @var bool
46
     */
47
    public $checkLoop = false;
48
49
    /**
50
     * @var int
51
     */
52
    public $parentsJoinLevels = 3;
53
54
    /**
55
     * @var int
56
     */
57
    public $childrenJoinLevels = 3;
58
59
    /**
60
     * @var bool
61
     */
62
    protected $operation;
63
64
    /**
65
     * @var ActiveRecord|self|null
66
     */
67
    protected $node;
68
69
    /**
70
     * @var SortableBehavior
71
     */
72
    protected $behavior;
73
74
    /**
75
     * @var ActiveRecord[]
76
     */
77
    private $_parentsOrdered;
78
79
    /**
80
     * @var array
81
     */
82
    private $_parentsIds;
83
84
    /**
85
     * @var array
86
     */
87
    private $_childrenIds;
88
89
90
    /**
91
     * @inheritdoc
92
     */
93 225
    public function events()
94
    {
95
        return [
96 225
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeSave',
97
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterSave',
98
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
99
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterSave',
100
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
101
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
102
        ];
103
    }
104
105
    /**
106
     * @param ActiveRecord $owner
107
     */
108 225
    public function attach($owner)
109
    {
110 225
        parent::attach($owner);
111 225
        if ($this->sortable !== false) {
112 225
            $this->behavior = Yii::createObject(array_merge(
113
                [
114 225
                    'class'         => SortableBehavior::className(),
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
115 225
                    'query'         => [$this->parentAttribute],
116
                ],
117 225
                $this->sortable
118
            ));
119 225
            $owner->attachBehavior('adjacency-list-sortable', $this->behavior);
120
        }
121 225
    }
122
123
    /**
124
     * @param int|null $depth
125
     * @return \yii\db\ActiveQuery
126
     * @throws Exception
127
     */
128 6
    public function getParents($depth = null)
129
    {
130 6
        $tableName = $this->owner->tableName();
131 6
        $ids = $this->getParentsIds($depth);
132 6
        $query = $this->owner->find()
133 6
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $ids]);
134 6
        $query->multiple = true;
135 6
        return $query;
136
    }
137
138
    /**
139
     * @param int|null $depth
140
     * @return ActiveRecord[]
141
     * @throws Exception
142
     */
143 3
    public function getParentsOrdered($depth = null)
144
    {
145 3
        if ($depth === null && $this->_parentsOrdered !== null) {
146
            return $this->_parentsOrdered;
147
        }
148 3
        $parents = $this->getParents($depth)->all();
149 3
        $ids = array_flip($this->getParentsIds());
150 3
        $primaryKey = $this->getPrimaryKey();
151 View Code Duplication
        usort($parents, function($a, $b) use ($ids, $primaryKey) {
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...
152 3
            $aIdx = $ids[$a->$primaryKey];
153 3
            $bIdx = $ids[$b->$primaryKey];
154 3
            if ($aIdx == $bIdx) {
155
                return 0;
156
            } else {
157 3
                return $aIdx > $bIdx ? -1 : 1;
158
            }
159 3
        });
160 3
        if ($depth !== null) {
161 3
            $this->_parentsOrdered = $parents;
162
        }
163 3
        return $parents;
164
    }
165
166
    /**
167
     * @return \yii\db\ActiveQuery
168
     * @throws Exception
169
     */
170 3
    public function getParent()
171
    {
172 3
        return $this->owner->hasOne($this->owner->className(), [$this->getPrimaryKey() => $this->parentAttribute]);
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
173
    }
174
175
    /**
176
     * @return \yii\db\ActiveQuery
177
     */
178 3
    public function getRoot()
179
    {
180 3
        $tableName = $this->owner->tableName();
181 3
        $id = $this->getParentsIds();
182 3
        $id = $id ? $id[count($id) - 1] : $this->owner->primaryKey;
183 3
        $query = $this->owner->find()
184 3
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $id]);
185 3
        $query->multiple = false;
186 3
        return $query;
187
    }
188
189
    /**
190
     * @param int|null $depth
191
     * @param bool $andSelf
192
     * @return \yii\db\ActiveQuery
193
     */
194 12
    public function getDescendants($depth = null, $andSelf = false)
195
    {
196 12
        $tableName = $this->owner->tableName();
197 12
        $ids = $this->getDescendantsIds($depth, true);
198 12
        if ($andSelf) {
199 3
            $ids[] = $this->owner->getPrimaryKey();
200
        }
201 12
        $query = $this->owner->find()
202 12
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $ids]);
203 12
        $query->multiple = true;
204 12
        return $query;
205
    }
206
207
    /**
208
     * @param int|null $depth
209
     * @return ActiveRecord[]
210
     * @throws Exception
211
     */
212 3
    public function getDescendantsOrdered($depth = null)
213
    {
214 3
        if ($depth === null) {
215 3
            $descendants = $this->owner->descendants;
216
        } else {
217 3
            $descendants = $this->getDescendants($depth)->all();
218
        }
219 3
        $ids = array_flip($this->getDescendantsIds($depth, true));
220 3
        $primaryKey = $this->getPrimaryKey();
221 View Code Duplication
        usort($descendants, function($a, $b) use ($ids, $primaryKey) {
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...
222 3
            $aIdx = $ids[$a->$primaryKey];
223 3
            $bIdx = $ids[$b->$primaryKey];
224 3
            if ($aIdx == $bIdx) {
225
                return 0;
226
            } else {
227 3
                return $aIdx > $bIdx ? 1 : -1;
228
            }
229 3
        });
230 3
        return $descendants;
231
    }
232
233
    /**
234
     * @return \yii\db\ActiveQuery
235
     */
236 12
    public function getChildren()
237
    {
238 12
        $result = $this->owner->hasMany($this->owner->className(), [$this->parentAttribute => $this->getPrimaryKey()]);
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
239 12
        if ($this->sortable !== false) {
240 12
            $result->orderBy([$this->behavior->sortAttribute => SORT_ASC]);
241
        }
242 12
        return $result;
243
    }
244
245
    /**
246
     * @param int|null $depth
247
     * @return \yii\db\ActiveQuery
248
     */
249 3
    public function getLeaves($depth = null)
250
    {
251 3
        $query = $this->getDescendants($depth)
252 3
            ->joinWith(['children' => function ($query) {
253
                /** @var \yii\db\ActiveQuery $query */
254 3
                $modelClass = $query->modelClass;
255
                $query
256 3
                    ->from($modelClass::tableName() . ' children')
257 3
                    ->orderBy(null);
258 3
            }])
259 3
            ->andWhere(["children.[[{$this->parentAttribute}]]" => null]);
260 3
        $query->multiple = true;
261 3
        return $query;
262
    }
263
264
    /**
265
     * @return \yii\db\ActiveQuery
266
     * @throws NotSupportedException
267
     */
268 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...
269
    {
270 3
        if ($this->sortable === false) {
271
            throw new NotSupportedException('prev() not allow if not set sortable');
272
        }
273 3
        $tableName = $this->owner->tableName();
274 3
        $query = $this->owner->find()
275 3
            ->andWhere([
276 3
                'and',
277 3
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
278 3
                ['<', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
279
            ])
280 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_DESC])
281 3
            ->limit(1);
282 3
        $query->multiple = false;
283 3
        return $query;
284
    }
285
286
    /**
287
     * @return \yii\db\ActiveQuery
288
     * @throws NotSupportedException
289
     */
290 3 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...
291
    {
292 3
        if ($this->sortable === false) {
293
            throw new NotSupportedException('next() not allow if not set sortable');
294
        }
295 3
        $tableName = $this->owner->tableName();
296 3
        $query = $this->owner->find()
297 3
            ->andWhere([
298 3
                'and',
299 3
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
300 3
                ['>', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
301
            ])
302 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC])
303 3
            ->limit(1);
304 3
        $query->multiple = false;
305 3
        return $query;
306
    }
307
308
    /**
309
     * @param int|null $depth
310
     * @param bool $cache
311
     * @return array
312
     */
313 27
    public function getParentsIds($depth = null, $cache = true)
314
    {
315 27
        if ($cache && $this->_parentsIds !== null) {
316 3
            return $depth === null ? $this->_parentsIds : array_slice($this->_parentsIds, 0, $depth);
317
        }
318
319 27
        $parentId = $this->owner->getAttribute($this->parentAttribute);
320 27
        if ($parentId === null) {
321 9
            if ($cache) {
322 9
                $this->_parentsIds = [];
323
            }
324 9
            return [];
325
        }
326 27
        $result     = [(string)$parentId];
327 27
        $tableName  = $this->owner->tableName();
328 27
        $primaryKey = $this->getPrimaryKey();
329 27
        $depthCur   = 1;
330 27
        while ($parentId !== null && ($depth === null || $depthCur < $depth)) {
331 27
            $query = (new Query())
332 27
                ->select(["lvl0.[[{$this->parentAttribute}]] AS lvl0"])
333 27
                ->from("{$tableName} lvl0")
334 27
                ->where(["lvl0.[[{$primaryKey}]]" => $parentId]);
335 27
            for ($i = 0; $i < $this->parentsJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
336 15
                $j = $i + 1;
337
                $query
338 15
                    ->addSelect(["lvl{$j}.[[{$this->parentAttribute}]] as lvl{$j}"])
339 15
                    ->leftJoin("{$tableName} lvl{$j}", "lvl{$j}.[[{$primaryKey}]] = lvl{$i}.[[{$this->parentAttribute}]]");
340
            }
341 27
            if ($parentIds = $query->one($this->owner->getDb())) {
342 27
                foreach ($parentIds as $parentId) {
343 27
                    $depthCur++;
344 27
                    if ($parentId === null) {
345 27
                        break;
346
                    }
347 27
                    $result[] = $parentId;
348
                }
349
            } else {
350
                $parentId = null;
351
            }
352
        }
353 27
        if ($cache && $depth === null) {
354 24
            $this->_parentsIds = $result;
355
        }
356 27
        return $result;
357
    }
358
359
    /**
360
     * @param int|null $depth
361
     * @param bool $flat
362
     * @param bool $cache
363
     * @return array
364
     */
365 24
    public function getDescendantsIds($depth = null, $flat = false, $cache = true)
366
    {
367 24
        if ($cache && $this->_childrenIds !== null) {
368 6
            $result = $depth === null ? $this->_childrenIds : array_slice($this->_childrenIds, 0, $depth);
369 6
            return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
370
        }
371
372 24
        $result       = [];
373 24
        $tableName    = $this->owner->tableName();
374 24
        $primaryKey   = $this->getPrimaryKey();
375 24
        $depthCur     = 0;
376 24
        $lastLevelIds = [$this->owner->primaryKey];
377 24
        while (!empty($lastLevelIds) && ($depth === null || $depthCur < $depth)) {
378 24
            $levels = 1;
379 24
            $depthCur++;
380 24
            $query = (new Query())
381 24
                ->select(["lvl0.[[{$primaryKey}]] AS lvl0"])
382 24
                ->from("{$tableName} lvl0")
383 24
                ->where(["lvl0.[[{$this->parentAttribute}]]" => $lastLevelIds]);
384 24 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...
385 24
                $query->orderBy(["lvl0.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
386
            }
387 24
            for ($i = 0; $i < $this->childrenJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
388 18
                $depthCur++;
389 18
                $levels++;
390 18
                $j = $i + 1;
391
                $query
392 18
                    ->addSelect(["lvl{$j}.[[{$primaryKey}]] as lvl{$j}"])
393 18
                    ->leftJoin("{$tableName} lvl{$j}", [
394 18
                        'and',
395 18
                        "lvl{$j}.[[{$this->parentAttribute}]] = lvl{$i}.[[{$primaryKey}]]",
396 18
                        ['is not', "lvl{$i}.[[{$primaryKey}]]", null],
397
                    ]);
398 18 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...
399 18
                    $query->addOrderBy(["lvl{$j}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
400
                }
401
            }
402 24
            if ($this->childrenJoinLevels) {
403 21
                $columns = [];
404 21
                foreach ($query->all($this->owner->getDb()) as $row) {
405 21
                    $level = 0;
406 21
                    foreach ($row as $id) {
407 21
                        if ($id !== null) {
408 21
                            $columns[$level][$id] = true;
409
                        }
410 21
                        $level++;
411
                    }
412
                }
413 21
                for ($i = 0; $i < $levels; $i++) {
414 21
                    if (isset($columns[$i])) {
415 21
                        $lastLevelIds = array_keys($columns[$i]);
416 21
                        $result[]     = $lastLevelIds;
417
                    } else {
418 18
                        $lastLevelIds = [];
419 18
                        break;
420
                    }
421
                }
422
            } else {
423 21
                $lastLevelIds = $query->column($this->owner->getDb());
424 21
                if ($lastLevelIds) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastLevelIds of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
425 21
                    $result[] = $lastLevelIds;
426
                }
427
            }
428
        }
429 24
        if ($cache && $depth === null) {
430 24
            $this->_childrenIds = $result;
431
        }
432 24
        return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
433
    }
434
435
    /**
436
     * Populate children relations for self and all descendants
437
     *
438
     * @param int $depth = null
439
     * @param string|array $with = null
440
     * @return static
441
     */
442 3
    public function populateTree($depth = null, $with = null)
443
    {
444
        /** @var ActiveRecord[]|static[] $nodes */
445 3
        $depths = [$this->owner->getPrimaryKey() => 0];
446 3
        $data = $this->getDescendantsIds($depth);
447 3
        foreach ($data as $i => $ids) {
448 3
            foreach ($ids as $id) {
449 3
                $depths[$id] = $i + 1;
450
            }
451
        }
452 3
        $query = $this->getDescendants($depth);
453 3
        if ($with) {
454
            $query->with($with);
455
        }
456
        $nodes = $query
457 3
            ->orderBy($this->sortable !== false ? [$this->behavior->sortAttribute => SORT_ASC] : null)
0 ignored issues
show
Bug introduced by
It seems like $this->sortable !== fals...ute => SORT_ASC) : null can also be of type null; however, yii\db\QueryTrait::orderBy() does only seem to accept string|array|object<yii\db\ExpressionInterface>, 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...
458 3
            ->all();
459
460 3
        $relates = [];
461 3
        foreach ($nodes as $node) {
462 3
            $key = $node->getAttribute($this->parentAttribute);
463 3
            if (!isset($relates[$key])) {
464 3
                $relates[$key] = [];
465
            }
466 3
            $relates[$key][] = $node;
467
        }
468
469 3
        $nodes[] = $this->owner;
470 3
        foreach ($nodes as $node) {
471 3
            $key = $node->getPrimaryKey();
472 3
            if (isset($relates[$key])) {
473 3
                $node->populateRelation('children', $relates[$key]);
474 3
            } elseif ($depth === null || (isset($depths[$node->getPrimaryKey()]) && $depths[$node->getPrimaryKey()] < $depth)) {
475 3
                $node->populateRelation('children', []);
476
            }
477
        }
478
479 3
        return $this->owner;
480
    }
481
482
    /**
483
     * @return bool
484
     */
485 81
    public function isRoot()
486
    {
487 81
        return $this->owner->getAttribute($this->parentAttribute) === null;
488
    }
489
490
    /**
491
     * @param ActiveRecord $node
492
     * @return bool
493
     */
494 15
    public function isChildOf($node)
495
    {
496 15
        $ids = $this->getParentsIds();
497 15
        return in_array($node->getPrimaryKey(), $ids);
498
    }
499
500
    /**
501
     * @return bool
502
     */
503 3
    public function isLeaf()
504
    {
505 3
        return count($this->owner->children) === 0;
506
    }
507
508
    /**
509
     * @return ActiveRecord
510
     */
511 6
    public function makeRoot()
512
    {
513 6
        $this->operation = self::OPERATION_MAKE_ROOT;
0 ignored issues
show
Documentation Bug introduced by
The property $operation was declared of type boolean, but self::OPERATION_MAKE_ROOT is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
514 6
        return $this->owner;
515
    }
516
517
    /**
518
     * @param ActiveRecord $node
519
     * @return ActiveRecord
520
     */
521 36
    public function prependTo($node)
522
    {
523 36
        $this->operation = self::OPERATION_PREPEND_TO;
0 ignored issues
show
Documentation Bug introduced by
The property $operation was declared of type boolean, but self::OPERATION_PREPEND_TO is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
524 36
        $this->node = $node;
525 36
        return $this->owner;
526
    }
527
528
    /**
529
     * @param ActiveRecord $node
530
     * @return ActiveRecord
531
     */
532 36
    public function appendTo($node)
533
    {
534 36
        $this->operation = self::OPERATION_APPEND_TO;
0 ignored issues
show
Documentation Bug introduced by
The property $operation was declared of type boolean, but self::OPERATION_APPEND_TO is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
535 36
        $this->node = $node;
536 36
        return $this->owner;
537
    }
538
539
    /**
540
     * @param ActiveRecord $node
541
     * @return ActiveRecord
542
     */
543 30
    public function insertBefore($node)
544
    {
545 30
        $this->operation = self::OPERATION_INSERT_BEFORE;
0 ignored issues
show
Documentation Bug introduced by
The property $operation was declared of type boolean, but self::OPERATION_INSERT_BEFORE is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
546 30
        $this->node = $node;
547 30
        return $this->owner;
548
    }
549
550
    /**
551
     * @param ActiveRecord $node
552
     * @return ActiveRecord
553
     */
554 33
    public function insertAfter($node)
555
    {
556 33
        $this->operation = self::OPERATION_INSERT_AFTER;
0 ignored issues
show
Documentation Bug introduced by
The property $operation was declared of type boolean, but self::OPERATION_INSERT_AFTER is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
557 33
        $this->node = $node;
558 33
        return $this->owner;
559
    }
560
561
    /**
562
     * Need for paulzi/auto-tree
563
     */
564
    public function preDeleteWithChildren()
565
    {
566
        $this->operation = self::OPERATION_DELETE_ALL;
0 ignored issues
show
Documentation Bug introduced by
The property $operation was declared of type boolean, but self::OPERATION_DELETE_ALL is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
567
    }
568
569
    /**
570
     * @return bool|int
571
     * @throws \Exception
572
     * @throws \yii\db\Exception
573
     */
574 12
    public function deleteWithChildren()
575
    {
576 12
        $this->operation = self::OPERATION_DELETE_ALL;
0 ignored issues
show
Documentation Bug introduced by
The property $operation was declared of type boolean, but self::OPERATION_DELETE_ALL is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
577 12
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
578
            $transaction = $this->owner->getDb()->beginTransaction();
579
            try {
580
                $result = $this->deleteWithChildrenInternal();
581
                if ($result === false) {
582
                    $transaction->rollBack();
583
                } else {
584
                    $transaction->commit();
585
                }
586
                return $result;
587
            } catch (\Exception $e) {
588
                $transaction->rollBack();
589
                throw $e;
590
            }
591
        } else {
592 12
            $result = $this->deleteWithChildrenInternal();
593
        }
594 9
        return $result;
595
    }
596
597
    /**
598
     * @param bool $middle
599
     * @return int
600
     */
601 3
    public function reorderChildren($middle = true)
602
    {
603
        /** @var ActiveRecord|SortableBehavior $item */
604 3
        $item = $this->owner->children[0];
605 3
        if ($item) {
606 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...
607
        } else {
608
            return 0;
609
        }
610
    }
611
612
    /**
613
     * @throws Exception
614
     * @throws NotSupportedException
615
     */
616 147
    public function beforeSave()
617
    {
618 147
        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\adjacencyList\AdjacencyListBehavior.

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

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...
620
        }
621 147
        switch ($this->operation) {
622 147
            case self::OPERATION_MAKE_ROOT:
623 6
                $this->owner->setAttribute($this->parentAttribute, null);
624 6
                if ($this->sortable !== false) {
625 6
                    $this->owner->setAttribute($this->behavior->sortAttribute, 0);
626
                }
627 6
                break;
628
629 141
            case self::OPERATION_PREPEND_TO:
630 36
                $this->insertIntoInternal(false);
631 24
                break;
632
633 105
            case self::OPERATION_APPEND_TO:
634 36
                $this->insertIntoInternal(true);
635 24
                break;
636
637 69
            case self::OPERATION_INSERT_BEFORE:
638 30
                $this->insertNearInternal(false);
639 21
                break;
640
641 39
            case self::OPERATION_INSERT_AFTER:
642 33
                $this->insertNearInternal(true);
643 21
                break;
644
645
            default:
646 6
                if ($this->owner->getIsNewRecord()) {
647 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
648
                }
649
        }
650 99
    }
651
652
    /**
653
     *
654
     */
655 99
    public function afterSave()
656
    {
657 99
        $this->operation = null;
658 99
        $this->node      = null;
659 99
    }
660
661
    /**
662
     * @param \yii\base\ModelEvent $event
663
     * @throws Exception
664
     */
665 21
    public function beforeDelete($event)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
666
    {
667 21
        if ($this->owner->getIsNewRecord()) {
668 6
            throw new Exception('Can not delete a node when it is new record.');
669
        }
670 15
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
671 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
672
        }
673 12
        $this->owner->refresh();
674 12
    }
675
676
    /**
677
     *
678
     */
679 12
    public function afterDelete()
680
    {
681 12
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
682 3
            $this->owner->updateAll(
683 3
                [$this->parentAttribute => $this->owner->getAttribute($this->parentAttribute)],
684 3
                [$this->parentAttribute => $this->owner->getPrimaryKey()]
685
            );
686
        }
687 12
        $this->operation = null;
688 12
    }
689
690
    /**
691
     * @return mixed
692
     * @throws Exception
693
     */
694 63
    protected function getPrimaryKey()
695
    {
696 63
        $primaryKey = $this->owner->primaryKey();
697 63
        if (!isset($primaryKey[0])) {
698
            throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
699
        }
700 63
        return $primaryKey[0];
701
    }
702
703
    /**
704
     * @param bool $forInsertNear
705
     * @throws Exception
706
     */
707 135
    protected function checkNode($forInsertNear = false)
708
    {
709 135
        if ($forInsertNear && $this->node->isRoot()) {
0 ignored issues
show
Bug introduced by
The method isRoot does only exist in paulzi\adjacencyList\AdjacencyListBehavior, 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...
710 9
            throw new Exception('Can not move a node before/after root.');
711
        }
712 126
        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\adjacencyList\AdjacencyListBehavior.

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...
713 12
            throw new Exception('Can not move a node when the target node is new record.');
714
        }
715
716 114
        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\adjacencyL...\AdjacencyListBehavior>; 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...
717 12
            throw new Exception('Can not move a node when the target node is same.');
718
        }
719
720 102
        if ($this->checkLoop && $this->node->isChildOf($this->owner)) {
0 ignored issues
show
Bug introduced by
The method isChildOf does only exist in paulzi\adjacencyList\AdjacencyListBehavior, 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...
721 12
            throw new Exception('Can not move a node when the target node is child.');
722
        }
723 90
    }
724
725
    /**
726
     * Append to operation internal handler
727
     * @param bool $append
728
     * @throws Exception
729
     */
730 72
    protected function insertIntoInternal($append)
731
    {
732 72
        $this->checkNode(false);
733 48
        $this->owner->setAttribute($this->parentAttribute, $this->node->getPrimaryKey());
734 48
        if ($this->sortable !== false) {
735 48
            if ($append) {
736 24
                $this->behavior->moveLast();
737
            } else {
738 24
                $this->behavior->moveFirst();
739
            }
740
        }
741 48
    }
742
743
    /**
744
     * Insert operation internal handler
745
     * @param bool $forward
746
     * @throws Exception
747
     */
748 63
    protected function insertNearInternal($forward)
749
    {
750 63
        $this->checkNode(true);
751 42
        $this->owner->setAttribute($this->parentAttribute, $this->node->getAttribute($this->parentAttribute));
0 ignored issues
show
Bug introduced by
The method getAttribute does only exist in yii\db\ActiveRecord, but not in paulzi\adjacencyList\AdjacencyListBehavior.

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 42
        if ($this->sortable !== false) {
753 42
            if ($forward) {
754 21
                $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\adjacencyL...\AdjacencyListBehavior>; 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...
755
            } else {
756 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\adjacencyL...\AdjacencyListBehavior>; 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...
757
            }
758
        }
759 42
    }
760
761
    /**
762
     * @return int
763
     */
764 12
    protected function deleteWithChildrenInternal()
765
    {
766 12
        if (!$this->owner->beforeDelete()) {
767
            return false;
768
        }
769 9
        $ids = $this->getDescendantsIds(null, true);
770 9
        $ids[] = $this->owner->primaryKey;
771 9
        $result = $this->owner->deleteAll([$this->getPrimaryKey() => $ids]);
772 9
        $this->owner->setOldAttributes(null);
773 9
        $this->owner->afterDelete();
774 9
        return $result;
775
    }
776
}