GitHub Access Token became invalid

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

AdjacencyListBehavior::getParentsIds()   D

Complexity

Conditions 17
Paths 22

Size

Total Lines 45
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 17.0097
Metric Value
dl 0
loc 45
ccs 30
cts 31
cp 0.9677
rs 4.9807
cc 17
eloc 33
nc 22
nop 2
crap 17.0097

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link https://github.com/paulzi/yii2-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 219
     */
93
    public function events()
94
    {
95 219
        return [
96 219
            ActiveRecord::EVENT_BEFORE_INSERT   => 'beforeSave',
97 219
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterSave',
98 219
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
99 219
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterSave',
100 219
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
101 219
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
102
        ];
103
    }
104
105
    /**
106
     * @param ActiveRecord $owner
107
     */
108
    public function attach($owner)
109 6
    {
110
        parent::attach($owner);
111 6
        if ($this->sortable !== false) {
112 6
            $this->behavior = Yii::createObject(array_merge(
113 6
                [
114 6
                    'class'         => SortableBehavior::className(),
115 6
                    'query'         => [$this->parentAttribute],
116 6
                ],
117
                $this->sortable
118
            ));
119
            $owner->attachBehavior('adjacency-list-sortable', $this->behavior);
120
        }
121
    }
122
123 3
    /**
124
     * @param int|null $depth
125 3
     * @return \yii\db\ActiveQuery
126
     * @throws Exception
127
     */
128 3
    public function getParents($depth = null)
129 3
    {
130 3
        $tableName = $this->owner->tableName();
131
        $ids = $this->getParentsIds($depth);
132 3
        $query = $this->owner->find()
133 3
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $ids]);
134 3
        $query->multiple = true;
135
        return $query;
136
    }
137 3
138
    /**
139 3
     * @return ActiveRecord[]
140 3
     * @throws Exception
141
     */
142
    public function getParentsOrdered()
143
    {
144
        if ($this->_parentsOrdered !== null) {
145
            return $this->_parentsOrdered;
146
        }
147 3
        $parents = $this->getParents()->all();
148
        $ids = array_flip($this->getParentsIds());
149 3
        $primaryKey = $this->getPrimaryKey();
150 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...
151
            $aIdx = $ids[$a->$primaryKey];
152
            $bIdx = $ids[$b->$primaryKey];
153
            if ($aIdx == $bIdx) {
154
                return 0;
155 3
            } else {
156
                return $aIdx > $bIdx ? -1 : 1;
157 3
            }
158 3
        });
159 3
        return $this->_parentsOrdered = $parents;
160 3
    }
161 3
162 3
    /**
163 3
     * @return \yii\db\ActiveQuery
164
     * @throws Exception
165
     */
166
    public function getParent()
167
    {
168
        return $this->owner->hasOne($this->owner->className(), [$this->getPrimaryKey() => $this->parentAttribute]);
169
    }
170
171 9
    /**
172
     * @return \yii\db\ActiveQuery
173 9
     */
174 9
    public function getRoot()
175 9
    {
176 3
        $tableName = $this->owner->tableName();
177 3
        $id = $this->getParentsIds();
178 9
        $id = $id ? $id[count($id) - 1] : $this->owner->primaryKey;
179 9
        $query = $this->owner->find()
180 9
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $id]);
181 9
        $query->multiple = false;
182
        return $query;
183
    }
184
185
    /**
186
     * @param int|null $depth
187
     * @param bool $andSelf
188 3
     * @return \yii\db\ActiveQuery
189
     */
190 3
    public function getDescendants($depth = null, $andSelf = false)
191 3
    {
192 3
        $tableName = $this->owner->tableName();
193
        $ids = $this->getDescendantsIds($depth, true);
194 3
        if ($andSelf) {
195 3
            $ids[] = $this->owner->getPrimaryKey();
196 3
        }
197
        $query = $this->owner->find()
198
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $ids]);
199 3
        $query->multiple = true;
200
        return $query;
201 3
    }
202 3
203
    /**
204
     * @return ActiveRecord[]
205
     * @throws Exception
206
     */
207
    public function getDescendantsOrdered()
208 57
    {
209
        $descendants = $this->owner->descendants;
210 57
        $ids = array_flip($this->getDescendantsIds(null, true));
211 57
        $primaryKey = $this->getPrimaryKey();
212 57 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...
213 57
            $aIdx = $ids[$a->$primaryKey];
214 57
            $bIdx = $ids[$b->$primaryKey];
215
            if ($aIdx == $bIdx) {
216
                return 0;
217
            } else {
218
                return $aIdx > $bIdx ? -1 : 1;
219
            }
220
        });
221 3
        return $descendants;
222
    }
223 3
224 3
    /**
225
     * @return \yii\db\ActiveQuery
226 3
     */
227
    public function getChildren()
228 3
    {
229 3
        $result = $this->owner->hasMany($this->owner->className(), [$this->parentAttribute => $this->getPrimaryKey()]);
230 3
        if ($this->sortable !== false) {
231 3
            $result->orderBy([$this->behavior->sortAttribute => SORT_ASC]);
232 3
        }
233 3
        return $result;
234
    }
235
236
    /**
237
     * @param int|null $depth
238
     * @return \yii\db\ActiveQuery
239
     */
240 3
    public function getLeaves($depth = null)
241
    {
242 3
        $query = $this->getDescendants($depth)
243
            ->joinWith(['children' => function ($query) {
244
                /** @var \yii\db\ActiveQuery $query */
245 3
                $modelClass = $query->modelClass;
246 3
                $query
247 3
                    ->from($modelClass::tableName() . ' children')
248 3
                    ->orderBy(null);
249 3
            }])
250 3
            ->andWhere(["children.[[{$this->parentAttribute}]]" => null]);
251 3
        $query->multiple = true;
252 3
        return $query;
253 3
    }
254 3
255 3
    /**
256
     * @return \yii\db\ActiveQuery
257
     * @throws NotSupportedException
258
     */
259 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...
260
    {
261
        if ($this->sortable === false) {
262 3
            throw new NotSupportedException('prev() not allow if not set sortable');
263
        }
264 3
        $tableName = $this->owner->tableName();
265
        $query = $this->owner->find()
266
            ->andWhere([
267 3
                'and',
268 3
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
269 3
                ['<', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
270 3
            ])
271 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_DESC])
272 3
            ->limit(1);
273 3
        $query->multiple = false;
274 3
        return $query;
275 3
    }
276 3
277 3
    /**
278
     * @return \yii\db\ActiveQuery
279
     * @throws NotSupportedException
280
     */
281 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...
282
    {
283
        if ($this->sortable === false) {
284
            throw new NotSupportedException('next() not allow if not set sortable');
285 27
        }
286
        $tableName = $this->owner->tableName();
287 27
        $query = $this->owner->find()
288 3
            ->andWhere([
289
                'and',
290
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
291 27
                ['>', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
292 27
            ])
293 9
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC])
294 9
            ->limit(1);
295 9
        $query->multiple = false;
296 9
        return $query;
297
    }
298 27
299 27
    /**
300 27
     * @param int|null $depth
301 27
     * @param bool $cache
302 27
     * @return array
303 27
     */
304 27
    public function getParentsIds($depth = null, $cache = true)
305 27
    {
306 27
        if ($cache && $this->_parentsIds !== null) {
307 27
            return $depth === null ? $this->_parentsIds : array_slice($this->_parentsIds, 0, $depth);
308 15
        }
309
310 15
        $parentId = $this->owner->getAttribute($this->parentAttribute);
311 15
        if ($parentId === null) {
312 15
            if ($cache) {
313 27
                $this->_parentsIds = [];
314 27
            }
315 27
            return [];
316 27
        }
317 27
        $result     = [(string)$parentId];
318
        $tableName  = $this->owner->tableName();
319 24
        $primaryKey = $this->getPrimaryKey();
320 27
        $depthCur   = 1;
321 27
        while ($parentId !== null && ($depth === null || $depthCur < $depth)) {
322
            $query = (new Query())
323
                ->select(["lvl0.[[{$this->parentAttribute}]] AS lvl0"])
324 27
                ->from("{$tableName} lvl0")
325 27
                ->where(["lvl0.[[{$primaryKey}]]" => $parentId]);
326 24
            for ($i = 0; $i < $this->parentsJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
327 24
                $j = $i + 1;
328 27
                $query
329
                    ->addSelect(["lvl{$j}.[[{$this->parentAttribute}]] as lvl{$j}"])
330
                    ->leftJoin("{$tableName} lvl{$j}", "lvl{$j}.[[{$primaryKey}]] = lvl{$i}.[[{$this->parentAttribute}]]");
331
            }
332
            if ($parentIds = $query->one($this->owner->getDb())) {
333
                foreach ($parentIds as $parentId) {
0 ignored issues
show
Bug introduced by
The expression $parentIds of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
334
                    $depthCur++;
335
                    if ($parentId === null) {
336
                        break;
337 21
                    }
338
                    $result[] = $parentId;
339 21
                }
340 3
            } else {
341 3
                $parentId = null;
342
            }
343
        }
344 21
        if ($cache && $depth === null) {
345 21
            $this->_parentsIds = $result;
346 21
        }
347 21
        return $result;
348 21
    }
349 21
350 21
    /**
351 21
     * @param int|null $depth
352 21
     * @param bool $flat
353 21
     * @param bool $cache
354 21
     * @return array
355 21
     */
356 21
    public function getDescendantsIds($depth = null, $flat = false, $cache = true)
357 21
    {
358 21
        if ($cache && $this->_childrenIds !== null) {
359 21
            $result = $depth === null ? $this->_childrenIds : array_slice($this->_childrenIds, 0, $depth);
360 18
            return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
361 18
        }
362 18
363
        $result       = [];
364 18
        $tableName    = $this->owner->tableName();
365 18
        $primaryKey   = $this->getPrimaryKey();
366 18
        $depthCur     = 0;
367 18
        $lastLevelIds = [$this->owner->primaryKey];
368 18
        while (!empty($lastLevelIds) && ($depth === null || $depthCur < $depth)) {
369 18
            $levels = 1;
370 18
            $depthCur++;
371 18
            $query = (new Query())
372 18
                ->select(["lvl0.[[{$primaryKey}]] AS lvl0"])
373 18
                ->from("{$tableName} lvl0")
374 21
                ->where(["lvl0.[[{$this->parentAttribute}]]" => $lastLevelIds]);
375 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...
376 18
                $query->orderBy(["lvl0.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
377 18
            }
378 18
            for ($i = 0; $i < $this->childrenJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
379 18
                $depthCur++;
380 18
                $levels++;
381 18
                $j = $i + 1;
382 18
                $query
383 18
                    ->addSelect(["lvl{$j}.[[{$primaryKey}]] as lvl{$j}"])
384 18
                    ->leftJoin("{$tableName} lvl{$j}", [
385 18
                        'and',
386 18
                        "lvl{$j}.[[{$this->parentAttribute}]] = lvl{$i}.[[{$primaryKey}]]",
387 18
                        ['is not', "lvl{$i}.[[{$primaryKey}]]", null],
388 18
                    ]);
389 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...
390 18
                    $query->addOrderBy(["lvl{$j}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
391 18
                }
392
            }
393 18
            if ($this->childrenJoinLevels) {
394 18
                $columns = [];
395 18
                foreach ($query->all($this->owner->getDb()) as $row) {
396 18
                    $level = 0;
397 18
                    foreach ($row as $id) {
398 18
                        if ($id !== null) {
399
                            $columns[$level][$id] = true;
400 21
                        }
401 21
                        $level++;
402 21
                    }
403 21
                }
404 21
                for ($i = 0; $i < $levels; $i++) {
405
                    if (isset($columns[$i])) {
406
                        $lastLevelIds = array_keys($columns[$i]);
407
                        $result[]     = $lastLevelIds;
408
                    } else {
409
                        $lastLevelIds = [];
410 81
                        break;
411
                    }
412 81
                }
413
            } else {
414
                $lastLevelIds = $query->column($this->owner->getDb());
415
                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...
416
                    $result[] = $lastLevelIds;
417
                }
418
            }
419 15
        }
420
        if ($cache && $depth === null) {
421 15
            $this->_childrenIds = $result;
422 15
        }
423
        return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
424
    }
425
426
    /**
427
     * Populate children relations for self and all descendants
428 3
     *
429
     * @param int $depth = null
430 3
     * @return static
431
     */
432
    public function populateTree($depth = null)
433
    {
434
        /** @var ActiveRecord[]|static[] $nodes */
435
        if ($depth === null) {
436 6
            $nodes = $this->owner->descendants;
437
        } else {
438 6
            $nodes = $this->getDescendants($depth)->all();
439 6
        }
440
441
        $relates = [];
442
        foreach ($nodes as $node) {
443
            $key = $node->getAttribute($this->parentAttribute);
444
            if (!isset($relates[$key])) {
445
                $relates[$key] = [];
446 36
            }
447
            $relates[$key][] = $node;
448 36
        }
449 36
450 36
        $nodes[$this->owner->getPrimaryKey()] = $this->owner;
451
        foreach ($nodes as $node) {
452
            $key = $node->getPrimaryKey();
453
            if (isset($relates[$key])) {
454
                $node->populateRelation('children', $relates[$key]);
455
            } elseif ($depth === null) {
456
                $node->populateRelation('children', []);
457 36
            }
458
        }
459 36
460 36
        return $this->owner;
461 36
    }
462
463
    /**
464
     * @return bool
465
     */
466
    public function isRoot()
467
    {
468 30
        return $this->owner->getAttribute($this->parentAttribute) === null;
469
    }
470 30
471 30
    /**
472 30
     * @param ActiveRecord $node
473
     * @return bool
474
     */
475
    public function isChildOf($node)
476
    {
477
        $ids = $this->getParentsIds();
478
        return in_array($node->getPrimaryKey(), $ids);
479 33
    }
480
481 33
    /**
482 33
     * @return bool
483 33
     */
484
    public function isLeaf()
485
    {
486
        return count($this->owner->children) === 0;
487
    }
488
489
    /**
490
     * @return ActiveRecord
491 12
     */
492
    public function makeRoot()
493 12
    {
494 12
        $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...
495
        return $this->owner;
496
    }
497
498
    /**
499
     * @param ActiveRecord $node
500
     * @return ActiveRecord
501
     */
502
    public function prependTo($node)
503
    {
504
        $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...
505
        $this->node = $node;
506
        return $this->owner;
507
    }
508
509 12
    /**
510
     * @param ActiveRecord $node
511 9
     * @return ActiveRecord
512
     */
513
    public function appendTo($node)
514
    {
515
        $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...
516
        $this->node = $node;
517
        return $this->owner;
518 147
    }
519
520 147
    /**
521 117
     * @param ActiveRecord $node
522 117
     * @return ActiveRecord
523 147
     */
524 147
    public function insertBefore($node)
525 6
    {
526 6
        $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...
527 6
        $this->node = $node;
528 6
        return $this->owner;
529 6
    }
530
531 141
    /**
532 36
     * @param ActiveRecord $node
533 24
     * @return ActiveRecord
534
     */
535 105
    public function insertAfter($node)
536 36
    {
537 24
        $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...
538
        $this->node = $node;
539 69
        return $this->owner;
540 30
    }
541 21
542
    /**
543 39
     * @return bool|int
544 33
     * @throws \Exception
545 21
     * @throws \yii\db\Exception
546
     */
547 6
    public function deleteWithChildren()
548 6
    {
549 3
        $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...
550
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
551 102
            $transaction = $this->owner->getDb()->beginTransaction();
552 99
            try {
553
                $result = $this->deleteWithChildrenInternal();
554
                if ($result === false) {
555
                    $transaction->rollBack();
556
                } else {
557 99
                    $transaction->commit();
558
                }
559 99
                return $result;
560 99
            } catch (\Exception $e) {
561 99
                $transaction->rollBack();
562
                throw $e;
563
            }
564
        } else {
565
            $result = $this->deleteWithChildrenInternal();
566
        }
567 21
        return $result;
568
    }
569 21
570 6
    /**
571
     * @param bool $middle
572 15
     * @return int
573 3
     */
574
    public function reorderChildren($middle = true)
575 12
    {
576 12
        /** @var ActiveRecord|SortableBehavior $item */
577
        $item = $this->owner->children[0];
578
        if ($item) {
579
            return $item->reorder($middle);
580
        } else {
581 12
            return 0;
582
        }
583 12
    }
584 3
585 3
    /**
586 3
     * @throws Exception
587 3
     * @throws NotSupportedException
588 3
     */
589 12
    public function beforeSave()
590 12
    {
591
        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...
592
            $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...
593
        }
594
        switch ($this->operation) {
595
            case self::OPERATION_MAKE_ROOT:
596 147
                $this->owner->setAttribute($this->parentAttribute, null);
597
                if ($this->sortable !== null) {
598 147
                    $this->owner->setAttribute($this->behavior->sortAttribute, 0);
599 147
                }
600
                break;
601
602 147
            case self::OPERATION_PREPEND_TO:
603
                $this->insertIntoInternal(false);
604
                break;
605
606
            case self::OPERATION_APPEND_TO:
607
                $this->insertIntoInternal(true);
608
                break;
609 135
610
            case self::OPERATION_INSERT_BEFORE:
611 135
                $this->insertNearInternal(false);
612 9
                break;
613
614 126
            case self::OPERATION_INSERT_AFTER:
615 12
                $this->insertNearInternal(true);
616
                break;
617
618 114
            default:
619 12
                if ($this->owner->getIsNewRecord()) {
620
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
621
                }
622 102
        }
623 12
    }
624
625 90
    /**
626
     *
627
     */
628
    public function afterSave()
629
    {
630
        $this->operation = null;
631 42
        $this->node      = null;
632
    }
633 42
634
    /**
635 42
     * @param \yii\base\ModelEvent $event
636 42
     * @throws Exception
637
     */
638 42
    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...
639
    {
640 42
        if ($this->owner->getIsNewRecord()) {
641 42
            throw new Exception('Can not delete a node when it is new record.');
642
        }
643 42
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
644 42
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
645 24
        }
646 24
        $this->owner->refresh();
647
    }
648 42
649 42
    /**
650 42
     *
651 42
     */
652 42
    public function afterDelete()
653 42
    {
654 42
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
655
            $this->owner->updateAll(
656 42
                [$this->parentAttribute => $this->owner->getAttribute($this->parentAttribute)],
657 42
                [$this->parentAttribute => $this->owner->getPrimaryKey()]
658 42
            );
659 42
        }
660 42
        $this->operation = null;
661 42
    }
662 42
663
    /**
664 42
     * @return mixed
665 42
     * @throws Exception
666
     */
667 42
    protected function getPrimaryKey()
668 42
    {
669 42
        $primaryKey = $this->owner->primaryKey();
670
        if (!isset($primaryKey[0])) {
671 42
            throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
672 42
        }
673
        return $primaryKey[0];
674
    }
675
676
    /**
677
     * @param bool $forInsertNear
678
     * @throws Exception
679 72
     */
680
    protected function checkNode($forInsertNear = false)
681 72
    {
682 48
        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...
683 48
            throw new Exception('Can not move a node before/after root.');
684 48
        }
685 48
        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...
686
            throw new Exception('Can not move a node when the target node is new record.');
687 48
        }
688 48
689 48
        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...
690
            throw new Exception('Can not move a node when the target node is same.');
691 48
        }
692 30
693 30
        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...
694 24
            throw new Exception('Can not move a node when the target node is child.');
695
        }
696 48
    }
697 48
698 48
    /**
699
     * Append to operation internal handler
700
     * @param bool $append
701
     * @throws Exception
702
     */
703
    protected function insertIntoInternal($append)
704
    {
705 63
        $this->checkNode(false);
706
        $this->owner->setAttribute($this->parentAttribute, $this->node->getPrimaryKey());
707 63
        if ($this->sortable !== false) {
708 42
            if ($append) {
709 42
                $this->owner->moveLast();
710 42
            } else {
711 42
                $this->owner->moveFirst();
712 42
            }
713
        }
714
    }
715
716
    /**
717 12
     * Insert operation internal handler
718
     * @param bool $forward
719 12
     * @throws Exception
720
     */
721
    protected function insertNearInternal($forward)
722 9
    {
723 9
        $this->checkNode(true);
724 9
        $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...
725 9
        if ($this->sortable !== null) {
726 9
            if ($forward) {
727 9
                $this->owner->moveAfter($this->node);
728
            } else {
729
                $this->owner->moveBefore($this->node);
730
            }
731
        }
732
    }
733
734
    /**
735
     * @return int
736
     */
737
    protected function deleteWithChildrenInternal()
738
    {
739
        if (!$this->owner->beforeDelete()) {
740
            return false;
741
        }
742
        $ids = $this->getDescendantsIds(null, true);
743
        $ids[] = $this->owner->primaryKey;
744
        $result = $this->owner->deleteAll([$this->getPrimaryKey() => $ids]);
745
        $this->owner->setOldAttributes(null);
746
        $this->owner->afterDelete();
747
        return $result;
748
    }
749
}