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
Pull Request — master (#2)
by
unknown
03:05
created

AdjacencyListBehavior::getDescendants()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2.024

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
ccs 9
cts 11
cp 0.8182
rs 9.4285
cc 2
eloc 10
nc 2
nop 2
crap 2.024
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 225
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterSave',
98 225
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
99 225
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterSave',
100 225
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
101 225
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
102 225
        ];
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(),
115 225
                    'query'         => [$this->parentAttribute],
116 225
                ],
117 225
                $this->sortable
118 225
            ));
119 225
            $owner->attachBehavior('adjacency-list-sortable', $this->behavior);
120 225
        }
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
     * @return ActiveRecord[]
140
     * @throws Exception
141
     */
142 3
    public function getParentsOrdered()
143
    {
144 3
        if ($this->_parentsOrdered !== null) {
145
            return $this->_parentsOrdered;
146
        }
147 3
        $parents = $this->getParents()->all();
148 3
        $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 3
            $aIdx = $ids[$a->$primaryKey];
152 3
            $bIdx = $ids[$b->$primaryKey];
153 3
            if ($aIdx == $bIdx) {
154
                return 0;
155
            } else {
156 3
                return $aIdx > $bIdx ? -1 : 1;
157
            }
158 3
        });
159 3
        return $this->_parentsOrdered = $parents;
160
    }
161
162
    /**
163
     * @return \yii\db\ActiveQuery
164
     * @throws Exception
165
     */
166 3
    public function getParent()
167
    {
168 3
        return $this->owner->hasOne($this->owner->className(), [$this->getPrimaryKey() => $this->parentAttribute]);
169
    }
170
171
    /**
172
     * @return \yii\db\ActiveQuery
173
     */
174 3
    public function getRoot()
175
    {
176 3
        $tableName = $this->owner->tableName();
177 3
        $id = $this->getParentsIds();
178 3
        $id = $id ? $id[count($id) - 1] : $this->owner->primaryKey;
179 3
        $query = $this->owner->find()
180 3
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $id]);
181 3
        $query->multiple = false;
182 3
        return $query;
183
    }
184
185
    /**
186
     * @param int|null $depth
187
     * @param bool $andSelf
188
     * @return \yii\db\ActiveQuery
189
     */
190 12
    public function getDescendants($depth = null, $andSelf = false)
191
    {
192 12
        $tableName = $this->owner->tableName();
193 12
        $ids = $this->getDescendantsIds($depth, true);
194 12
        if ($andSelf) {
195
            $ids[] = $this->owner->getPrimaryKey();
196
        }
197 12
        $query = $this->owner->find()
198 12
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $ids])
199 12
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
200 12
        $query->multiple = true;
201 12
        return $query;
202
    }
203
204
    /**
205
     * @return ActiveRecord[]
206
     * @throws Exception
207
     */
208 3
    public function getDescendantsOrdered()
209
    {
210 3
        $descendants = $this->owner->descendants;
211 3
        $ids = array_flip($this->getDescendantsIds(null, true));
212 3
        $primaryKey = $this->getPrimaryKey();
213 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...
214 3
            $aIdx = $ids[$a->$primaryKey];
215 3
            $bIdx = $ids[$b->$primaryKey];
216 3
            if ($aIdx == $bIdx) {
217
                return 0;
218
            } else {
219 3
                return $aIdx > $bIdx ? -1 : 1;
220
            }
221 3
        });
222 3
        return $descendants;
223
    }
224
225
    /**
226
     * @return \yii\db\ActiveQuery
227
     */
228 12
    public function getChildren()
229
    {
230 12
        $result = $this->owner->hasMany($this->owner->className(), [$this->parentAttribute => $this->getPrimaryKey()]);
231 12
        if ($this->sortable !== false) {
232 12
            $result->orderBy([$this->behavior->sortAttribute => SORT_ASC]);
233 12
        }
234 12
        return $result;
235
    }
236
237
    /**
238
     * @param int|null $depth
239
     * @return \yii\db\ActiveQuery
240
     */
241 3
    public function getLeaves($depth = null)
242
    {
243 3
        $query = $this->getDescendants($depth)
244 3
            ->joinWith(['children' => function ($query) {
245
                /** @var \yii\db\ActiveQuery $query */
246 3
                $modelClass = $query->modelClass;
247
                $query
248 3
                    ->from($modelClass::tableName() . ' children')
249 3
                    ->orderBy(null);
250 3
            }])
251 3
            ->andWhere(["children.[[{$this->parentAttribute}]]" => null]);
252 3
        $query->multiple = true;
253 3
        return $query;
254
    }
255
256
    /**
257
     * @return \yii\db\ActiveQuery
258
     * @throws NotSupportedException
259
     */
260 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...
261
    {
262 3
        if ($this->sortable === false) {
263
            throw new NotSupportedException('prev() not allow if not set sortable');
264
        }
265 3
        $tableName = $this->owner->tableName();
266 3
        $query = $this->owner->find()
267 3
            ->andWhere([
268 3
                'and',
269 3
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
270 3
                ['<', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
271 3
            ])
272 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_DESC])
273 3
            ->limit(1);
274 3
        $query->multiple = false;
275 3
        return $query;
276
    }
277
278
    /**
279
     * @return \yii\db\ActiveQuery
280
     * @throws NotSupportedException
281
     */
282 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...
283
    {
284 3
        if ($this->sortable === false) {
285
            throw new NotSupportedException('next() not allow if not set sortable');
286
        }
287 3
        $tableName = $this->owner->tableName();
288 3
        $query = $this->owner->find()
289 3
            ->andWhere([
290 3
                'and',
291 3
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
292 3
                ['>', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
293 3
            ])
294 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC])
295 3
            ->limit(1);
296 3
        $query->multiple = false;
297 3
        return $query;
298
    }
299
300
    /**
301
     * @param int|null $depth
302
     * @param bool $cache
303
     * @return array
304
     */
305 27
    public function getParentsIds($depth = null, $cache = true)
306
    {
307 27
        if ($cache && $this->_parentsIds !== null) {
308 3
            return $depth === null ? $this->_parentsIds : array_slice($this->_parentsIds, 0, $depth);
309
        }
310
311 27
        $parentId = $this->owner->getAttribute($this->parentAttribute);
312 27
        if ($parentId === null) {
313 9
            if ($cache) {
314 9
                $this->_parentsIds = [];
315 9
            }
316 9
            return [];
317
        }
318 27
        $result     = [(string)$parentId];
319 27
        $tableName  = $this->owner->tableName();
320 27
        $primaryKey = $this->getPrimaryKey();
321 27
        $depthCur   = 1;
322 27
        while ($parentId !== null && ($depth === null || $depthCur < $depth)) {
323 27
            $query = (new Query())
324 27
                ->select(["lvl0.[[{$this->parentAttribute}]] AS lvl0"])
325 27
                ->from("{$tableName} lvl0")
326 27
                ->where(["lvl0.[[{$primaryKey}]]" => $parentId]);
327 27
            for ($i = 0; $i < $this->parentsJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
328 15
                $j = $i + 1;
329
                $query
330 15
                    ->addSelect(["lvl{$j}.[[{$this->parentAttribute}]] as lvl{$j}"])
331 15
                    ->leftJoin("{$tableName} lvl{$j}", "lvl{$j}.[[{$primaryKey}]] = lvl{$i}.[[{$this->parentAttribute}]]");
332 15
            }
333 27
            if ($parentIds = $query->one($this->owner->getDb())) {
334 27
                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...
335 27
                    $depthCur++;
336 27
                    if ($parentId === null) {
337 27
                        break;
338
                    }
339 24
                    $result[] = $parentId;
340 27
                }
341 27
            } else {
342
                $parentId = null;
343
            }
344 27
        }
345 27
        if ($cache && $depth === null) {
346 24
            $this->_parentsIds = $result;
347 24
        }
348 27
        return $result;
349
    }
350
351
    /**
352
     * @param int|null $depth
353
     * @param bool $flat
354
     * @param bool $cache
355
     * @return array
356
     */
357 24
    public function getDescendantsIds($depth = null, $flat = false, $cache = true)
358
    {
359 24
        if ($cache && $this->_childrenIds !== null) {
360 3
            $result = $depth === null ? $this->_childrenIds : array_slice($this->_childrenIds, 0, $depth);
361 3
            return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
362
        }
363
364 24
        $result       = [];
365 24
        $tableName    = $this->owner->tableName();
366 24
        $primaryKey   = $this->getPrimaryKey();
367 24
        $depthCur     = 0;
368 24
        $lastLevelIds = [$this->owner->primaryKey];
369 24
        while (!empty($lastLevelIds) && ($depth === null || $depthCur < $depth)) {
370 24
            $levels = 1;
371 24
            $depthCur++;
372 24
            $query = (new Query())
373 24
                ->select(["lvl0.[[{$primaryKey}]] AS lvl0"])
374 24
                ->from("{$tableName} lvl0")
375 24
                ->where(["lvl0.[[{$this->parentAttribute}]]" => $lastLevelIds]);
376 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...
377 24
                $query->orderBy(["lvl0.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
378 24
            }
379 24
            for ($i = 0; $i < $this->childrenJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
380 15
                $depthCur++;
381 15
                $levels++;
382 15
                $j = $i + 1;
383
                $query
384 15
                    ->addSelect(["lvl{$j}.[[{$primaryKey}]] as lvl{$j}"])
385 15
                    ->leftJoin("{$tableName} lvl{$j}", [
386 15
                        'and',
387 15
                        "lvl{$j}.[[{$this->parentAttribute}]] = lvl{$i}.[[{$primaryKey}]]",
388 15
                        ['is not', "lvl{$i}.[[{$primaryKey}]]", null],
389 15
                    ]);
390 15 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...
391 15
                    $query->addOrderBy(["lvl{$j}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
392 15
                }
393 15
            }
394 24
            if ($this->childrenJoinLevels) {
395 18
                $columns = [];
396 18
                foreach ($query->all($this->owner->getDb()) as $row) {
397 18
                    $level = 0;
398 18
                    foreach ($row as $id) {
399 18
                        if ($id !== null) {
400 18
                            $columns[$level][$id] = true;
401 18
                        }
402 18
                        $level++;
403 18
                    }
404 18
                }
405 18
                for ($i = 0; $i < $levels; $i++) {
406 18
                    if (isset($columns[$i])) {
407 18
                        $lastLevelIds = array_keys($columns[$i]);
408 18
                        $result[]     = $lastLevelIds;
409 18
                    } else {
410 15
                        $lastLevelIds = [];
411 15
                        break;
412
                    }
413 18
                }
414 18
            } else {
415 21
                $lastLevelIds = $query->column($this->owner->getDb());
416 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...
417 21
                    $result[] = $lastLevelIds;
418 21
                }
419
            }
420 24
        }
421 24
        if ($cache && $depth === null) {
422 24
            $this->_childrenIds = $result;
423 24
        }
424 24
        return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
425
    }
426
427
    /**
428
     * Populate children relations for self and all descendants
429
     *
430
     * @param int $depth = null
431
     * @return static
432
     */
433 3
    public function populateTree($depth = null)
434
    {
435
        /** @var ActiveRecord[]|static[] $nodes */
436 3
        if ($depth === null) {
437 3
            $nodes = $this->owner->descendants;
438 3
        } else {
439 3
            $nodes = $this->getDescendants($depth)->all();
440
        }
441
442 3
        $relates = [];
443 3
        foreach ($nodes as $node) {
444 3
            $key = $node->getAttribute($this->parentAttribute);
445 3
            if (!isset($relates[$key])) {
446 3
                $relates[$key] = [];
447 3
            }
448 3
            $relates[$key][] = $node;
449 3
        }
450
451 3
        $nodes[] = $this->owner;
452 3
        foreach ($nodes as $node) {
453 3
            $key = $node->getPrimaryKey();
454 3
            if (isset($relates[$key])) {
455 3
                $node->populateRelation('children', $relates[$key]);
456 3
            } elseif ($depth === null) {
457 3
                $node->populateRelation('children', []);
458 3
            }
459 3
        }
460
461 3
        return $this->owner;
462
    }
463
464
    /**
465
     * @return bool
466
     */
467 81
    public function isRoot()
468
    {
469 81
        return $this->owner->getAttribute($this->parentAttribute) === null;
470
    }
471
472
    /**
473
     * @param ActiveRecord $node
474
     * @return bool
475
     */
476 15
    public function isChildOf($node)
477
    {
478 15
        $ids = $this->getParentsIds();
479 15
        return in_array($node->getPrimaryKey(), $ids);
480
    }
481
482
    /**
483
     * @return bool
484
     */
485 3
    public function isLeaf()
486
    {
487 3
        return count($this->owner->children) === 0;
488
    }
489
490
    /**
491
     * @return ActiveRecord
492
     */
493 6
    public function makeRoot()
494
    {
495 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...
496 6
        return $this->owner;
497
    }
498
499
    /**
500
     * @param ActiveRecord $node
501
     * @return ActiveRecord
502
     */
503 36
    public function prependTo($node)
504
    {
505 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...
506 36
        $this->node = $node;
507 36
        return $this->owner;
508
    }
509
510
    /**
511
     * @param ActiveRecord $node
512
     * @return ActiveRecord
513
     */
514 36
    public function appendTo($node)
515
    {
516 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...
517 36
        $this->node = $node;
518 36
        return $this->owner;
519
    }
520
521
    /**
522
     * @param ActiveRecord $node
523
     * @return ActiveRecord
524
     */
525 30
    public function insertBefore($node)
526
    {
527 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...
528 30
        $this->node = $node;
529 30
        return $this->owner;
530
    }
531
532
    /**
533
     * @param ActiveRecord $node
534
     * @return ActiveRecord
535
     */
536 33
    public function insertAfter($node)
537
    {
538 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...
539 33
        $this->node = $node;
540 33
        return $this->owner;
541
    }
542
543
    /**
544
     * @return bool|int
545
     * @throws \Exception
546
     * @throws \yii\db\Exception
547
     */
548 12
    public function deleteWithChildren()
549
    {
550 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...
551 12
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
552
            $transaction = $this->owner->getDb()->beginTransaction();
553
            try {
554
                $result = $this->deleteWithChildrenInternal();
555
                if ($result === false) {
556
                    $transaction->rollBack();
557
                } else {
558
                    $transaction->commit();
559
                }
560
                return $result;
561
            } catch (\Exception $e) {
562
                $transaction->rollBack();
563
                throw $e;
564
            }
565
        } else {
566 12
            $result = $this->deleteWithChildrenInternal();
567
        }
568 9
        return $result;
569
    }
570
571
    /**
572
     * @param bool $middle
573
     * @return int
574
     */
575 3
    public function reorderChildren($middle = true)
576
    {
577
        /** @var ActiveRecord|SortableBehavior $item */
578 3
        $item = $this->owner->children[0];
579 3
        if ($item) {
580 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...
581
        } else {
582
            return 0;
583
        }
584
    }
585
586
    /**
587
     * @throws Exception
588
     * @throws NotSupportedException
589
     */
590 147
    public function beforeSave()
591
    {
592 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...
593 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...
594 117
        }
595 147
        switch ($this->operation) {
596 147
            case self::OPERATION_MAKE_ROOT:
597 6
                $this->owner->setAttribute($this->parentAttribute, null);
598 6
                if ($this->sortable !== false) {
599 6
                    $this->owner->setAttribute($this->behavior->sortAttribute, 0);
600 6
                }
601 6
                break;
602
603 141
            case self::OPERATION_PREPEND_TO:
604 36
                $this->insertIntoInternal(false);
605 24
                break;
606
607 105
            case self::OPERATION_APPEND_TO:
608 36
                $this->insertIntoInternal(true);
609 24
                break;
610
611 69
            case self::OPERATION_INSERT_BEFORE:
612 30
                $this->insertNearInternal(false);
613 21
                break;
614
615 39
            case self::OPERATION_INSERT_AFTER:
616 33
                $this->insertNearInternal(true);
617 21
                break;
618
619 6
            default:
620 6
                if ($this->owner->getIsNewRecord()) {
621 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
622
                }
623 102
        }
624 99
    }
625
626
    /**
627
     *
628
     */
629 99
    public function afterSave()
630
    {
631 99
        $this->operation = null;
632 99
        $this->node      = null;
633 99
    }
634
635
    /**
636
     * @param \yii\base\ModelEvent $event
637
     * @throws Exception
638
     */
639 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...
640
    {
641 21
        if ($this->owner->getIsNewRecord()) {
642 6
            throw new Exception('Can not delete a node when it is new record.');
643
        }
644 15
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
645 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
646
        }
647 12
        $this->owner->refresh();
648 12
    }
649
650
    /**
651
     *
652
     */
653 12
    public function afterDelete()
654
    {
655 12
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
656 3
            $this->owner->updateAll(
657 3
                [$this->parentAttribute => $this->owner->getAttribute($this->parentAttribute)],
658 3
                [$this->parentAttribute => $this->owner->getPrimaryKey()]
659 3
            );
660 3
        }
661 12
        $this->operation = null;
662 12
    }
663
664
    /**
665
     * @return mixed
666
     * @throws Exception
667
     */
668 63
    protected function getPrimaryKey()
669
    {
670 63
        $primaryKey = $this->owner->primaryKey();
671 63
        if (!isset($primaryKey[0])) {
672
            throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
673
        }
674 63
        return $primaryKey[0];
675
    }
676
677
    /**
678
     * @param bool $forInsertNear
679
     * @throws Exception
680
     */
681 135
    protected function checkNode($forInsertNear = false)
682
    {
683 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...
684 9
            throw new Exception('Can not move a node before/after root.');
685
        }
686 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...
687 12
            throw new Exception('Can not move a node when the target node is new record.');
688
        }
689
690 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...
691 12
            throw new Exception('Can not move a node when the target node is same.');
692
        }
693
694 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...
695 12
            throw new Exception('Can not move a node when the target node is child.');
696
        }
697 90
    }
698
699
    /**
700
     * Append to operation internal handler
701
     * @param bool $append
702
     * @throws Exception
703
     */
704 72
    protected function insertIntoInternal($append)
705
    {
706 72
        $this->checkNode(false);
707 48
        $this->owner->setAttribute($this->parentAttribute, $this->node->getPrimaryKey());
708 48
        if ($this->sortable !== false) {
709 48
            if ($append) {
710 24
                $this->owner->moveLast();
711 24
            } else {
712 24
                $this->owner->moveFirst();
713
            }
714 48
        }
715 48
    }
716
717
    /**
718
     * Insert operation internal handler
719
     * @param bool $forward
720
     * @throws Exception
721
     */
722 63
    protected function insertNearInternal($forward)
723
    {
724 63
        $this->checkNode(true);
725 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...
726 42
        if ($this->sortable !== false) {
727 42
            if ($forward) {
728 21
                $this->owner->moveAfter($this->node);
729 21
            } else {
730 21
                $this->owner->moveBefore($this->node);
731
            }
732 42
        }
733 42
    }
734
735
    /**
736
     * @return int
737
     */
738 12
    protected function deleteWithChildrenInternal()
739
    {
740 12
        if (!$this->owner->beforeDelete()) {
741
            return false;
742
        }
743 9
        $ids = $this->getDescendantsIds(null, true);
744 9
        $ids[] = $this->owner->primaryKey;
745 9
        $result = $this->owner->deleteAll([$this->getPrimaryKey() => $ids]);
746 9
        $this->owner->setOldAttributes(null);
747 9
        $this->owner->afterDelete();
748 9
        return $result;
749
    }
750
}
751