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 ( 968e85...055e70 )
by Pavel
02:37
created

AdjacencyListBehavior::insertNearInternal()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
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
            ActiveRecord::EVENT_AFTER_INSERT    => 'afterSave',
98
            ActiveRecord::EVENT_BEFORE_UPDATE   => 'beforeSave',
99
            ActiveRecord::EVENT_AFTER_UPDATE    => 'afterSave',
100
            ActiveRecord::EVENT_BEFORE_DELETE   => 'beforeDelete',
101
            ActiveRecord::EVENT_AFTER_DELETE    => 'afterDelete',
102
        ];
103
    }
104
105
    /**
106
     * @param ActiveRecord $owner
107
     */
108 225
    public function attach($owner)
109
    {
110 225
        parent::attach($owner);
111 225
        if ($this->sortable !== false) {
112 225
            $this->behavior = Yii::createObject(array_merge(
113
                [
114 225
                    'class'         => SortableBehavior::className(),
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

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

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

Loading history...
115 225
                    'query'         => [$this->parentAttribute],
116
                ],
117 225
                $this->sortable
118
            ));
119 225
            $owner->attachBehavior('adjacency-list-sortable', $this->behavior);
120
        }
121 225
    }
122
123
    /**
124
     * @param int|null $depth
125
     * @return \yii\db\ActiveQuery
126
     * @throws Exception
127
     */
128 6
    public function getParents($depth = null)
129
    {
130 6
        $tableName = $this->owner->tableName();
131 6
        $ids = $this->getParentsIds($depth);
132 6
        $query = $this->owner->find()
133 6
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $ids]);
134 6
        $query->multiple = true;
135 6
        return $query;
136
    }
137
138
    /**
139
     * @param int|null $depth
140
     * @return ActiveRecord[]
141
     * @throws Exception
142
     */
143 3
    public function getParentsOrdered($depth = null)
144
    {
145 3
        if ($depth === null && $this->_parentsOrdered !== null) {
146
            return $this->_parentsOrdered;
147
        }
148 3
        $parents = $this->getParents($depth)->all();
149 3
        $ids = array_flip($this->getParentsIds());
150 3
        $primaryKey = $this->getPrimaryKey();
151 View Code Duplication
        usort($parents, function($a, $b) use ($ids, $primaryKey) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152 3
            $aIdx = $ids[$a->$primaryKey];
153 3
            $bIdx = $ids[$b->$primaryKey];
154 3
            if ($aIdx == $bIdx) {
155
                return 0;
156
            } else {
157 3
                return $aIdx > $bIdx ? -1 : 1;
158
            }
159 3
        });
160 3
        if ($depth !== null) {
161 3
            $this->_parentsOrdered = $parents;
162
        }
163 3
        return $parents;
164
    }
165
166
    /**
167
     * @return \yii\db\ActiveQuery
168
     * @throws Exception
169
     */
170 3
    public function getParent()
171
    {
172 3
        return $this->owner->hasOne($this->owner->className(), [$this->getPrimaryKey() => $this->parentAttribute]);
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

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

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

Loading history...
173
    }
174
175
    /**
176
     * @return \yii\db\ActiveQuery
177
     */
178 3
    public function getRoot()
179
    {
180 3
        $tableName = $this->owner->tableName();
181 3
        $id = $this->getParentsIds();
182 3
        $id = $id ? $id[count($id) - 1] : $this->owner->primaryKey;
183 3
        $query = $this->owner->find()
184 3
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $id]);
185 3
        $query->multiple = false;
186 3
        return $query;
187
    }
188
189
    /**
190
     * @param int|null $depth
191
     * @param bool $andSelf
192
     * @return \yii\db\ActiveQuery
193
     */
194 12
    public function getDescendants($depth = null, $andSelf = false)
195
    {
196 12
        $tableName = $this->owner->tableName();
197 12
        $ids = $this->getDescendantsIds($depth, true);
198 12
        if ($andSelf) {
199 3
            $ids[] = $this->owner->getPrimaryKey();
200
        }
201 12
        $query = $this->owner->find()
202 12
            ->andWhere(["{$tableName}.[[" . $this->getPrimaryKey() . "]]" => $ids]);
203 12
        $query->multiple = true;
204 12
        return $query;
205
    }
206
207
    /**
208
     * @param int|null $depth
209
     * @return ActiveRecord[]
210
     * @throws Exception
211
     */
212 6
    public function getDescendantsOrdered($depth = null)
213
    {
214 6
        if ($depth === null) {
215 6
            $descendants = $this->owner->descendants;
216
        } else {
217 3
            $descendants = $this->getDescendants($depth)->all();
218
        }
219 6
        $ids = array_flip($this->getDescendantsIds($depth, true));
220 6
        $primaryKey = $this->getPrimaryKey();
221 View Code Duplication
        usort($descendants, function($a, $b) use ($ids, $primaryKey) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222 6
            $aIdx = $ids[$a->$primaryKey];
223 6
            $bIdx = $ids[$b->$primaryKey];
224 6
            if ($aIdx == $bIdx) {
225
                return 0;
226
            } else {
227 6
                return $aIdx > $bIdx ? 1 : -1;
228
            }
229 6
        });
230 6
        return $descendants;
231
    }
232
233
    /**
234
     * @return \yii\db\ActiveQuery
235
     */
236 12
    public function getChildren()
237
    {
238 12
        $result = $this->owner->hasMany($this->owner->className(), [$this->parentAttribute => $this->getPrimaryKey()]);
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

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

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

Loading history...
239 12
        if ($this->sortable !== false) {
240 12
            $result->orderBy([$this->behavior->sortAttribute => SORT_ASC]);
241
        }
242 12
        return $result;
243
    }
244
245
    /**
246
     * @param int|null $depth
247
     * @return \yii\db\ActiveQuery
248
     */
249 3
    public function getLeaves($depth = null)
250
    {
251 3
        $query = $this->getDescendants($depth)
252 3
            ->joinWith(['children' => function ($query) {
253
                /** @var \yii\db\ActiveQuery $query */
254 3
                $modelClass = $query->modelClass;
255
                $query
256 3
                    ->from($modelClass::tableName() . ' children')
257 3
                    ->orderBy(null);
258 3
            }])
259 3
            ->andWhere(["children.[[{$this->parentAttribute}]]" => null]);
260 3
        $query->multiple = true;
261 3
        return $query;
262
    }
263
264
    /**
265
     * @return \yii\db\ActiveQuery
266
     * @throws NotSupportedException
267
     */
268 3 View Code Duplication
    public function getPrev()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
269
    {
270 3
        if ($this->sortable === false) {
271
            throw new NotSupportedException('prev() not allow if not set sortable');
272
        }
273 3
        $tableName = $this->owner->tableName();
274 3
        $query = $this->owner->find()
275 3
            ->andWhere([
276 3
                'and',
277 3
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
278 3
                ['<', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
279
            ])
280 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_DESC])
281 3
            ->limit(1);
282 3
        $query->multiple = false;
283 3
        return $query;
284
    }
285
286
    /**
287
     * @return \yii\db\ActiveQuery
288
     * @throws NotSupportedException
289
     */
290 3 View Code Duplication
    public function getNext()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
291
    {
292 3
        if ($this->sortable === false) {
293
            throw new NotSupportedException('next() not allow if not set sortable');
294
        }
295 3
        $tableName = $this->owner->tableName();
296 3
        $query = $this->owner->find()
297 3
            ->andWhere([
298 3
                'and',
299 3
                ["{$tableName}.[[{$this->parentAttribute}]]" => $this->owner->getAttribute($this->parentAttribute)],
300 3
                ['>', "{$tableName}.[[{$this->behavior->sortAttribute}]]", $this->owner->getSortablePosition()],
301
            ])
302 3
            ->orderBy(["{$tableName}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC])
303 3
            ->limit(1);
304 3
        $query->multiple = false;
305 3
        return $query;
306
    }
307
308
    /**
309
     * @param int|null $depth
310
     * @param bool $cache
311
     * @return array
312
     */
313 27
    public function getParentsIds($depth = null, $cache = true)
314
    {
315 27
        if ($cache && $this->_parentsIds !== null) {
316 3
            return $depth === null ? $this->_parentsIds : array_slice($this->_parentsIds, 0, $depth);
317
        }
318
319 27
        $parentId = $this->owner->getAttribute($this->parentAttribute);
320 27
        if ($parentId === null) {
321 9
            if ($cache) {
322 9
                $this->_parentsIds = [];
323
            }
324 9
            return [];
325
        }
326 27
        $result     = [(string)$parentId];
327 27
        $tableName  = $this->owner->tableName();
328 27
        $primaryKey = $this->getPrimaryKey();
329 27
        $depthCur   = 1;
330 27
        while ($parentId !== null && ($depth === null || $depthCur < $depth)) {
331 27
            $query = (new Query())
332 27
                ->select(["lvl0.[[{$this->parentAttribute}]] AS lvl0"])
333 27
                ->from("{$tableName} lvl0")
334 27
                ->where(["lvl0.[[{$primaryKey}]]" => $parentId]);
335 27
            for ($i = 0; $i < $this->parentsJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
336 15
                $j = $i + 1;
337
                $query
338 15
                    ->addSelect(["lvl{$j}.[[{$this->parentAttribute}]] as lvl{$j}"])
339 15
                    ->leftJoin("{$tableName} lvl{$j}", "lvl{$j}.[[{$primaryKey}]] = lvl{$i}.[[{$this->parentAttribute}]]");
340
            }
341 27
            if ($parentIds = $query->one($this->owner->getDb())) {
342 27
                foreach ($parentIds as $parentId) {
343 27
                    $depthCur++;
344 27
                    if ($parentId === null) {
345 27
                        break;
346
                    }
347 27
                    $result[] = $parentId;
348
                }
349
            } else {
350
                $parentId = null;
351
            }
352
        }
353 27
        if ($cache && $depth === null) {
354 24
            $this->_parentsIds = $result;
355
        }
356 27
        return $result;
357
    }
358
359
    /**
360
     * @param int|null $depth
361
     * @param bool $flat
362
     * @param bool $cache
363
     * @return array
364
     */
365 24
    public function getDescendantsIds($depth = null, $flat = false, $cache = true)
366
    {
367 24
        if ($cache && $this->_childrenIds !== null) {
368 6
            $result = $depth === null ? $this->_childrenIds : array_slice($this->_childrenIds, 0, $depth);
369 6
            return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
370
        }
371
372 24
        $result       = [];
373 24
        $tableName    = $this->owner->tableName();
374 24
        $primaryKey   = $this->getPrimaryKey();
375 24
        $depthCur     = 0;
376 24
        $lastLevelIds = [$this->owner->primaryKey];
377 24
        while (!empty($lastLevelIds) && ($depth === null || $depthCur < $depth)) {
378 24
            $levels = 1;
379 24
            $depthCur++;
380 24
            $query = (new Query())
381 24
                ->select(["lvl0.[[{$primaryKey}]] AS lvl0"])
382 24
                ->from("{$tableName} lvl0")
383 24
                ->where(["lvl0.[[{$this->parentAttribute}]]" => $lastLevelIds]);
384 24 View Code Duplication
            if ($this->sortable !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385 24
                $query->orderBy(["lvl0.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
386
            }
387 24
            for ($i = 0; $i < $this->childrenJoinLevels && ($depth === null || $i + $depthCur + 1 < $depth); $i++) {
388 18
                $depthCur++;
389 18
                $levels++;
390 18
                $j = $i + 1;
391
                $query
392 18
                    ->addSelect(["lvl{$j}.[[{$primaryKey}]] as lvl{$j}"])
393 18
                    ->leftJoin("{$tableName} lvl{$j}", [
394 18
                        'and',
395 18
                        "lvl{$j}.[[{$this->parentAttribute}]] = lvl{$i}.[[{$primaryKey}]]",
396 18
                        ['is not', "lvl{$i}.[[{$primaryKey}]]", null],
397
                    ]);
398 18 View Code Duplication
                if ($this->sortable !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
399 18
                    $query->addOrderBy(["lvl{$j}.[[{$this->behavior->sortAttribute}]]" => SORT_ASC]);
400
                }
401
            }
402 24
            if ($this->childrenJoinLevels) {
403 21
                $columns = [];
404 21
                foreach ($query->all($this->owner->getDb()) as $row) {
405 21
                    $level = 0;
406 21
                    foreach ($row as $id) {
407 21
                        if ($id !== null) {
408 21
                            $columns[$level][$id] = true;
409
                        }
410 21
                        $level++;
411
                    }
412
                }
413 21
                for ($i = 0; $i < $levels; $i++) {
414 21
                    if (isset($columns[$i])) {
415 21
                        $lastLevelIds = array_keys($columns[$i]);
416 21
                        $result[]     = $lastLevelIds;
417
                    } else {
418 18
                        $lastLevelIds = [];
419 18
                        break;
420
                    }
421
                }
422
            } else {
423 21
                $lastLevelIds = $query->column($this->owner->getDb());
424 21
                if ($lastLevelIds) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastLevelIds of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
425 21
                    $result[] = $lastLevelIds;
426
                }
427
            }
428
        }
429 24
        if ($cache && $depth === null) {
430 24
            $this->_childrenIds = $result;
431
        }
432 24
        return $flat && !empty($result) ? call_user_func_array('array_merge', $result) : $result;
433
    }
434
435
    /**
436
     * Populate children relations for self and all descendants
437
     *
438
     * @param int $depth = null
439
     * @return static
440
     */
441 3
    public function populateTree($depth = null)
442
    {
443
        /** @var ActiveRecord[]|static[] $nodes */
444 3
        $depths = [$this->owner->getPrimaryKey() => 0];
445 3
        if ($depth === null) {
446 3
            $nodes = $this->owner->descendantsOrdered;
447
        } else {
448 3
            $data = $this->getDescendantsIds($depth);
449 3
            foreach ($data as $i => $ids) {
450 3
                foreach ($ids as $id) {
451 3
                    $depths[$id] = $i + 1;
452
                }
453
            }
454 3
            $nodes  = $this->getDescendants($depth)
455 3
                ->orderBy($this->sortable !== false ? [$this->behavior->sortAttribute => SORT_ASC] : null)
0 ignored issues
show
Bug introduced by
It seems like $this->sortable !== fals...ute => SORT_ASC) : null can also be of type null; however, yii\db\QueryTrait::orderBy() does only seem to accept string|array|object<yii\db\ExpressionInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
456 3
                ->all();
457
        }
458
459 3
        $relates = [];
460 3
        foreach ($nodes as $node) {
461 3
            $key = $node->getAttribute($this->parentAttribute);
462 3
            if (!isset($relates[$key])) {
463 3
                $relates[$key] = [];
464
            }
465 3
            $relates[$key][] = $node;
466
        }
467
468 3
        $nodes[] = $this->owner;
469 3
        foreach ($nodes as $node) {
470 3
            $key = $node->getPrimaryKey();
471 3
            if (isset($relates[$key])) {
472 3
                $node->populateRelation('children', $relates[$key]);
473 3
            } elseif ($depth === null || (isset($depths[$node->getPrimaryKey()]) && $depths[$node->getPrimaryKey()] < $depth)) {
474 3
                $node->populateRelation('children', []);
475
            }
476
        }
477
478 3
        return $this->owner;
479
    }
480
481
    /**
482
     * @return bool
483
     */
484 81
    public function isRoot()
485
    {
486 81
        return $this->owner->getAttribute($this->parentAttribute) === null;
487
    }
488
489
    /**
490
     * @param ActiveRecord $node
491
     * @return bool
492
     */
493 15
    public function isChildOf($node)
494
    {
495 15
        $ids = $this->getParentsIds();
496 15
        return in_array($node->getPrimaryKey(), $ids);
497
    }
498
499
    /**
500
     * @return bool
501
     */
502 3
    public function isLeaf()
503
    {
504 3
        return count($this->owner->children) === 0;
505
    }
506
507
    /**
508
     * @return ActiveRecord
509
     */
510 6
    public function makeRoot()
511
    {
512 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...
513 6
        return $this->owner;
514
    }
515
516
    /**
517
     * @param ActiveRecord $node
518
     * @return ActiveRecord
519
     */
520 36
    public function prependTo($node)
521
    {
522 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...
523 36
        $this->node = $node;
524 36
        return $this->owner;
525
    }
526
527
    /**
528
     * @param ActiveRecord $node
529
     * @return ActiveRecord
530
     */
531 36
    public function appendTo($node)
532
    {
533 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...
534 36
        $this->node = $node;
535 36
        return $this->owner;
536
    }
537
538
    /**
539
     * @param ActiveRecord $node
540
     * @return ActiveRecord
541
     */
542 30
    public function insertBefore($node)
543
    {
544 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...
545 30
        $this->node = $node;
546 30
        return $this->owner;
547
    }
548
549
    /**
550
     * @param ActiveRecord $node
551
     * @return ActiveRecord
552
     */
553 33
    public function insertAfter($node)
554
    {
555 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...
556 33
        $this->node = $node;
557 33
        return $this->owner;
558
    }
559
560
    /**
561
     * Need for paulzi/auto-tree
562
     */
563
    public function preDeleteWithChildren()
564
    {
565
        $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...
566
    }
567
568
    /**
569
     * @return bool|int
570
     * @throws \Exception
571
     * @throws \yii\db\Exception
572
     */
573 12
    public function deleteWithChildren()
574
    {
575 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...
576 12
        if (!$this->owner->isTransactional(ActiveRecord::OP_DELETE)) {
577
            $transaction = $this->owner->getDb()->beginTransaction();
578
            try {
579
                $result = $this->deleteWithChildrenInternal();
580
                if ($result === false) {
581
                    $transaction->rollBack();
582
                } else {
583
                    $transaction->commit();
584
                }
585
                return $result;
586
            } catch (\Exception $e) {
587
                $transaction->rollBack();
588
                throw $e;
589
            }
590
        } else {
591 12
            $result = $this->deleteWithChildrenInternal();
592
        }
593 9
        return $result;
594
    }
595
596
    /**
597
     * @param bool $middle
598
     * @return int
599
     */
600 3
    public function reorderChildren($middle = true)
601
    {
602
        /** @var ActiveRecord|SortableBehavior $item */
603 3
        $item = $this->owner->children[0];
604 3
        if ($item) {
605 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...
606
        } else {
607
            return 0;
608
        }
609
    }
610
611
    /**
612
     * @throws Exception
613
     * @throws NotSupportedException
614
     */
615 147
    public function beforeSave()
616
    {
617 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...
618 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...
619
        }
620 147
        switch ($this->operation) {
621 147
            case self::OPERATION_MAKE_ROOT:
622 6
                $this->owner->setAttribute($this->parentAttribute, null);
623 6
                if ($this->sortable !== false) {
624 6
                    $this->owner->setAttribute($this->behavior->sortAttribute, 0);
625
                }
626 6
                break;
627
628 141
            case self::OPERATION_PREPEND_TO:
629 36
                $this->insertIntoInternal(false);
630 24
                break;
631
632 105
            case self::OPERATION_APPEND_TO:
633 36
                $this->insertIntoInternal(true);
634 24
                break;
635
636 69
            case self::OPERATION_INSERT_BEFORE:
637 30
                $this->insertNearInternal(false);
638 21
                break;
639
640 39
            case self::OPERATION_INSERT_AFTER:
641 33
                $this->insertNearInternal(true);
642 21
                break;
643
644
            default:
645 6
                if ($this->owner->getIsNewRecord()) {
646 3
                    throw new NotSupportedException('Method "' . $this->owner->className() . '::insert" is not supported for inserting new nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

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

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

Loading history...
647
                }
648
        }
649 99
    }
650
651
    /**
652
     *
653
     */
654 99
    public function afterSave()
655
    {
656 99
        $this->operation = null;
657 99
        $this->node      = null;
658 99
    }
659
660
    /**
661
     * @param \yii\base\ModelEvent $event
662
     * @throws Exception
663
     */
664 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...
665
    {
666 21
        if ($this->owner->getIsNewRecord()) {
667 6
            throw new Exception('Can not delete a node when it is new record.');
668
        }
669 15
        if ($this->isRoot() && $this->operation !== self::OPERATION_DELETE_ALL) {
670 3
            throw new Exception('Method "'. $this->owner->className() . '::delete" is not supported for deleting root nodes.');
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

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

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

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

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

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

Loading history...
698
        }
699 63
        return $primaryKey[0];
700
    }
701
702
    /**
703
     * @param bool $forInsertNear
704
     * @throws Exception
705
     */
706 135
    protected function checkNode($forInsertNear = false)
707
    {
708 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...
709 9
            throw new Exception('Can not move a node before/after root.');
710
        }
711 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...
712 12
            throw new Exception('Can not move a node when the target node is new record.');
713
        }
714
715 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...
716 12
            throw new Exception('Can not move a node when the target node is same.');
717
        }
718
719 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...
720 12
            throw new Exception('Can not move a node when the target node is child.');
721
        }
722 90
    }
723
724
    /**
725
     * Append to operation internal handler
726
     * @param bool $append
727
     * @throws Exception
728
     */
729 72
    protected function insertIntoInternal($append)
730
    {
731 72
        $this->checkNode(false);
732 48
        $this->owner->setAttribute($this->parentAttribute, $this->node->getPrimaryKey());
733 48
        if ($this->sortable !== false) {
734 48
            if ($append) {
735 24
                $this->behavior->moveLast();
736
            } else {
737 24
                $this->behavior->moveFirst();
738
            }
739
        }
740 48
    }
741
742
    /**
743
     * Insert operation internal handler
744
     * @param bool $forward
745
     * @throws Exception
746
     */
747 63
    protected function insertNearInternal($forward)
748
    {
749 63
        $this->checkNode(true);
750 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...
751 42
        if ($this->sortable !== false) {
752 42
            if ($forward) {
753 21
                $this->behavior->moveAfter($this->node);
0 ignored issues
show
Bug introduced by
It seems like $this->node can also be of type null or object<paulzi\adjacencyL...\AdjacencyListBehavior>; however, paulzi\sortable\SortableBehavior::moveAfter() does only seem to accept object<yii\db\ActiveRecord>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
754
            } else {
755 21
                $this->behavior->moveBefore($this->node);
0 ignored issues
show
Bug introduced by
It seems like $this->node can also be of type null or object<paulzi\adjacencyL...\AdjacencyListBehavior>; however, paulzi\sortable\SortableBehavior::moveBefore() does only seem to accept object<yii\db\ActiveRecord>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
756
            }
757
        }
758 42
    }
759
760
    /**
761
     * @return int
762
     */
763 12
    protected function deleteWithChildrenInternal()
764
    {
765 12
        if (!$this->owner->beforeDelete()) {
766
            return false;
767
        }
768 9
        $ids = $this->getDescendantsIds(null, true);
769 9
        $ids[] = $this->owner->primaryKey;
770 9
        $result = $this->owner->deleteAll([$this->getPrimaryKey() => $ids]);
771 9
        $this->owner->setOldAttributes(null);
772 9
        $this->owner->afterDelete();
773 9
        return $result;
774
    }
775
}