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 ( 6eda4a...7f94e2 )
by Pavel
35:10 queued 32:10
created

AdjacencyListBehavior::makeRoot()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

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