Completed
Push — master ( ff35ff...cb4013 )
by ARCANEDEV
05:05
created

NodeTrait::getScopeAttributes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php namespace Arcanedev\LaravelNestedSet;
2
3
use Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation;
4
use Arcanedev\LaravelNestedSet\Traits\EloquentTrait;
5
use Arcanedev\LaravelNestedSet\Traits\SoftDeleteTrait;
6
use Arcanedev\LaravelNestedSet\Utilities\NestedSet;
7
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
8
use LogicException;
9
10
/**
11
 * Class     NodeTrait
12
 *
13
 * @package  Arcanedev\Taxonomies\Traits
14
 * @author   ARCANEDEV <[email protected]>
15
 *
16
 * @property  array  $attributes
17
 * @property  array  $original
18
 * @property  bool   $exists
19
 *
20
 * @method  static  bool   isBroken()
21
 * @method  static  array  getNodeData($id, $required = false)
22
 * @method  static  array  getPlainNodeData($id, $required = false)
23
 *
24
 * @method  \Illuminate\Database\Eloquent\Relations\BelongsTo  belongsTo(string $related, string $foreignKey = null, string $otherKey = null, string $relation = null)
25
 * @method  \Illuminate\Database\Eloquent\Relations\HasMany    hasMany(string $related, string $foreignKey = null, string $localKey = null)
26
 */
27
trait NodeTrait
28
{
29
    /* ------------------------------------------------------------------------------------------------
30
     |  Traits
31
     | ------------------------------------------------------------------------------------------------
32
     */
33
    use EloquentTrait, SoftDeleteTrait;
34
35
    /* ------------------------------------------------------------------------------------------------
36
     |  Properties
37
     | ------------------------------------------------------------------------------------------------
38
     */
39
    /**
40
     * Pending operation.
41
     *
42
     * @var array|null
43
     */
44
    protected $pending;
45
46
    /**
47
     * Whether the node has moved since last save.
48
     *
49
     * @var bool
50
     */
51
    protected $moved = false;
52
53
    /**
54
     * Keep track of the number of performed operations.
55
     *
56
     * @var int
57
     */
58
    public static $actionsPerformed = 0;
59
60
    /* ------------------------------------------------------------------------------------------------
61
     |  Boot Function
62
     | ------------------------------------------------------------------------------------------------
63
     */
64
    /**
65
     * Sign on model events.
66
     */
67 280
    public static function bootNodeTrait()
68
    {
69
        static::saving(function ($model) {
70
            /** @var self $model */
71 96
            $model->getConnection()->beginTransaction();
72
73 96
            return $model->callPendingAction();
74 280
        });
75
76
        static::saved(function ($model) {
77
            /** @var self $model */
78 92
            $model->getConnection()->commit();
79 280
        });
80
81
        static::deleting(function ($model) {
0 ignored issues
show
Bug introduced by
The method deleting() does not exist on Arcanedev\LaravelNestedSet\NodeTrait. Did you maybe mean hardDeleting()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
82
            /** @var self $model */
83 20
            $model->getConnection()->beginTransaction();
84
85
            // We will need fresh data to delete node safely
86 20
            $model->refreshNode();
87 280
        });
88
89
        static::deleted(function ($model) {
0 ignored issues
show
Bug introduced by
The method deleted() does not exist on Arcanedev\LaravelNestedSet\NodeTrait. Did you maybe mean deleteDescendants()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
90
            /** @var self $model */
91 20
            $model->deleteDescendants();
92
93 20
            $model->getConnection()->commit();
94 280
        });
95
96 280
        if (static::usesSoftDelete()) {
97
            static::restoring(function ($model) {
98
                /** @var self $model */
99 4
                $model->getConnection()->beginTransaction();
100
101 4
                static::$deletedAt = $model->{$model->getDeletedAtColumn()};
0 ignored issues
show
Bug introduced by
It seems like getDeletedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
102 236
            });
103
104 236
            static::restored(function ($model) {
0 ignored issues
show
Bug introduced by
The method restored() does not exist on Arcanedev\LaravelNestedSet\NodeTrait. Did you maybe mean restoreDescendants()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
105
                /** @var self $model */
106 4
                $model->restoreDescendants(static::$deletedAt);
107
108 4
                $model->getConnection()->commit();
109 236
            });
110 177
        }
111 280
    }
112
113
    /* ------------------------------------------------------------------------------------------------
114
     |  Relationships
115
     | ------------------------------------------------------------------------------------------------
116
     */
117
    /**
118
     * Relation to the parent.
119
     *
120
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
121
     */
122 16
    public function parent()
123
    {
124 16
        return $this->belongsTo(get_class($this), $this->getParentIdName())
125 16
            ->setModel($this);
126
    }
127
128
    /**
129
     * Relation to children.
130
     *
131
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
132
     */
133 16
    public function children()
134
    {
135 16
        return $this->hasMany(get_class($this), $this->getParentIdName())
136 16
            ->setModel($this);
137
    }
138
139
    /**
140
     * Get query for descendants of the node.
141
     *
142
     * @return \Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation
143
     */
144 44
    public function descendants()
145
    {
146 44
        return new DescendantsRelation($this->newScopedQuery(), $this);
147
    }
148
149
    /**
150
     * Get query for siblings of the node.
151
     *
152
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
153
     */
154 8
    public function siblings()
155
    {
156 8
        return $this->newScopedQuery()
157 8
            ->where($this->getKeyName(), '<>', $this->getKey())
158 8
            ->where($this->getParentIdName(), '=', $this->getParentId());
159
    }
160
161
    /* ------------------------------------------------------------------------------------------------
162
     |  Getters & Setters
163
     | ------------------------------------------------------------------------------------------------
164
     */
165
    /**
166
     * Get the lft key name.
167
     *
168
     * @return string
169
     */
170 248
    public function getLftName()
171
    {
172 248
        return NestedSet::LFT;
173
    }
174
175
    /**
176
     * Get the rgt key name.
177
     *
178
     * @return string
179
     */
180 216
    public function getRgtName()
181
    {
182 216
        return NestedSet::RGT;
183
    }
184
185
    /**
186
     * Get the parent id key name.
187
     *
188
     * @return string
189
     */
190 164
    public function getParentIdName()
191
    {
192 164
        return NestedSet::PARENT_ID;
193
    }
194
195
    /**
196
     * Get the value of the model's lft key.
197
     *
198
     * @return int
199
     */
200 180
    public function getLft()
201
    {
202 180
        return $this->getAttributeValue($this->getLftName());
203
    }
204
205
    /**
206
     * Set the value of the model's lft key.
207
     *
208
     * @param  int  $value
209
     *
210
     * @return self
211
     */
212 92
    public function setLft($value)
213
    {
214 92
        $this->attributes[$this->getLftName()] = $value;
215
216 92
        return $this;
217
    }
218
219
    /**
220
     * Get the value of the model's rgt key.
221
     *
222
     * @return int
223
     */
224 120
    public function getRgt()
225
    {
226 120
        return $this->getAttributeValue($this->getRgtName());
227
    }
228
229
    /**
230
     * Set the value of the model's rgt key.
231
     *
232
     * @param  int  $value
233
     *
234
     * @return self
235
     */
236 92
    public function setRgt($value)
237
    {
238 92
        $this->attributes[$this->getRgtName()] = $value;
239
240 92
        return $this;
241
    }
242
243
    /**
244
     * Get the value of the model's parent id key.
245
     *
246
     * @return int
247
     */
248 96
    public function getParentId()
249
    {
250 96
        return $this->getAttributeValue($this->getParentIdName());
251
    }
252
253
    /**
254
     * Set the value of the model's parent id key.
255
     *
256
     * @param  int  $value
257
     *
258
     * @return self
259
     */
260 64
    public function setParentId($value)
261
    {
262 64
        $this->attributes[$this->getParentIdName()] = $value;
263
264 64
        return $this;
265
    }
266
267
    /**
268
     * Apply parent model.
269
     *
270
     * @param  \Illuminate\Database\Eloquent\Model|null  $value
271
     *
272
     * @return self
273
     */
274 56
    protected function setParent($value)
275
    {
276 56
        $this->setParentId($value ? $value->getKey() : null)
277 56
            ->setRelation('parent', $value);
278
279 56
        return $this;
280
    }
281
282
    /**
283
     * Set the value of model's parent id key.
284
     *
285
     * Behind the scenes node is appended to found parent node.
286
     *
287
     * @param  int  $value
288
     *
289
     * @throws \Exception If parent node doesn't exists
290
     */
291 12
    public function setParentIdAttribute($value)
292
    {
293 12
        if ($this->getParentId() == $value) return;
294
295 12
        if ($value) {
296
            /** @var self $node */
297 12
            $node = $this->newScopedQuery()->findOrFail($value);
298
299 12
            $this->appendToNode($node);
300 9
        } else {
301 4
            $this->makeRoot();
302
        }
303 12
    }
304
305
    /**
306
     * Get the boundaries.
307
     *
308
     * @return array
309
     */
310 44
    public function getBounds()
311
    {
312 44
        return [$this->getLft(), $this->getRgt()];
313
    }
314
315
    /**
316
     * @return array
317
     */
318 160
    protected function getScopeAttributes()
319
    {
320 160
        return null;
321
    }
322
323
    /**
324
     * Set the lft and rgt boundaries to null.
325
     *
326
     * @return self
327
     */
328 68
    protected function dirtyBounds()
329
    {
330 68
        return $this->setLft(null)->setRgt(null);
331
    }
332
333
    /**
334
     * Returns node that is next to current node without constraining to siblings.
335
     * This can be either a next sibling or a next sibling of the parent node.
336
     *
337
     * @param  array  $columns
338
     *
339
     * @return self
340
     */
341
    public function getNextNode(array $columns = ['*'])
342
    {
343
        return $this->nextNodes()->defaultOrder()->first($columns);
344
    }
345
346
    /**
347
     * Returns node that is before current node without constraining to siblings.
348
     * This can be either a prev sibling or parent node.
349
     *
350
     * @param  array  $columns
351
     *
352
     * @return self
353
     */
354 4
    public function getPrevNode(array $columns = ['*'])
355
    {
356 4
        return $this->prevNodes()->defaultOrder('desc')->first($columns);
357
    }
358
359
    /**
360
     * Get the ancestors nodes.
361
     *
362
     * @param  array  $columns
363
     *
364
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
365
     */
366 8
    public function getAncestors(array $columns = ['*'])
367
    {
368 8
        return $this->newScopedQuery()
369 8
            ->defaultOrder()
370 8
            ->ancestorsOf($this, $columns);
371
    }
372
373
    /**
374
     * Get the descendants nodes.
375
     *
376
     * @param  array  $columns
377
     *
378
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
379
     */
380 12
    public function getDescendants(array $columns = ['*'])
381
    {
382 12
        return $this->descendants()->get($columns);
0 ignored issues
show
Documentation Bug introduced by
The method get does not exist on object<Arcanedev\Laravel...nt\DescendantsRelation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
383
    }
384
385
    /**
386
     * Get the siblings nodes.
387
     *
388
     * @param  array  $columns
389
     *
390
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
391
     */
392 8
    public function getSiblings(array $columns = ['*'])
393
    {
394 8
        return $this->siblings()->get($columns);
395
    }
396
397
    /**
398
     * Get the next siblings nodes.
399
     *
400
     * @param  array  $columns
401
     *
402
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
403
     */
404 8
    public function getNextSiblings(array $columns = ['*'])
405
    {
406 8
        return $this->nextSiblings()->get($columns);
407
    }
408
409
    /**
410
     * Get the previous siblings nodes.
411
     *
412
     * @param  array  $columns
413
     *
414
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
415
     */
416 8
    public function getPrevSiblings(array $columns = ['*'])
417
    {
418 8
        return $this->prevSiblings()->get($columns);
419
    }
420
421
    /**
422
     * Get the next sibling node.
423
     *
424
     * @param  array  $columns
425
     *
426
     * @return self
427
     */
428 4
    public function getNextSibling(array $columns = ['*'])
429
    {
430 4
        return $this->nextSiblings()->defaultOrder()->first($columns);
431
    }
432
433
    /**
434
     * Get the previous sibling node.
435
     *
436
     * @param  array  $columns
437
     *
438
     * @return self
439
     */
440 4
    public function getPrevSibling(array $columns = ['*'])
441
    {
442 4
        return $this->prevSiblings()->defaultOrder('desc')->first($columns);
443
    }
444
445
    /**
446
     * Get node height (rgt - lft + 1).
447
     *
448
     * @return int
449
     */
450 36
    public function getNodeHeight()
451
    {
452 36
        if ( ! $this->exists) return 2;
453
454 4
        return $this->getRgt() - $this->getLft() + 1;
455
    }
456
457
    /**
458
     * Get number of descendant nodes.
459
     *
460
     * @return int
461
     */
462 4
    public function getDescendantCount()
463
    {
464 4
        return (int) ceil($this->getNodeHeight() / 2) - 1;
465
    }
466
467
    /**
468
     * Set an action.
469
     *
470
     * @param  string  $action
471
     *
472
     * @return self
473
     */
474 116
    protected function setNodeAction($action)
475
    {
476 116
        $this->pending = func_get_args();
477 116
        unset($action);
478
479 116
        return $this;
480
    }
481
482
    /* ------------------------------------------------------------------------------------------------
483
     |  Other Functions
484
     | ------------------------------------------------------------------------------------------------
485
     */
486
    /**
487
     * Get the lower bound.
488
     *
489
     * @return int
490
     */
491 32
    protected function getLowerBound()
492
    {
493 32
        return (int) $this->newNestedSetQuery()->max($this->getRgtName());
0 ignored issues
show
Documentation Bug introduced by
The method max does not exist on object<Arcanedev\Laravel...\Eloquent\QueryBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
494
    }
495
496
    /**
497
     * Call pending action.
498
     *
499
     * @return null|false
500
     */
501 96
    protected function callPendingAction()
502
    {
503 96
        $this->moved = false;
504
505 96
        if ( ! $this->pending && ! $this->exists) {
506 20
            $this->makeRoot();
507 15
        }
508
509 96
        if ( ! $this->pending) return;
510
511 92
        $method        = 'action'.ucfirst(array_shift($this->pending));
512 92
        $parameters    = $this->pending;
513
514 92
        $this->pending = null;
515 92
        $this->moved   = call_user_func_array([$this, $method], $parameters);
516 92
    }
517
518
    /**
519
     * @return bool
520
     */
521 8
    protected function actionRaw()
522
    {
523 8
        return true;
524
    }
525
526
    /**
527
     * Make a root node.
528
     */
529 32
    protected function actionRoot()
530
    {
531
        // Simplest case that do not affect other nodes.
532 32
        if ( ! $this->exists) {
533 20
            $cut = $this->getLowerBound() + 1;
534
535 20
            $this->setLft($cut);
536 20
            $this->setRgt($cut + 1);
537
538 20
            return true;
539
        }
540
541 12
        if ($this->isRoot()) return false;
542
543
544
        // Reset parent object
545 12
        $this->setParent(null);
546
547 12
        return $this->insertAt($this->getLowerBound() + 1);
548
    }
549
550
    /**
551
     * Append or prepend a node to the parent.
552
     *
553
     * @param  self  $parent
0 ignored issues
show
introduced by
The type NodeTrait for parameter $parent is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
554
     * @param  bool  $prepend
555
     *
556
     * @return bool
557
     */
558 32
    protected function actionAppendOrPrepend(self $parent, $prepend = false)
559
    {
560 32
        $parent->refreshNode();
561
562 32
        $cut = $prepend ? $parent->getLft() + 1 : $parent->getRgt();
563
564 32
        if ( ! $this->insertAt($cut)) {
565
            return false;
566
        }
567
568 32
        $parent->refreshNode();
569
570 32
        return true;
571
    }
572
573
    /**
574
     * Insert node before or after another node.
575
     *
576
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
577
     * @param  bool  $after
578
     *
579
     * @return bool
580
     */
581 28
    protected function actionBeforeOrAfter(self $node, $after = false)
582
    {
583 28
        $node->refreshNode();
584
585 28
        return $this->insertAt($after ? $node->getRgt() + 1 : $node->getLft());
586
    }
587
588
    /**
589
     * Refresh node's crucial attributes.
590
     */
591 88
    public function refreshNode()
592
    {
593 88
        if ( ! $this->exists || static::$actionsPerformed === 0) return;
594
595 64
        $attributes = $this->newNestedSetQuery()->getNodeData($this->getKey());
596
597 64
        $this->attributes = array_merge($this->attributes, $attributes);
598 64
        $this->original   = array_merge($this->original,   $attributes);
599 64
    }
600
601
    /**
602
     * Get query for siblings after the node.
603
     *
604
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
605
     */
606 24
    public function nextSiblings()
607
    {
608 24
        return $this->nextNodes()
609 24
            ->where($this->getParentIdName(), '=', $this->getParentId());
610
    }
611
612
    /**
613
     * Get query for siblings before the node.
614
     *
615
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
616
     */
617 16
    public function prevSiblings()
618
    {
619 16
        return $this->prevNodes()
620 16
            ->where($this->getParentIdName(), '=', $this->getParentId());
621
    }
622
623
    /**
624
     * Get query for nodes after current node.
625
     *
626
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
627
     */
628 28
    public function nextNodes()
629
    {
630 28
        return $this->newScopedQuery()
631 28
            ->where($this->getLftName(), '>', $this->getLft());
632
    }
633
634
    /**
635
     * Get query for nodes before current node in reversed order.
636
     *
637
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
638
     */
639 20
    public function prevNodes()
640
    {
641 20
        return $this->newScopedQuery()
642 20
            ->where($this->getLftName(), '<', $this->getLft());
643
    }
644
645
    /**
646
     * Get query for ancestors to the node not including the node itself.
647
     *
648
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
649
     */
650 4
    public function ancestors()
651
    {
652 4
        return $this->newScopedQuery()
653 4
            ->whereAncestorOf($this)->defaultOrder();
654
    }
655
656
    /**
657
     * Make this node a root node.
658
     *
659
     * @return self
660
     */
661 48
    public function makeRoot()
662
    {
663 48
        return $this->setNodeAction('root');
664
    }
665
666
    /**
667
     * Save node as root.
668
     *
669
     * @return bool
670
     */
671 8
    public function saveAsRoot()
672
    {
673 8
        if ($this->exists && $this->isRoot()) {
674
            return true;
675
        }
676
677 8
        return $this->makeRoot()->save();
678
    }
679
680
    /**
681
     * Append and save a node.
682
     *
683
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
684
     *
685
     * @return bool
686
     */
687 16
    public function appendNode(self $node)
688
    {
689 16
        return $node->appendToNode($this)->save();
690
    }
691
692
    /**
693
     * Prepend and save a node.
694
     *
695
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
696
     *
697
     * @return bool
698
     */
699 4
    public function prependNode(self $node)
700
    {
701 4
        return $node->prependToNode($this)->save();
702
    }
703
704
    /**
705
     * Append a node to the new parent.
706
     *
707
     * @param  self  $parent
0 ignored issues
show
introduced by
The type NodeTrait for parameter $parent is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
708
     *
709
     * @return self
710
     */
711 40
    public function appendToNode(self $parent)
712
    {
713 40
        return $this->appendOrPrependTo($parent);
714
    }
715
716
    /**
717
     * Prepend a node to the new parent.
718
     *
719
     * @param  self  $parent
0 ignored issues
show
introduced by
The type NodeTrait for parameter $parent is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
720
     *
721
     * @return self
722
     */
723 4
    public function prependToNode(self $parent)
724
    {
725 4
        return $this->appendOrPrependTo($parent, true);
726
    }
727
728
    /**
729
     * @param  self  $parent
0 ignored issues
show
introduced by
The type NodeTrait for parameter $parent is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
730
     * @param  bool  $prepend
731
     *
732
     * @return self
733
     */
734 44
    public function appendOrPrependTo(self $parent, $prepend = false)
735
    {
736 44
        $this->assertNodeExists($parent)
737 44
             ->assertNotDescendant($parent);
738
739 40
        $this->setParent($parent)->dirtyBounds();
740
741 40
        return $this->setNodeAction('appendOrPrepend', $parent, $prepend);
742
    }
743
744
    /**
745
     * Insert self after a node.
746
     *
747
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
748
     *
749
     * @return self
750
     */
751 28
    public function afterNode(self $node)
752
    {
753 28
        return $this->beforeOrAfterNode($node, true);
754
    }
755
756
    /**
757
     * Insert self before node.
758
     *
759
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
760
     *
761
     * @return self
762
     */
763 8
    public function beforeNode(self $node)
764
    {
765 8
        return $this->beforeOrAfterNode($node);
766
    }
767
768
    /**
769
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
770
     * @param  bool  $after
771
     *
772
     * @return self
773
     */
774 36
    public function beforeOrAfterNode(self $node, $after = false)
775
    {
776 36
        $this->assertNodeExists($node)->assertNotDescendant($node);
777
778 32
        if ( ! $this->isSiblingOf($node)) {
779 12
            $this->setParent($node->getRelationValue('parent'));
780 9
        }
781
782 32
        $this->dirtyBounds();
783
784 32
        return $this->setNodeAction('beforeOrAfter', $node, $after);
785
    }
786
787
    /**
788
     * Insert self after a node and save.
789
     *
790
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
791
     *
792
     * @return bool
793
     */
794 16
    public function insertAfterNode(self $node)
795
    {
796 16
        return $this->afterNode($node)->save();
797
    }
798
799
    /**
800
     * Insert self before a node and save.
801
     *
802
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
803
     *
804
     * @return bool
805
     */
806 4
    public function insertBeforeNode(self $node)
807
    {
808 4
        if ( ! $this->beforeNode($node)->save()) return false;
809
810
        // We'll update the target node since it will be moved
811 4
        $node->refreshNode();
812
813 4
        return true;
814
    }
815
816
    /**
817
     * @param  int  $lft
818
     * @param  int  $rgt
819
     * @param  int  $parentId
820
     *
821
     * @return self
822
     */
823 8
    public function rawNode($lft, $rgt, $parentId)
824
    {
825 8
        $this->setLft($lft)->setRgt($rgt)->setParentId($parentId);
826
827 8
        return $this->setNodeAction('raw');
828
    }
829
830
    /**
831
     * Move node up given amount of positions.
832
     *
833
     * @param  int  $amount
834
     *
835
     * @return bool
836
     */
837 4
    public function up($amount = 1)
838
    {
839 4
        $sibling = $this->prevSiblings()
0 ignored issues
show
Documentation Bug introduced by
The method skip does not exist on object<Arcanedev\Laravel...\Eloquent\QueryBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
840 4
                        ->defaultOrder('desc')
841 4
                        ->skip($amount - 1)
842 4
                        ->first();
843
844 4
        if ( ! $sibling) return false;
845
846 4
        return $this->insertBeforeNode($sibling);
847
    }
848
849
    /**
850
     * Move node down given amount of positions.
851
     *
852
     * @param  int  $amount
853
     *
854
     * @return bool
855
     */
856 16
    public function down($amount = 1)
857
    {
858 16
        $sibling = $this->nextSiblings()
0 ignored issues
show
Documentation Bug introduced by
The method skip does not exist on object<Arcanedev\Laravel...\Eloquent\QueryBuilder>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
859 16
                        ->defaultOrder()
860 16
                        ->skip($amount - 1)
861 16
                        ->first();
862
863 16
        if ( ! $sibling) return false;
864
865 16
        return $this->insertAfterNode($sibling);
866
    }
867
868
    /**
869
     * Insert node at specific position.
870
     *
871
     * @param  int  $position
872
     *
873
     * @return bool
874
     */
875 68
    protected function insertAt($position)
876
    {
877 68
        ++static::$actionsPerformed;
878
879 68
        $result = $this->exists
880 61
            ? $this->moveNode($position)
881 68
            : $this->insertNode($position);
882
883 68
        return $result;
884
    }
885
886
    /**
887
     * Move a node to the new position.
888
     *
889
     * @param  int  $position
890
     *
891
     * @return int
892
     */
893 40
    protected function moveNode($position)
894
    {
895 40
        $updated = $this->newNestedSetQuery()
896 40
                        ->moveNode($this->getKey(), $position) > 0;
897
898 40
        if ($updated) $this->refreshNode();
899
900 40
        return $updated;
901
    }
902
903
    /**
904
     * Insert new node at specified position.
905
     *
906
     * @param  int  $position
907
     *
908
     * @return bool
909
     */
910 32
    protected function insertNode($position)
911
    {
912 32
        $this->newNestedSetQuery()->makeGap($position, 2);
913
914 32
        $height = $this->getNodeHeight();
915
916 32
        $this->setLft($position);
917 32
        $this->setRgt($position + $height - 1);
918
919 32
        return true;
920
    }
921
922
    /**
923
     * Update the tree when the node is removed physically.
924
     */
925 20
    protected function deleteDescendants()
926
    {
927 20
        $lft = $this->getLft();
928 20
        $rgt = $this->getRgt();
929
930 20
        $method = ($this->usesSoftDelete() && $this->forceDeleting)
931 18
            ? 'forceDelete'
932 20
            : 'delete';
933
934 20
        $this->descendants()->{$method}();
935
936 20
        if ($this->hardDeleting()) {
937 16
            $height = $rgt - $lft + 1;
938
939 16
            $this->newNestedSetQuery()->makeGap($rgt + 1, -$height);
940
941
            // In case if user wants to re-create the node
942 16
            $this->makeRoot();
943
944 16
            static::$actionsPerformed++;
945 12
        }
946 20
    }
947
948
    /**
949
     * Restore the descendants.
950
     *
951
     * @param  \Carbon\Carbon  $deletedAt
952
     */
953 4
    protected function restoreDescendants($deletedAt)
954
    {
955 4
        $this->descendants()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<Arcanedev\Laravel...nt\DescendantsRelation>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
956 4
            ->where($this->getDeletedAtColumn(), '>=', $deletedAt)
0 ignored issues
show
Bug introduced by
It seems like getDeletedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
957 4
            ->applyScopes()
958 4
            ->restore();
959 4
    }
960
961
    /**
962
     * Get a new base query that includes deleted nodes.
963
     *
964
     * @param  string|null $table
965
     *
966
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
967
     */
968 116
    public function newNestedSetQuery($table = null)
969
    {
970 116
        $builder = $this->usesSoftDelete()
971 110
            ? $this->withTrashed()
0 ignored issues
show
Bug introduced by
It seems like withTrashed() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
972 116
            : $this->newQuery();
973
974 116
        return $this->applyNestedSetScope($builder, $table);
975
    }
976
977
    /**
978
     * @param  string|null  $table
979
     *
980
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
981
     */
982 136
    public function newScopedQuery($table = null)
983
    {
984 136
        return $this->applyNestedSetScope($this->newQuery(), $table);
985
    }
986
987
    /**
988
     * @param  \Illuminate\Database\Eloquent\Builder  $query
989
     * @param  string                                 $table
990
     *
991
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder|\Illuminate\Database\Query\Builder
992
     */
993 204
    public function applyNestedSetScope($query, $table = null)
994
    {
995 204
        if ( ! $scoped = $this->getScopeAttributes()) {
996 160
            return $query;
997
        }
998
999 44
        if ($table === null) {
1000 44
            $table = $this->getTable();
1001 33
        }
1002
1003 44
        foreach ($scoped as $attribute) {
1004 44
            $query->where("$table.$attribute", '=', $this->getAttributeValue($attribute));
1005 33
        }
1006
1007 44
        return $query;
1008
    }
1009
1010
    /**
1011
     * @param  array  $attributes
1012
     *
1013
     * @return self
1014
     */
1015 12
    public static function scoped(array $attributes)
1016
    {
1017 12
        $instance = new static;
1018
1019 12
        $instance->setRawAttributes($attributes);
1020
1021 12
        return $instance->newScopedQuery();
1022
    }
1023
1024
    /**
1025
     * Save a new model and return the instance.
1026
     *
1027
     * Use `children` key on `$attributes` to create child nodes.
1028
     *
1029
     * @param  array  $attributes
1030
     * @param  self   $parent
0 ignored issues
show
introduced by
The type NodeTrait for parameter $parent is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
1031
     *
1032
     * @return static
1033
     */
1034 12
    public static function create(array $attributes = [], self $parent = null)
1035
    {
1036 12
        $children = array_pull($attributes, 'children');
1037 12
        $instance = new static($attributes);
0 ignored issues
show
Unused Code introduced by
The call to NodeTrait::__construct() has too many arguments starting with $attributes.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1038
1039 12
        if ($parent) {
1040 4
            $instance->appendToNode($parent);
1041 3
        }
1042
1043 12
        $instance->save();
1044
1045
        // Now create children
1046 12
        $relation = new EloquentCollection;
1047
1048 12
        foreach ((array) $children as $child) {
1049 4
            $relation->add($child = static::create($child, $instance));
1050
1051 4
            $child->setRelation('parent', $instance);
1052 9
        }
1053
1054 12
        return $instance->setRelation('children', $relation);
1055
    }
1056
1057
    /* ------------------------------------------------------------------------------------------------
1058
     |  Check Functions
1059
     | ------------------------------------------------------------------------------------------------
1060
     */
1061
    /**
1062
     * Get whether node is root.
1063
     *
1064
     * @return bool
1065
     */
1066 16
    public function isRoot()
1067
    {
1068 16
        return is_null($this->getParentId());
1069
    }
1070
1071
    /**
1072
     * Get whether a node is a descendant of other node.
1073
     *
1074
     * @param self $other
0 ignored issues
show
introduced by
The type NodeTrait for parameter $other is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
1075
     *
1076
     * @return bool
1077
     */
1078 76
    public function isDescendantOf(self $other)
1079
    {
1080
        return (
1081 76
            $this->getLft() > $other->getLft() &&
1082 70
            $this->getLft() < $other->getRgt()
1083 57
        );
1084
    }
1085
1086
    /**
1087
     * Get whether the node is immediate children of other node.
1088
     *
1089
     * @param  self  $other
0 ignored issues
show
introduced by
The type NodeTrait for parameter $other is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
1090
     *
1091
     * @return bool
1092
     */
1093 4
    public function isChildOf(self $other)
1094
    {
1095 4
        return $this->getParentId() == $other->getKey();
1096
    }
1097
1098
    /**
1099
     * Get whether the node is a sibling of another node.
1100
     *
1101
     * @param  self  $other
0 ignored issues
show
introduced by
The type NodeTrait for parameter $other is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
1102
     *
1103
     * @return bool
1104
     */
1105 32
    public function isSiblingOf(self $other)
1106
    {
1107 32
        return $this->getParentId() == $other->getParentId();
1108
    }
1109
1110
    /**
1111
     * Get whether the node is an ancestor of other node, including immediate parent.
1112
     *
1113
     * @param  self  $other
0 ignored issues
show
introduced by
The type NodeTrait for parameter $other is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
1114
     *
1115
     * @return bool
1116
     */
1117 4
    public function isAncestorOf(self $other)
1118
    {
1119 4
        return $other->isDescendantOf($this);
1120
    }
1121
1122
    /**
1123
     * Get whether the node has moved since last save.
1124
     *
1125
     * @return bool
1126
     */
1127 24
    public function hasMoved()
1128
    {
1129 24
        return $this->moved;
1130
    }
1131
1132
    /* ------------------------------------------------------------------------------------------------
1133
     |  Assertion Functions
1134
     | ------------------------------------------------------------------------------------------------
1135
     */
1136
    /**
1137
     * Assert that the node is not a descendant.
1138
     *
1139
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
1140
     *
1141
     * @return self
1142
     */
1143 76
    protected function assertNotDescendant(self $node)
1144
    {
1145 76
        if ($node == $this || $node->isDescendantOf($this)) {
1146 8
            throw new LogicException('Node must not be a descendant.');
1147
        }
1148
1149 68
        return $this;
1150
    }
1151
1152
    /**
1153
     * Assert node exists.
1154
     *
1155
     * @param  self  $node
0 ignored issues
show
introduced by
The type NodeTrait for parameter $node is a trait, and thus cannot be used for type-hinting in PHP. Maybe consider adding an interface and use that for type-hinting?
Loading history...
1156
     *
1157
     * @return self
1158
     */
1159 76
    protected function assertNodeExists(self $node)
1160
    {
1161 76
        if ( ! $node->getLft() || ! $node->getRgt()) {
1162
            throw new LogicException('Node must exists.');
1163
        }
1164
1165 76
        return $this;
1166
    }
1167
}
1168