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 ( 65da6a...9371a0 )
by Pavel
03:23
created

AdjacencyListBehavior::afterSave()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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