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 ( 8263c8...65da6a )
by Pavel
06:25
created

AdjacencyListBehavior::insertIntoInternal()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 8
nc 3
nop 1
crap 3
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 6
    public function getDescendantsOrdered()
208
    {
209 6
        $descendants = $this->owner->descendants;
210 6
        $ids = array_flip($this->getDescendantsIds(null, true));
211 6
        $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 6
            $aIdx = $ids[$a->$primaryKey];
214 6
            $bIdx = $ids[$b->$primaryKey];
215 6
            if ($aIdx == $bIdx) {
216
                return 0;
217
            } else {
218 6
                return $aIdx > $bIdx ? 1 : -1;
219
            }
220 6
        });
221 6
        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) {
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 6
            $result = $depth === null ? $this->_childrenIds : array_slice($this->_childrenIds, 0, $depth);
360 6
            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->descendantsOrdered;
437 3
        } else {
438 3
            $nodes = $this->getDescendants($depth)
439 3
                ->orderBy($this->sortable !== false ? [$this->behavior->sortAttribute => SORT_ASC] : null)
0 ignored issues
show
Bug introduced by
It seems like $this->sortable !== fals...ute => SORT_ASC) : null can also be of type null; however, yii\db\QueryTrait::orderBy() does only seem to accept string|array|object<yii\db\Expression>, 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...
440 3
                ->all();
441
        }
442
443 3
        $relates = [];
444 3
        foreach ($nodes as $node) {
445 3
            $key = $node->getAttribute($this->parentAttribute);
446 3
            if (!isset($relates[$key])) {
447 3
                $relates[$key] = [];
448 3
            }
449 3
            $relates[$key][] = $node;
450 3
        }
451
452 3
        $nodes[] = $this->owner;
453 3
        foreach ($nodes as $node) {
454 3
            $key = $node->getPrimaryKey();
455 3
            if (isset($relates[$key])) {
456 3
                $node->populateRelation('children', $relates[$key]);
457 3
            } elseif ($depth === null) {
458 3
                $node->populateRelation('children', []);
459 3
            }
460 3
        }
461
462 3
        return $this->owner;
463
    }
464
465
    /**
466
     * @return bool
467
     */
468 81
    public function isRoot()
469
    {
470 81
        return $this->owner->getAttribute($this->parentAttribute) === null;
471
    }
472
473
    /**
474
     * @param ActiveRecord $node
475
     * @return bool
476
     */
477 15
    public function isChildOf($node)
478
    {
479 15
        $ids = $this->getParentsIds();
480 15
        return in_array($node->getPrimaryKey(), $ids);
481
    }
482
483
    /**
484
     * @return bool
485
     */
486 3
    public function isLeaf()
487
    {
488 3
        return count($this->owner->children) === 0;
489
    }
490
491
    /**
492
     * @return ActiveRecord
493
     */
494 6
    public function makeRoot()
495
    {
496 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...
497 6
        return $this->owner;
498
    }
499
500
    /**
501
     * @param ActiveRecord $node
502
     * @return ActiveRecord
503
     */
504 36
    public function prependTo($node)
505
    {
506 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...
507 36
        $this->node = $node;
508 36
        return $this->owner;
509
    }
510
511
    /**
512
     * @param ActiveRecord $node
513
     * @return ActiveRecord
514
     */
515 36
    public function appendTo($node)
516
    {
517 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...
518 36
        $this->node = $node;
519 36
        return $this->owner;
520
    }
521
522
    /**
523
     * @param ActiveRecord $node
524
     * @return ActiveRecord
525
     */
526 30
    public function insertBefore($node)
527
    {
528 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...
529 30
        $this->node = $node;
530 30
        return $this->owner;
531
    }
532
533
    /**
534
     * @param ActiveRecord $node
535
     * @return ActiveRecord
536
     */
537 33
    public function insertAfter($node)
538
    {
539 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...
540 33
        $this->node = $node;
541 33
        return $this->owner;
542
    }
543
544
    /**
545
     * Need for paulzi/auto-tree
546
     */
547
    public function preDeleteWithChildren()
548
    {
549
        $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
    }
551
552
    /**
553
     * @return bool|int
554
     * @throws \Exception
555
     * @throws \yii\db\Exception
556
     */
557 12
    public function deleteWithChildren()
558
    {
559 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...
560 12
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
561
            $transaction = $this->owner->getDb()->beginTransaction();
562
            try {
563
                $result = $this->deleteWithChildrenInternal();
564
                if ($result === false) {
565
                    $transaction->rollBack();
566
                } else {
567
                    $transaction->commit();
568
                }
569
                return $result;
570
            } catch (\Exception $e) {
571
                $transaction->rollBack();
572
                throw $e;
573
            }
574
        } else {
575 12
            $result = $this->deleteWithChildrenInternal();
576
        }
577 9
        return $result;
578
    }
579
580
    /**
581
     * @param bool $middle
582
     * @return int
583
     */
584 3
    public function reorderChildren($middle = true)
585
    {
586
        /** @var ActiveRecord|SortableBehavior $item */
587 3
        $item = $this->owner->children[0];
588 3
        if ($item) {
589 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...
590
        } else {
591
            return 0;
592
        }
593
    }
594
595
    /**
596
     * @throws Exception
597
     * @throws NotSupportedException
598
     */
599 147
    public function beforeSave()
600
    {
601 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...
602 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...
603 117
        }
604 147
        switch ($this->operation) {
605 147
            case self::OPERATION_MAKE_ROOT:
606 6
                $this->owner->setAttribute($this->parentAttribute, null);
607 6
                if ($this->sortable !== false) {
608 6
                    $this->owner->setAttribute($this->behavior->sortAttribute, 0);
609 6
                }
610 6
                break;
611
612 141
            case self::OPERATION_PREPEND_TO:
613 36
                $this->insertIntoInternal(false);
614 24
                break;
615
616 105
            case self::OPERATION_APPEND_TO:
617 36
                $this->insertIntoInternal(true);
618 24
                break;
619
620 69
            case self::OPERATION_INSERT_BEFORE:
621 30
                $this->insertNearInternal(false);
622 21
                break;
623
624 39
            case self::OPERATION_INSERT_AFTER:
625 33
                $this->insertNearInternal(true);
626 21
                break;
627
628 6
            default:
629 120
                if ($this->owner->getIsNewRecord()) {
630 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
631
                }
632 102
        }
633 99
    }
634
635
    /**
636
     *
637
     */
638 99
    public function afterSave()
639
    {
640 99
        $this->operation = null;
641 99
        $this->node      = null;
642 99
    }
643
644
    /**
645
     * @param \yii\base\ModelEvent $event
646
     * @throws Exception
647
     */
648 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...
649
    {
650 21
        if ($this->owner->getIsNewRecord()) {
651 6
            throw new Exception('Can not delete a node when it is new record.');
652
        }
653 15
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
654 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
655
        }
656 12
        $this->owner->refresh();
657 12
    }
658
659
    /**
660
     *
661
     */
662 12
    public function afterDelete()
663
    {
664 12
        if ($this->operation !== static::OPERATION_DELETE_ALL) {
665 3
            $this->owner->updateAll(
666 3
                [$this->parentAttribute => $this->owner->getAttribute($this->parentAttribute)],
667 3
                [$this->parentAttribute => $this->owner->getPrimaryKey()]
668 3
            );
669 3
        }
670 12
        $this->operation = null;
671 12
    }
672
673
    /**
674
     * @return mixed
675
     * @throws Exception
676
     */
677 63
    protected function getPrimaryKey()
678
    {
679 63
        $primaryKey = $this->owner->primaryKey();
680 63
        if (!isset($primaryKey[0])) {
681
            throw new Exception('"' . $this->owner->className() . '" must have a primary key.');
682
        }
683 63
        return $primaryKey[0];
684
    }
685
686
    /**
687
     * @param bool $forInsertNear
688
     * @throws Exception
689
     */
690 135
    protected function checkNode($forInsertNear = false)
691
    {
692 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...
693 9
            throw new Exception('Can not move a node before/after root.');
694
        }
695 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...
696 12
            throw new Exception('Can not move a node when the target node is new record.');
697
        }
698
699 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...
700 12
            throw new Exception('Can not move a node when the target node is same.');
701
        }
702
703 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...
704 12
            throw new Exception('Can not move a node when the target node is child.');
705
        }
706 90
    }
707
708
    /**
709
     * Append to operation internal handler
710
     * @param bool $append
711
     * @throws Exception
712
     */
713 72
    protected function insertIntoInternal($append)
714
    {
715 72
        $this->checkNode(false);
716 48
        $this->owner->setAttribute($this->parentAttribute, $this->node->getPrimaryKey());
717 48
        if ($this->sortable !== false) {
718 48
            if ($append) {
719 24
                $this->owner->moveLast();
720 24
            } else {
721 24
                $this->owner->moveFirst();
722
            }
723 48
        }
724 48
    }
725
726
    /**
727
     * Insert operation internal handler
728
     * @param bool $forward
729
     * @throws Exception
730
     */
731 63
    protected function insertNearInternal($forward)
732
    {
733 63
        $this->checkNode(true);
734 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...
735 42
        if ($this->sortable !== false) {
736 42
            if ($forward) {
737 21
                $this->owner->moveAfter($this->node);
738 21
            } else {
739 21
                $this->owner->moveBefore($this->node);
740
            }
741 42
        }
742 42
    }
743
744
    /**
745
     * @return int
746
     */
747 12
    protected function deleteWithChildrenInternal()
748
    {
749 12
        if (!$this->owner->beforeDelete()) {
750
            return false;
751
        }
752 9
        $ids = $this->getDescendantsIds(null, true);
753 9
        $ids[] = $this->owner->primaryKey;
754 9
        $result = $this->owner->deleteAll([$this->getPrimaryKey() => $ids]);
755 9
        $this->owner->setOldAttributes(null);
756 9
        $this->owner->afterDelete();
757 9
        return $result;
758
    }
759
}