Completed
Push — master ( 026f51...318521 )
by ARCANEDEV
10:18
created

NodeTrait::siblingsAndSelf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
ccs 0
cts 3
cp 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
crap 2
1
<?php namespace Arcanedev\LaravelNestedSet;
2
3
use Arcanedev\LaravelNestedSet\Contracts\Nodeable;
4
use Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation;
5
use Arcanedev\LaravelNestedSet\Traits\EloquentTrait;
6
use Arcanedev\LaravelNestedSet\Traits\SoftDeleteTrait;
7
use Arcanedev\LaravelNestedSet\Utilities\NestedSet;
8
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
9
use LogicException;
10
11
/**
12
 * Class     NodeTrait
13
 *
14
 * @package  Arcanedev\LaravelNestedSet
15
 * @author   ARCANEDEV <[email protected]>
16
 *
17
 * @property  int                                             id
18
 * @property  int                                             _lft
19
 * @property  int                                             _rgt
20
 * @property  int                                             parent_id
21
 * @property  array                                           attributes
22
 * @property  array                                           original
23
 * @property  bool                                            exists
24
 * @property  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  parent
25
 * @property  \Illuminate\Database\Eloquent\Collection        children
26
 *
27
 * @method  static  bool                                               isBroken()
28
 * @method  static  array                                              getNodeData($id, $required = false)
29
 * @method  static  array                                              getPlainNodeData($id, $required = false)
30
 * @method  static  int                                                rebuildTree(array $data, bool $delete = false)
31
 * @method  static  int                                                fixTree()
32
 * @method  static  \Arcanedev\LaravelNestedSet\Contracts\Nodeable     root(array $columns = ['*'])
33
 *
34
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  ancestorsOf(mixed $id, array $columns = ['*'])
35
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  withDepth(string $as = 'depth')
36
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  withoutRoot()
37
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  whereDescendantOf(mixed $id, string $boolean = 'and', bool $not = false)
38
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  whereAncestorOf(mixed $id)
39
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  whereIsRoot()
40
 * @method  static  \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder  descendantsAndSelf(mixed $id, array $columns = ['*'])
41
 * @method  static  array                                              countErrors()
42
 *
43
 * @method  \Illuminate\Database\Eloquent\Relations\BelongsTo          belongsTo(string $related, string $foreignKey = null, string $otherKey = null, string $relation = null)
44
 * @method  \Illuminate\Database\Eloquent\Relations\HasMany            hasMany(string $related, string $foreignKey = null, string $localKey = null)
45
 * @method  static  \Illuminate\Database\Eloquent\Builder              where(string $column, string $operator = null, mixed $value = null, string $boolean = 'and')
46
 */
47
trait NodeTrait
48
{
49
    /* ------------------------------------------------------------------------------------------------
50
     |  Traits
51
     | ------------------------------------------------------------------------------------------------
52
     */
53
    use EloquentTrait, SoftDeleteTrait;
54
55
    /* ------------------------------------------------------------------------------------------------
56
     |  Properties
57
     | ------------------------------------------------------------------------------------------------
58
     */
59
    /**
60
     * Pending operation.
61
     *
62
     * @var array|null
63
     */
64
    protected $pending;
65
66
    /**
67
     * Whether the node has moved since last save.
68
     *
69
     * @var bool
70
     */
71
    protected $moved = false;
72
73
    /**
74
     * Keep track of the number of performed operations.
75
     *
76
     * @var int
77
     */
78
    public static $actionsPerformed = 0;
79
80
    /* ------------------------------------------------------------------------------------------------
81
     |  Boot Function
82
     | ------------------------------------------------------------------------------------------------
83
     */
84
    /**
85
     * Sign on model events.
86
     */
87 432
    public static function bootNodeTrait()
88
    {
89
        static::saving(function ($model) {
90 150
            return $model->callPendingAction();
91 432
        });
92
93
        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...
94
            // We will need fresh data to delete node safely
95 30
            $model->refreshNode();
96 432
        });
97
98
        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...
99 30
            $model->deleteDescendants();
100 432
        });
101
102 432
        if (static::usesSoftDelete()) {
103
            static::restoring(function ($model) {
104 6
                static::$deletedAt = $model->{$model->getDeletedAtColumn()};
105 366
            });
106 366
            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...
107 6
                $model->restoreDescendants(static::$deletedAt);
108 366
            });
109 122
        }
110 432
    }
111
112
    /* ------------------------------------------------------------------------------------------------
113
     |  Relationships
114
     | ------------------------------------------------------------------------------------------------
115
     */
116
    /**
117
     * Relation to the parent.
118
     *
119
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
120
     */
121 24
    public function parent()
122
    {
123 24
        return $this->belongsTo(get_class($this), $this->getParentIdName())
124 24
            ->setModel($this);
125
    }
126
127
    /**
128
     * Relation to children.
129
     *
130
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
131
     */
132 24
    public function children()
133
    {
134 24
        return $this->hasMany(get_class($this), $this->getParentIdName())
135 24
            ->setModel($this);
136
    }
137
138
    /**
139
     * Get query for descendants of the node.
140
     *
141
     * @return \Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation
142
     */
143 72
    public function descendants()
144
    {
145 72
        return new DescendantsRelation($this->newScopedQuery(), $this);
146
    }
147
148
    /**
149
     * Get query for siblings of the node.
150
     *
151
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
152
     */
153 12
    public function siblings()
154
    {
155 12
        return $this->newScopedQuery()
156 12
            ->where($this->getKeyName(), '<>', $this->getKey())
157 12
            ->where($this->getParentIdName(), '=', $this->getParentId());
158
    }
159
160
    /**
161
     * Get query for the node siblings and the node itself.
162
     *
163
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
164
     */
165
    public function siblingsAndSelf()
166
    {
167
        return $this->newScopedQuery()
168
            ->where($this->getParentIdName(), '=', $this->getParentId());
169
    }
170
171
    /**
172
     * Get the node siblings and the node itself.
173
     *
174
     * @param  array  $columns
175
     *
176
     * @return \Illuminate\Database\Eloquent\Collection
177
     */
178
    public function getSiblingsAndSelf(array $columns = ['*'])
179
    {
180
        return $this->siblingsAndSelf()->get($columns);
181
    }
182
183
    /* ------------------------------------------------------------------------------------------------
184
     |  Getters & Setters
185
     | ------------------------------------------------------------------------------------------------
186
     */
187
    /**
188
     * Get the lft key name.
189
     *
190
     * @return string
191
     */
192 396
    public function getLftName()
193
    {
194 396
        return NestedSet::LFT;
195
    }
196
197
    /**
198
     * Get the rgt key name.
199
     *
200
     * @return string
201
     */
202 342
    public function getRgtName()
203
    {
204 342
        return NestedSet::RGT;
205
    }
206
207
    /**
208
     * Get the parent id key name.
209
     *
210
     * @return string
211
     */
212 258
    public function getParentIdName()
213
    {
214 258
        return NestedSet::PARENT_ID;
215
    }
216
217
    /**
218
     * Get the value of the model's lft key.
219
     *
220
     * @return int
221
     */
222 288
    public function getLft()
223
    {
224 288
        return $this->getAttributeValue($this->getLftName());
225
    }
226
227
    /**
228
     * Set the value of the model's lft key.
229
     *
230
     * @param  int  $value
231
     *
232
     * @return self
233
     */
234 90
    public function setLft($value)
235
    {
236 90
        $this->attributes[$this->getLftName()] = $value;
237
238 90
        return $this;
239
    }
240
241
    /**
242
     * Get the value of the model's rgt key.
243
     *
244
     * @return int
245
     */
246 192
    public function getRgt()
247
    {
248 192
        return $this->getAttributeValue($this->getRgtName());
249
    }
250
251
    /**
252
     * Set the value of the model's rgt key.
253
     *
254
     * @param  int  $value
255
     *
256
     * @return self
257
     */
258 90
    public function setRgt($value)
259
    {
260 90
        $this->attributes[$this->getRgtName()] = $value;
261
262 90
        return $this;
263
    }
264
265
    /**
266
     * Get the value of the model's parent id key.
267
     *
268
     * @return int
269
     */
270 156
    public function getParentId()
271
    {
272 156
        return $this->getAttributeValue($this->getParentIdName());
273
    }
274
275
    /**
276
     * Set the value of the model's parent id key.
277
     *
278
     * @param  int  $value
279
     *
280
     * @return self
281
     */
282 102
    public function setParentId($value)
283
    {
284 102
        $this->attributes[$this->getParentIdName()] = $value;
285
286 102
        return $this;
287
    }
288
289
    /**
290
     * Apply parent model.
291
     *
292
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable|null  $value
293
     *
294
     * @return self
295
     */
296 84
    protected function setParent($value)
297
    {
298 84
        $this->setParentId($value ? $value->getKey() : null)
299 84
            ->setRelation('parent', $value);
300
301 84
        return $this;
302
    }
303
304
    /**
305
     * Set the value of model's parent id key.
306
     *
307
     * Behind the scenes node is appended to found parent node.
308
     *
309
     * @param  int  $value
310
     *
311
     * @throws \Exception If parent node doesn't exists
312
     */
313 18
    public function setParentIdAttribute($value)
314
    {
315 18
        if ($this->getParentId() == $value) return;
316
317 18
        if ($value) {
318
            /** @var \Arcanedev\LaravelNestedSet\Contracts\Nodeable $node */
319 18
            $node = $this->newScopedQuery()->findOrFail($value);
320
321 18
            $this->appendToNode($node);
322 6
        } else {
323 6
            $this->makeRoot();
324
        }
325 18
    }
326
327
    /**
328
     * Get the boundaries.
329
     *
330
     * @return array
331
     */
332 72
    public function getBounds()
333
    {
334 72
        return [$this->getLft(), $this->getRgt()];
335
    }
336
337
    /**
338
     * Get the scope attributes.
339
     *
340
     * @return array
341
     */
342 246
    protected function getScopeAttributes()
343
    {
344 246
        return null;
345
    }
346
347
    /**
348
     * Set the lft and rgt boundaries to null.
349
     *
350
     * @return self
351
     */
352 102
    protected function dirtyBounds()
353
    {
354 102
        $this->original[$this->getLftName()] = null;
355 102
        $this->original[$this->getRgtName()] = null;
356
357 102
        return $this;
358
    }
359
360
    /**
361
     * Returns node that is next to current node without constraining to siblings.
362
     * This can be either a next sibling or a next sibling of the parent node.
363
     *
364
     * @param  array  $columns
365
     *
366
     * @return self
367
     */
368
    public function getNextNode(array $columns = ['*'])
369
    {
370
        return $this->nextNodes()->defaultOrder()->first($columns);
371
    }
372
373
    /**
374
     * Returns node that is before current node without constraining to siblings.
375
     * This can be either a prev sibling or parent node.
376
     *
377
     * @param  array  $columns
378
     *
379
     * @return self
380
     */
381 6
    public function getPrevNode(array $columns = ['*'])
382
    {
383 6
        return $this->prevNodes()->defaultOrder('desc')->first($columns);
384
    }
385
386
    /**
387
     * Get the ancestors nodes.
388
     *
389
     * @param  array  $columns
390
     *
391
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
392
     */
393 12
    public function getAncestors(array $columns = ['*'])
394
    {
395 12
        return $this->newScopedQuery()
396 12
            ->defaultOrder()
397 12
            ->ancestorsOf($this, $columns);
398
    }
399
400
    /**
401
     * Get the descendants nodes.
402
     *
403
     * @param  array  $columns
404
     *
405
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
406
     */
407 18
    public function getDescendants(array $columns = ['*'])
408
    {
409 18
        return $this->descendants()->get($columns);
410
    }
411
412
    /**
413
     * Get the siblings nodes.
414
     *
415
     * @param  array  $columns
416
     *
417
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
418
     */
419 12
    public function getSiblings(array $columns = ['*'])
420
    {
421 12
        return $this->siblings()->get($columns);
422
    }
423
424
    /**
425
     * Get the next siblings nodes.
426
     *
427
     * @param  array  $columns
428
     *
429
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
430
     */
431 12
    public function getNextSiblings(array $columns = ['*'])
432
    {
433 12
        return $this->nextSiblings()->get($columns);
434
    }
435
436
    /**
437
     * Get the previous siblings nodes.
438
     *
439
     * @param  array  $columns
440
     *
441
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
442
     */
443 12
    public function getPrevSiblings(array $columns = ['*'])
444
    {
445 12
        return $this->prevSiblings()->get($columns);
446
    }
447
448
    /**
449
     * Get the next sibling node.
450
     *
451
     * @param  array  $columns
452
     *
453
     * @return self
454
     */
455 6
    public function getNextSibling(array $columns = ['*'])
456
    {
457 6
        return $this->nextSiblings()->defaultOrder()->first($columns);
458
    }
459
460
    /**
461
     * Get the previous sibling node.
462
     *
463
     * @param  array  $columns
464
     *
465
     * @return self
466
     */
467 6
    public function getPrevSibling(array $columns = ['*'])
468
    {
469 6
        return $this->prevSiblings()->defaultOrder('desc')->first($columns);
470
    }
471
472
    /**
473
     * Get node height (rgt - lft + 1).
474
     *
475
     * @return int
476
     */
477 54
    public function getNodeHeight()
478
    {
479 54
        if ( ! $this->exists) return 2;
480
481 6
        return $this->getRgt() - $this->getLft() + 1;
482
    }
483
484
    /**
485
     * Get number of descendant nodes.
486
     *
487
     * @return int
488
     */
489 6
    public function getDescendantCount()
490
    {
491 6
        return (int) ceil($this->getNodeHeight() / 2) - 1;
492
    }
493
494
    /**
495
     * Set raw node.
496
     *
497
     * @param  int  $lft
498
     * @param  int  $rgt
499
     * @param  int  $parentId
500
     *
501
     * @return self
502
     */
503 18
    public function rawNode($lft, $rgt, $parentId)
504
    {
505 18
        $this->setLft($lft)->setRgt($rgt)->setParentId($parentId);
506
507 18
        return $this->setNodeAction('raw');
508
    }
509
510
    /**
511
     * Set an action.
512
     *
513
     * @param  string  $action
514
     *
515
     * @return self
516
     */
517 180
    protected function setNodeAction($action)
518
    {
519 180
        $this->pending = func_get_args();
520 180
        unset($action);
521
522 180
        return $this;
523
    }
524
525
    /* ------------------------------------------------------------------------------------------------
526
     |  Other Functions
527
     | ------------------------------------------------------------------------------------------------
528
     */
529
    /**
530
     * Get the lower bound.
531
     *
532
     * @return int
533
     */
534 48
    protected function getLowerBound()
535
    {
536 48
        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...
537
    }
538
539
    /**
540
     * Call pending action.
541
     */
542 150
    protected function callPendingAction()
543
    {
544 150
        $this->moved = false;
545
546 150
        if ( ! $this->pending && ! $this->exists) {
547 30
            $this->makeRoot();
548 10
        }
549
550 150
        if ( ! $this->pending) return;
551
552 144
        $method        = 'action'.ucfirst(array_shift($this->pending));
553 144
        $parameters    = $this->pending;
554
555 144
        $this->pending = null;
556 144
        $this->moved   = call_user_func_array([$this, $method], $parameters);
557 144
    }
558
559
    /**
560
     * @return bool
561
     */
562 18
    protected function actionRaw()
563
    {
564 18
        return true;
565
    }
566
567
    /**
568
     * Make a root node.
569
     */
570 48
    protected function actionRoot()
571
    {
572
        // Simplest case that do not affect other nodes.
573 48
        if ( ! $this->exists) {
574 30
            $cut = $this->getLowerBound() + 1;
575
576 30
            $this->setLft($cut);
577 30
            $this->setRgt($cut + 1);
578
579 30
            return true;
580
        }
581
582 18
        if ($this->isRoot()) return false;
583
584
585
        // Reset parent object
586 18
        $this->setParent(null);
587
588 18
        return $this->insertAt($this->getLowerBound() + 1);
589
    }
590
591
    /**
592
     * Append or prepend a node to the parent.
593
     *
594
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
595
     * @param  bool                                            $prepend
596
     *
597
     * @return bool
598
     */
599 48
    protected function actionAppendOrPrepend(Nodeable $parent, $prepend = false)
600
    {
601 48
        $parent->refreshNode();
602
603 48
        $cut = $prepend ? $parent->getLft() + 1 : $parent->getRgt();
604
605 48
        if ( ! $this->insertAt($cut)) {
606
            return false;
607
        }
608
609 48
        $parent->refreshNode();
610
611 48
        return true;
612
    }
613
614
    /**
615
     * Insert node before or after another node.
616
     *
617
     * @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...
618
     * @param  bool  $after
619
     *
620
     * @return bool
621
     */
622 42
    protected function actionBeforeOrAfter(self $node, $after = false)
623
    {
624 42
        $node->refreshNode();
625
626 42
        return $this->insertAt($after ? $node->getRgt() + 1 : $node->getLft());
627
    }
628
629
    /**
630
     * Refresh node's crucial attributes.
631
     */
632 132
    public function refreshNode()
633
    {
634 132
        if ( ! $this->exists || static::$actionsPerformed === 0) return;
635
636 96
        $attributes = $this->newNestedSetQuery()->getNodeData($this->getKey());
637
638 96
        $this->attributes = array_merge($this->attributes, $attributes);
639 96
    }
640
641
    /**
642
     * Get query for siblings after the node.
643
     *
644
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
645
     */
646 36
    public function nextSiblings()
647
    {
648 36
        return $this->nextNodes()
649 36
            ->where($this->getParentIdName(), '=', $this->getParentId());
650
    }
651
652
    /**
653
     * Get query for siblings before the node.
654
     *
655
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
656
     */
657 24
    public function prevSiblings()
658
    {
659 24
        return $this->prevNodes()
660 24
            ->where($this->getParentIdName(), '=', $this->getParentId());
661
    }
662
663
    /**
664
     * Get query for nodes after current node.
665
     *
666
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
667
     */
668 42
    public function nextNodes()
669
    {
670 42
        return $this->newScopedQuery()
671 42
            ->where($this->getLftName(), '>', $this->getLft());
672
    }
673
674
    /**
675
     * Get query for nodes before current node in reversed order.
676
     *
677
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
678
     */
679 30
    public function prevNodes()
680
    {
681 30
        return $this->newScopedQuery()
682 30
            ->where($this->getLftName(), '<', $this->getLft());
683
    }
684
685
    /**
686
     * Get query for ancestors to the node not including the node itself.
687
     *
688
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
689
     */
690 6
    public function ancestors()
691
    {
692 6
        return $this->newScopedQuery()
693 6
            ->whereAncestorOf($this)->defaultOrder();
694
    }
695
696
    /**
697
     * Make this node a root node.
698
     *
699
     * @return self
700
     */
701 72
    public function makeRoot()
702
    {
703 72
        return $this->setNodeAction('root');
704
    }
705
706
    /**
707
     * Save node as root.
708
     *
709
     * @return bool
710
     */
711 12
    public function saveAsRoot()
712
    {
713 12
        if ($this->exists && $this->isRoot()) {
714
            return true;
715
        }
716
717 12
        return $this->makeRoot()->save();
718
    }
719
720
    /**
721
     * Append and save a node.
722
     *
723
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
724
     *
725
     * @return bool
726
     */
727 24
    public function appendNode(Nodeable $node)
728
    {
729 24
        return $node->appendToNode($this)->save();
730
    }
731
732
    /**
733
     * Prepend and save a node.
734
     *
735
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
736
     *
737
     * @return bool
738
     */
739 6
    public function prependNode(Nodeable $node)
740
    {
741 6
        return $node->prependToNode($this)->save();
742
    }
743
744
    /**
745
     * Append a node to the new parent.
746
     *
747
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
748
     *
749
     * @return self
750
     */
751 66
    public function appendToNode(Nodeable $parent)
752
    {
753 66
        return $this->appendOrPrependTo($parent);
754
    }
755
756
    /**
757
     * Prepend a node to the new parent.
758
     *
759
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
760
     *
761
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
762
     */
763 12
    public function prependToNode(Nodeable $parent)
764
    {
765 12
        return $this->appendOrPrependTo($parent, true);
766
    }
767
768
    /**
769
     * Append or prepend a node to parent.
770
     *
771
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $parent
772
     * @param  bool                                            $prepend
773
     *
774
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
775
     */
776 78
    public function appendOrPrependTo(Nodeable $parent, $prepend = false)
777
    {
778 78
        $this->assertNodeExists($parent)
779 72
             ->assertNotDescendant($parent);
780
781 60
        $this->setParent($parent)->dirtyBounds();
782
783 60
        return $this->setNodeAction('appendOrPrepend', $parent, $prepend);
784
    }
785
786
    /**
787
     * Insert self after a node.
788
     *
789
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
790
     *
791
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
792
     */
793 42
    public function afterNode(Nodeable $node)
794
    {
795 42
        return $this->beforeOrAfterNode($node, true);
796
    }
797
798
    /**
799
     * Insert self before node.
800
     *
801
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
802
     *
803
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
804
     */
805 12
    public function beforeNode(Nodeable $node)
806 1
    {
807 12
        return $this->beforeOrAfterNode($node);
808
    }
809
810
    /**
811
     * Set before or after a node.
812
     *
813
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
814
     * @param  bool                                            $after
815
     *
816
     * @return \Arcanedev\LaravelNestedSet\Contracts\Nodeable
817
     */
818 54
    public function beforeOrAfterNode(Nodeable $node, $after = false)
819
    {
820 54
        $this->assertNodeExists($node)->assertNotDescendant($node);
821
822 48
        if ( ! $this->isSiblingOf($node)) {
823 18
            $this->setParent($node->getRelationValue('parent'));
824 6
        }
825
826 48
        $this->dirtyBounds();
827
828 48
        return $this->setNodeAction('beforeOrAfter', $node, $after);
829
    }
830
831
    /**
832
     * Insert after a node and save.
833
     *
834
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
835
     *
836
     * @return bool
837
     */
838 24
    public function insertAfterNode(Nodeable $node)
839
    {
840 24
        return $this->afterNode($node)->save();
841
    }
842
843
    /**
844
     * Insert self before a node and save.
845
     *
846
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
847
     *
848
     * @return bool
849
     */
850 6
    public function insertBeforeNode(Nodeable $node)
851
    {
852 6
        if ( ! $this->beforeNode($node)->save()) return false;
853
854
        // We'll update the target node since it will be moved
855 6
        $node->refreshNode();
856
857 6
        return true;
858
    }
859
860
    /**
861
     * Move node up given amount of positions.
862
     *
863
     * @param  int  $amount
864
     *
865
     * @return bool
866
     */
867 6
    public function up($amount = 1)
868
    {
869 6
        $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...
870 6
                        ->defaultOrder('desc')
871 6
                        ->skip($amount - 1)
872 6
                        ->first();
873
874 6
        if ( ! $sibling) return false;
875
876 6
        return $this->insertBeforeNode($sibling);
877
    }
878
879
    /**
880
     * Move node down given amount of positions.
881
     *
882
     * @param  int  $amount
883
     *
884
     * @return bool
885
     */
886 24
    public function down($amount = 1)
887
    {
888 24
        $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...
889 24
                        ->defaultOrder()
890 24
                        ->skip($amount - 1)
891 24
                        ->first();
892
893 24
        if ( ! $sibling) return false;
894
895 24
        return $this->insertAfterNode($sibling);
896
    }
897
898
    /**
899
     * Insert node at specific position.
900
     *
901
     * @param  int  $position
902
     *
903
     * @return bool
904
     */
905 102
    protected function insertAt($position)
906
    {
907 102
        ++static::$actionsPerformed;
908
909 102
        $result = $this->exists
910 74
            ? $this->moveNode($position)
911 102
            : $this->insertNode($position);
912
913 102
        return $result;
914
    }
915
916
    /**
917
     * Move a node to the new position.
918
     *
919
     * @param  int  $position
920
     *
921
     * @return int
922
     */
923 60
    protected function moveNode($position)
924
    {
925 60
        $updated = $this->newNestedSetQuery()
926 60
                        ->moveNode($this->getKey(), $position) > 0;
927
928 60
        if ($updated) $this->refreshNode();
929
930 60
        return $updated;
931
    }
932
933
    /**
934
     * Insert new node at specified position.
935
     *
936
     * @param  int  $position
937
     *
938
     * @return bool
939
     */
940 48
    protected function insertNode($position)
941
    {
942 48
        $this->newNestedSetQuery()->makeGap($position, 2);
943
944 48
        $height = $this->getNodeHeight();
945
946 48
        $this->setLft($position);
947 48
        $this->setRgt($position + $height - 1);
948
949 48
        return true;
950
    }
951
952
    /**
953
     * Update the tree when the node is removed physically.
954
     */
955 30
    protected function deleteDescendants()
956
    {
957 30
        $lft = $this->getLft();
958 30
        $rgt = $this->getRgt();
959
960 30
        $method = ($this->usesSoftDelete() && $this->forceDeleting)
961 22
            ? 'forceDelete'
962 30
            : 'delete';
963
964 30
        $this->descendants()->{$method}();
965
966 30
        if ($this->hardDeleting()) {
967 24
            $height = $rgt - $lft + 1;
968
969 24
            $this->newNestedSetQuery()->makeGap($rgt + 1, -$height);
970
971
            // In case if user wants to re-create the node
972 24
            $this->makeRoot();
973
974 24
            static::$actionsPerformed++;
975 8
        }
976 30
    }
977
978
    /**
979
     * Restore the descendants.
980
     *
981
     * @param  \Carbon\Carbon  $deletedAt
982
     */
983 6
    protected function restoreDescendants($deletedAt)
984
    {
985 6
        $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...
986 6
            ->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...
987 6
            ->applyScopes()
988 6
            ->restore();
989 6
    }
990
991
    /**
992
     * Get a new base query that includes deleted nodes.
993
     *
994
     * @param  string|null $table
995
     *
996
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
997
     */
998 180
    public function newNestedSetQuery($table = null)
999
    {
1000 180
        $builder = $this->usesSoftDelete()
1001 156
            ? $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...
1002 180
            : $this->newQuery();
1003
1004 180
        return $this->applyNestedSetScope($builder, $table);
1005
    }
1006
1007
    /**
1008
     * Create a new scoped query.
1009
     *
1010
     * @param  string|null  $table
1011
     *
1012
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1013
     */
1014 210
    public function newScopedQuery($table = null)
1015
    {
1016 210
        return $this->applyNestedSetScope($this->newQuery(), $table);
1017
    }
1018
1019
    /**
1020
     * Apply the nested set scope.
1021
     *
1022
     * @param  \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder  $query
1023
     * @param  string                                                                    $table
1024
     *
1025
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder|\Illuminate\Database\Query\Builder
1026
     */
1027 312
    public function applyNestedSetScope($query, $table = null)
1028
    {
1029 312
        if ( ! $scoped = $this->getScopeAttributes()) {
1030 246
            return $query;
1031
        }
1032
1033 66
        if ($table === null) {
1034 66
            $table = $this->getTable();
1035 22
        }
1036
1037 66
        foreach ($scoped as $attribute) {
1038 66
            $query->where("$table.$attribute", '=', $this->getAttributeValue($attribute));
1039 22
        }
1040
1041 66
        return $query;
1042
    }
1043
1044
    /**
1045
     * @param  array  $attributes
1046
     *
1047
     * @return self
1048
     */
1049 18
    public static function scoped(array $attributes)
1050
    {
1051 18
        $instance = new static;
1052
1053 18
        $instance->setRawAttributes($attributes);
1054
1055 18
        return $instance->newScopedQuery();
1056
    }
1057
1058
    /**
1059
     * Save a new model and return the instance.
1060
     *
1061
     * Use `children` key on `$attributes` to create child nodes.
1062
     *
1063
     * @param  array  $attributes
1064
     * @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...
1065
     *
1066
     * @return self
1067
     */
1068 18
    public static function create(array $attributes = [], self $parent = null)
1069
    {
1070 18
        $children = array_pull($attributes, 'children');
1071 18
        $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...
1072
1073 18
        if ($parent) {
1074 6
            $instance->appendToNode($parent);
1075 2
        }
1076
1077 18
        $instance->save();
1078
1079
        // Now create children
1080 18
        $relation = new EloquentCollection;
1081
1082 18
        foreach ((array) $children as $child) {
1083 6
            $relation->add($child = static::create($child, $instance));
1084
1085 6
            $child->setRelation('parent', $instance);
1086 6
        }
1087
1088 18
        return $instance->setRelation('children', $relation);
1089
    }
1090
1091
    /* ------------------------------------------------------------------------------------------------
1092
     |  Check Functions
1093
     | ------------------------------------------------------------------------------------------------
1094
     */
1095
    /**
1096
     * Get whether node is root.
1097
     *
1098
     * @return bool
1099
     */
1100 24
    public function isRoot()
1101
    {
1102 24
        return is_null($this->getParentId());
1103
    }
1104
1105
    /**
1106
     * Get whether a node is a descendant of other node.
1107
     *
1108
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1109
     *
1110
     * @return bool
1111
     */
1112 114
    public function isDescendantOf(Nodeable $node)
1113
    {
1114
        return (
1115 114
            $this->getLft() > $node->getLft() &&
1116 110
            $this->getLft() < $node->getRgt()
1117 38
        );
1118
    }
1119
1120
    /**
1121
     * Get whether the node is immediate children of other node.
1122
     *
1123
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1124
     *
1125
     * @return bool
1126
     */
1127 6
    public function isChildOf(Nodeable $node)
1128
    {
1129 6
        return $this->getParentId() == $node->getKey();
1130
    }
1131
1132
    /**
1133
     * Get whether the node is a sibling of another node.
1134
     *
1135
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1136
     *
1137
     * @return bool
1138
     */
1139 48
    public function isSiblingOf(Nodeable $node)
1140
    {
1141 48
        return $this->getParentId() == $node->getParentId();
1142
    }
1143
1144
    /**
1145
     * Get whether the node is an ancestor of other node, including immediate parent.
1146
     *
1147
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1148
     *
1149
     * @return bool
1150
     */
1151 6
    public function isAncestorOf(Nodeable $node)
1152
    {
1153
        /** @var \Arcanedev\LaravelNestedSet\Contracts\Nodeable $this */
1154 6
        return $node->isDescendantOf($this);
1155
    }
1156
1157
    /**
1158
     * Get whether the node has moved since last save.
1159
     *
1160
     * @return bool
1161
     */
1162 36
    public function hasMoved()
1163
    {
1164 36
        return $this->moved;
1165
    }
1166
1167
    /* ------------------------------------------------------------------------------------------------
1168
     |  Assertion Functions
1169
     | ------------------------------------------------------------------------------------------------
1170
     */
1171
    /**
1172
     * Assert that the node is not a descendant.
1173
     *
1174
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1175
     *
1176
     * @return self
1177
     *
1178
     * @throws \LogicException
1179
     */
1180 120
    protected function assertNotDescendant(Nodeable $node)
1181
    {
1182
        /** @var \Arcanedev\LaravelNestedSet\Contracts\Nodeable $this */
1183 120
        if ($node == $this || $node->isDescendantOf($this)) {
1184 18
            throw new LogicException('Node must not be a descendant.');
1185
        }
1186
1187 102
        return $this;
1188
    }
1189
1190
    /**
1191
     * Assert node exists.
1192
     *
1193
     * @param  \Arcanedev\LaravelNestedSet\Contracts\Nodeable  $node
1194
     *
1195
     * @return self
1196
     *
1197
     * @throws \LogicException
1198
     */
1199 126
    protected function assertNodeExists(Nodeable $node)
1200
    {
1201 126
        if ( ! $node->getLft() || ! $node->getRgt()) {
1202 6
            throw new LogicException('Node must exists.');
1203
        }
1204
1205 120
        return $this;
1206
    }
1207
}
1208