Completed
Push — master ( 9383fe...482732 )
by ARCANEDEV
08:20
created

NodeTrait::getRgtName()   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 1
Bugs 0 Features 0
Metric Value
c 1
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\Traits;
2
3
use Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation;
4
use Arcanedev\LaravelNestedSet\Eloquent\Collection;
5
use Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder;
6
use Arcanedev\LaravelNestedSet\Utilities\NestedSet;
7
use Exception;
8
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
9
use LogicException;
10
11
/**
12
 * Class     NodeTrait
13
 *
14
 * @package  Arcanedev\Taxonomies\Traits
15
 * @author   ARCANEDEV <[email protected]>
16
 *
17
 * @property  array  $attributes
18
 * @property  array  $original
19
 * @property  bool   $exists
20
 * @property  bool   $forceDeleting
21
 *
22
 * @method  static  bool   isBroken()
23
 * @method  static  array  getNodeData($id, $required = false)
24
 * @method  static  array  getPlainNodeData($id, $required = false)
25
 *
26
 * @method  \Illuminate\Database\Eloquent\Relations\BelongsTo  belongsTo(string $related, string $foreignKey = null, string $otherKey = null, string $relation = null)
27
 * @method  \Illuminate\Database\Eloquent\Relations\HasMany    hasMany(string $related, string $foreignKey = null, string $localKey = null)
28
 */
29
trait NodeTrait
30
{
31
    /* ------------------------------------------------------------------------------------------------
32
     |  Properties
33
     | ------------------------------------------------------------------------------------------------
34
     */
35
    /**
36
     * Pending operation.
37
     *
38
     * @var array
39
     */
40
    protected $pending;
41
42
    /**
43
     * Whether the node has moved since last save.
44
     *
45
     * @var bool
46
     */
47
    protected $moved = false;
48
49
    /**
50
     * @var \Carbon\Carbon
51
     */
52
    public static $deletedAt;
53
54
    /**
55
     * Keep track of the number of performed operations.
56
     *
57
     * @var int
58
     */
59
    public static $actionsPerformed = 0;
60
61
    /* ------------------------------------------------------------------------------------------------
62
     |  Boot Function
63
     | ------------------------------------------------------------------------------------------------
64
     */
65
    /**
66
     * Sign on model events.
67
     */
68 280
    public static function bootNodeTrait()
69
    {
70
        static::saving(function (self $model) {
71 92
            $model->getConnection()->beginTransaction();
72
73 92
            return $model->callPendingAction();
74 280
        });
75
76
        static::saved(function (self $model) {
77 88
            $model->getConnection()->commit();
78 280
        });
79
80
        static::deleting(function (self $model) {
0 ignored issues
show
Bug introduced by
The method deleting() does not exist on Arcanedev\LaravelNestedSet\Traits\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...
81 19
            $model->getConnection()->beginTransaction();
82
83
            // We will need fresh data to delete node safely
84 19
            $model->refreshNode();
85 280
        });
86
87
        static::deleted(function (self $model) {
0 ignored issues
show
Bug introduced by
The method deleted() does not exist on Arcanedev\LaravelNestedSet\Traits\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...
88 19
            $model->deleteDescendants();
89
90 19
            $model->getConnection()->commit();
91 280
        });
92
93 280
        if (static::usesSoftDelete()) {
94
            static::restoring(function (self $model) {
95 4
                $model->getConnection()->beginTransaction();
96
97 4
                static::$deletedAt = $model->{$model->getDeletedAtColumn()};
98 236
            });
99
100 236
            static::restored(function (self $model) {
0 ignored issues
show
Bug introduced by
The method restored() does not exist on Arcanedev\LaravelNestedSet\Traits\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...
101 4
                $model->restoreDescendants(static::$deletedAt);
102
103 4
                $model->getConnection()->commit();
104 236
            });
105 177
        }
106 280
    }
107
108
    /* ------------------------------------------------------------------------------------------------
109
     |  Eloquent Functions
110
     | ------------------------------------------------------------------------------------------------
111
     */
112
    /**
113
     * Get the database connection for the model.
114
     *
115
     * @return \Illuminate\Database\Connection
116
     */
117
    abstract public function getConnection();
118
119
    /**
120
     * Get the table associated with the model.
121
     *
122
     * @return string
123
     */
124
    abstract public function getTable();
125
126
    /**
127
     * Get the value of the model's primary key.
128
     *
129
     * @return mixed
130
     */
131
    abstract public function getKey();
132
133
    /**
134
     * Get the primary key for the model.
135
     *
136
     * @return string
137
     */
138
    abstract public function getKeyName();
139
140
    /**
141
     * Get a plain attribute (not a relationship).
142
     *
143
     * @param  string  $key
144
     *
145
     * @return mixed
146
     */
147
    abstract public function getAttributeValue($key);
148
149
    /**
150
     * Set the array of model attributes. No checking is done.
151
     *
152
     * @param  array  $attributes
153
     * @param  bool   $sync
154
     *
155
     * @return self
156
     */
157
    abstract public function setRawAttributes(array $attributes, $sync = false);
158
159
    /**
160
     * Set the specific relationship in the model.
161
     *
162
     * @param  string  $relation
163
     * @param  mixed   $value
164
     *
165
     * @return self
166
     */
167
    abstract public function setRelation($relation, $value);
168
169
    /**
170
     * Get a relationship.
171
     *
172
     * @param  string  $key
173
     *
174
     * @return mixed
175
     */
176
    abstract public function getRelationValue($key);
177
178
    /**
179
     * Determine if the model or given attribute(s) have been modified.
180
     *
181
     * @param  array|string|null  $attributes
182
     *
183
     * @return bool
184
     */
185
    abstract public function isDirty($attributes = null);
186
187
    /**
188
     * Fill the model with an array of attributes.
189
     *
190
     * @param  array  $attributes
191
     *
192
     * @return self
193
     *
194
     * @throws \Illuminate\Database\Eloquent\MassAssignmentException
195
     */
196
    abstract public function fill(array $attributes);
197
198
    /**
199
     * Save the model to the database.
200
     *
201
     * @param  array  $options
202
     *
203
     * @return bool
204
     */
205
    abstract public function save(array $options = []);
206
207
    /**
208
     * Get a new query builder for the model's table.
209
     *
210
     * @return \Illuminate\Database\Eloquent\Builder
211
     */
212
    abstract public function newQuery();
213
214
    /* ------------------------------------------------------------------------------------------------
215
     |  Relationships
216
     | ------------------------------------------------------------------------------------------------
217
     */
218
    /**
219
     * Relation to the parent.
220
     *
221
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
222
     */
223 16
    public function parent()
224
    {
225 16
        return $this->belongsTo(get_class($this), $this->getParentIdName())
226 16
            ->setModel($this);
227
    }
228
229
    /**
230
     * Relation to children.
231
     *
232
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
233
     */
234 16
    public function children()
235
    {
236 16
        return $this->hasMany(get_class($this), $this->getParentIdName())
237 16
            ->setModel($this);
238
    }
239
240
    /**
241
     * Get query for descendants of the node.
242
     *
243
     * @return \Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation
244
     */
245 43
    public function descendants()
246
    {
247 43
        return new DescendantsRelation($this->newScopedQuery(), $this);
248
    }
249
250
    /**
251
     * Get query for siblings of the node.
252
     *
253
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
254
     */
255 8
    public function siblings()
256
    {
257 8
        return $this->newScopedQuery()
258 8
            ->where($this->getKeyName(), '<>', $this->getKey())
259 8
            ->where($this->getParentIdName(), '=', $this->getParentId());
260
    }
261
262
    /* ------------------------------------------------------------------------------------------------
263
     |  Getters & Setters
264
     | ------------------------------------------------------------------------------------------------
265
     */
266
    /**
267
     * Get the lft key name.
268
     *
269
     * @return string
270
     */
271 208
    public function getLftName()
272
    {
273 208
        return NestedSet::LFT;
274
    }
275
276
    /**
277
     * Get the rgt key name.
278
     *
279
     * @return string
280
     */
281 180
    public function getRgtName()
282
    {
283 180
        return NestedSet::RGT;
284
    }
285
286
    /**
287
     * Get the parent id key name.
288
     *
289
     * @return string
290
     */
291 140
    public function getParentIdName()
292
    {
293 140
        return NestedSet::PARENT_ID;
294
    }
295
296
    /**
297
     * Get the value of the model's lft key.
298
     *
299
     * @return int
300
     */
301 177
    public function getLft()
302
    {
303 177
        return $this->getAttributeValue($this->getLftName());
304
    }
305
306
    /**
307
     * Set the value of the model's lft key.
308
     *
309
     * @param  int  $value
310
     *
311
     * @return self
312
     */
313 91
    public function setLft($value)
314
    {
315 91
        $this->attributes[$this->getLftName()] = $value;
316
317 91
        return $this;
318
    }
319
320
    /**
321
     * Get the value of the model's rgt key.
322
     *
323
     * @return int
324
     */
325 119
    public function getRgt()
326
    {
327 119
        return $this->getAttributeValue($this->getRgtName());
328
    }
329
330
    /**
331
     * Set the value of the model's rgt key.
332
     *
333
     * @param  int  $value
334
     *
335
     * @return self
336
     */
337 91
    public function setRgt($value)
338
    {
339 91
        $this->attributes[$this->getRgtName()] = $value;
340
341 91
        return $this;
342
    }
343
344
    /**
345
     * Get the value of the model's parent id key.
346
     *
347
     * @return int
348
     */
349 96
    public function getParentId()
350
    {
351 96
        return $this->getAttributeValue($this->getParentIdName());
352
    }
353
354
    /**
355
     * Set the value of the model's parent id key.
356
     *
357
     * @param  int  $value
358
     *
359
     * @return self
360
     */
361 63
    public function setParentId($value)
362
    {
363 63
        $this->attributes[$this->getParentIdName()] = $value;
364
365 63
        return $this;
366
    }
367
368
    /**
369
     * Apply parent model.
370
     *
371
     * @param  \Illuminate\Database\Eloquent\Model|null  $value
372
     *
373
     * @return self
374
     */
375 55
    protected function setParent($value)
376
    {
377 55
        $this->setParentId($value ? $value->getKey() : null)
378 55
            ->setRelation('parent', $value);
379
380 55
        return $this;
381
    }
382
383
    /**
384
     * Set the value of model's parent id key.
385
     *
386
     * Behind the scenes node is appended to found parent node.
387
     *
388
     * @param  int  $value
389
     *
390
     * @throws Exception If parent node doesn't exists
391
     */
392 12
    public function setParentIdAttribute($value)
393
    {
394 12
        if ($this->getParentId() == $value) return;
395
396 12
        if ($value) {
397
            /** @var self $node */
398 12
            $node = $this->newScopedQuery()->findOrFail($value);
399
400 12
            $this->appendToNode($node);
401 9
        } else {
402 4
            $this->makeRoot();
403
        }
404 12
    }
405
406
    /**
407
     * Get the boundaries.
408
     *
409
     * @return array
410
     */
411 43
    public function getBounds()
412
    {
413 43
        return [$this->getLft(), $this->getRgt()];
414
    }
415
416
    /**
417
     * Set the lft and rgt boundaries to null.
418
     *
419
     * @return self
420
     */
421 68
    protected function dirtyBounds()
422
    {
423 68
        return $this->setLft(null)->setRgt(null);
424
    }
425
426
    /**
427
     * Returns node that is next to current node without constraining to siblings.
428
     * This can be either a next sibling or a next sibling of the parent node.
429
     *
430
     * @param  array  $columns
431
     *
432
     * @return self
433
     */
434
    public function getNextNode(array $columns = ['*'])
435
    {
436
        return $this->nextNodes()->defaultOrder()->first($columns);
437
    }
438
439
    /**
440
     * Returns node that is before current node without constraining to siblings.
441
     * This can be either a prev sibling or parent node.
442
     *
443
     * @param  array  $columns
444
     *
445
     * @return self
446
     */
447 4
    public function getPrevNode(array $columns = ['*'])
448
    {
449 4
        return $this->prevNodes()->defaultOrder('desc')->first($columns);
450
    }
451
452
    /**
453
     * Get the ancestors nodes.
454
     *
455
     * @param  array  $columns
456
     *
457
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
458
     */
459 8
    public function getAncestors(array $columns = ['*'])
460
    {
461 8
        return $this->newScopedQuery()
462 8
            ->defaultOrder()
463 8
            ->ancestorsOf($this, $columns);
464
    }
465
466
    /**
467
     * Get the descendants nodes.
468
     *
469
     * @param  array  $columns
470
     *
471
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
472
     */
473 12
    public function getDescendants(array $columns = ['*'])
474
    {
475 12
        return $this->descendants()->get($columns);
476
    }
477
478
    /**
479
     * Get the siblings nodes.
480
     *
481
     * @param  array  $columns
482
     *
483
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
484
     */
485 8
    public function getSiblings(array $columns = ['*'])
486
    {
487 8
        return $this->siblings()->get($columns);
488
    }
489
490
    /**
491
     * Get the next siblings nodes.
492
     *
493
     * @param  array  $columns
494
     *
495
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
496
     */
497 8
    public function getNextSiblings(array $columns = ['*'])
498
    {
499 8
        return $this->nextSiblings()->get($columns);
500
    }
501
502
    /**
503
     * Get the previous siblings nodes.
504
     *
505
     * @param  array  $columns
506
     *
507
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
508
     */
509 8
    public function getPrevSiblings(array $columns = ['*'])
510
    {
511 8
        return $this->prevSiblings()->get($columns);
512
    }
513
514
    /**
515
     * Get the next sibling node.
516
     *
517
     * @param  array  $columns
518
     *
519
     * @return self
520
     */
521 4
    public function getNextSibling(array $columns = ['*'])
522
    {
523 4
        return $this->nextSiblings()->defaultOrder()->first($columns);
524
    }
525
526
    /**
527
     * Get the previous sibling node.
528
     *
529
     * @param  array  $columns
530
     *
531
     * @return self
532
     */
533 4
    public function getPrevSibling(array $columns = ['*'])
534
    {
535 4
        return $this->prevSiblings()->defaultOrder('desc')->first($columns);
536
    }
537
538
    /**
539
     * Get node height (rgt - lft + 1).
540
     *
541
     * @return int
542
     */
543 36
    public function getNodeHeight()
544
    {
545 36
        if ( ! $this->exists) return 2;
546
547 4
        return $this->getRgt() - $this->getLft() + 1;
548
    }
549
550
    /**
551
     * Get number of descendant nodes.
552
     *
553
     * @return int
554
     */
555 4
    public function getDescendantCount()
556
    {
557 4
        return (int) ceil($this->getNodeHeight() / 2) - 1;
558
    }
559
560
    /**
561
     * Get an attribute array of all arrayable relations.
562
     *
563
     * @return array
564
     */
565
    protected function getArrayableRelations()
566
    {
567
        $result = parent::getArrayableRelations();
568
569
        unset($result['parent']);
570
571
        return $result;
572
    }
573
574
    /**
575
     * Set an action.
576
     *
577
     * @param  string  $action
578
     *
579
     * @return $this
580
     */
581 114
    protected function setNodeAction($action)
0 ignored issues
show
Unused Code introduced by
The parameter $action 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...
582
    {
583 114
        $this->pending = func_get_args();
584
585 114
        return $this;
586
    }
587
588
    /**
589
     * @return bool
590
     */
591 8
    protected function actionRaw()
592
    {
593 8
        return true;
594
    }
595
596
    /**
597
     * Call pending action.
598
     *
599
     * @return null|false
600
     */
601 92
    protected function callPendingAction()
602
    {
603 92
        $this->moved = false;
604
605 92
        if ( ! $this->pending && ! $this->exists) {
606 19
            $this->makeRoot();
607 15
        }
608
609 92
        if ( ! $this->pending) return;
610
611 88
        $method        = 'action'.ucfirst(array_shift($this->pending));
612 88
        $parameters    = $this->pending;
613
614 88
        $this->pending = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $pending.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
615 88
        $this->moved   = call_user_func_array([$this, $method], $parameters);
616 88
    }
617
618
    /* ------------------------------------------------------------------------------------------------
619
     |  Other Functions
620
     | ------------------------------------------------------------------------------------------------
621
     */
622
    /**
623
     * Make a root node.
624
     */
625 30
    protected function actionRoot()
626
    {
627
        // Simplest case that do not affect other nodes.
628 30
        if ( ! $this->exists) {
629 19
            $cut = $this->getLowerBound() + 1;
630
631 19
            $this->setLft($cut);
632 19
            $this->setRgt($cut + 1);
633
634 19
            return true;
635
        }
636
637 11
        if ($this->isRoot()) return false;
638
639
640
        // Reset parent object
641 11
        $this->setParent(null);
642
643 11
        return $this->insertAt($this->getLowerBound() + 1);
644
    }
645
646
    /**
647
     * Get the lower bound.
648
     *
649
     * @return int
650
     */
651 30
    protected function getLowerBound()
652
    {
653 30
        return (int) $this->newNestedSetQuery()->max($this->getRgtName());
654
    }
655
656
    /**
657
     * Append or prepend a node to the parent.
658
     *
659
     * @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...
660
     * @param  bool  $prepend
661
     *
662
     * @return bool
663
     */
664 32
    protected function actionAppendOrPrepend(self $parent, $prepend = false)
665
    {
666 32
        $parent->refreshNode();
667
668 32
        $cut = $prepend ? $parent->getLft() + 1 : $parent->getRgt();
669
670 32
        if ( ! $this->insertAt($cut)) {
671
            return false;
672
        }
673
674 32
        $parent->refreshNode();
675
676 32
        return true;
677
    }
678
679
    /**
680
     * Insert node before or after another node.
681
     *
682
     * @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...
683
     * @param  bool  $after
684
     *
685
     * @return bool
686
     */
687 26
    protected function actionBeforeOrAfter(self $node, $after = false)
688
    {
689 26
        $node->refreshNode();
690
691 26
        return $this->insertAt($after ? $node->getRgt() + 1 : $node->getLft());
692
    }
693
694
    /**
695
     * Refresh node's crucial attributes.
696
     */
697 84
    public function refreshNode()
698
    {
699 84
        if ( ! $this->exists || static::$actionsPerformed === 0) return;
700
701 61
        $attributes = $this->newNestedSetQuery()->getNodeData($this->getKey());
702
703 61
        $this->attributes = array_merge($this->attributes, $attributes);
704 61
        $this->original   = array_merge($this->original,   $attributes);
705 61
    }
706
707
    /**
708
     * Get query for siblings after the node.
709
     *
710
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
711
     */
712 24
    public function nextSiblings()
713
    {
714 24
        return $this->nextNodes()
715 24
            ->where($this->getParentIdName(), '=', $this->getParentId());
716
    }
717
718
    /**
719
     * Get query for siblings before the node.
720
     *
721
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
722
     */
723 16
    public function prevSiblings()
724
    {
725 16
        return $this->prevNodes()
726 16
            ->where($this->getParentIdName(), '=', $this->getParentId());
727
    }
728
729
    /**
730
     * Get query for nodes after current node.
731
     *
732
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
733
     */
734 28
    public function nextNodes()
735
    {
736 28
        return $this->newScopedQuery()
737 28
            ->where($this->getLftName(), '>', $this->getLft());
738
    }
739
740
    /**
741
     * Get query for nodes before current node in reversed order.
742
     *
743
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
744
     */
745 20
    public function prevNodes()
746
    {
747 20
        return $this->newScopedQuery()
748 20
            ->where($this->getLftName(), '<', $this->getLft());
749
    }
750
751
    /**
752
     * Get query for ancestors to the node not including the node itself.
753
     *
754
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
755
     */
756 4
    public function ancestors()
757
    {
758 4
        return $this->newScopedQuery()
759 4
            ->whereAncestorOf($this)->defaultOrder();
760
    }
761
762
    /**
763
     * Make this node a root node.
764
     *
765
     * @return self
766
     */
767 46
    public function makeRoot()
768
    {
769 46
        return $this->setNodeAction('root');
770
    }
771
772
    /**
773
     * Save node as root.
774
     *
775
     * @return bool
776
     */
777 8
    public function saveAsRoot()
778
    {
779 8
        if ($this->exists && $this->isRoot()) {
780
            return true;
781
        }
782
783 8
        return $this->makeRoot()->save();
784
    }
785
786
    /**
787
     * Append and save a node.
788
     *
789
     * @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...
790
     *
791
     * @return bool
792
     */
793 16
    public function appendNode(self $node)
794
    {
795 16
        return $node->appendToNode($this)->save();
796
    }
797
798
    /**
799
     * Prepend and save a node.
800
     *
801
     * @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...
802
     *
803
     * @return bool
804
     */
805 4
    public function prependNode(self $node)
806
    {
807 4
        return $node->prependToNode($this)->save();
808
    }
809
810
    /**
811
     * Append a node to the new parent.
812
     *
813
     * @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...
814
     *
815
     * @return self
816
     */
817 40
    public function appendToNode(self $parent)
818
    {
819 40
        return $this->appendOrPrependTo($parent);
820
    }
821
822
    /**
823
     * Prepend a node to the new parent.
824
     *
825
     * @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...
826
     *
827
     * @return self
828
     */
829 4
    public function prependToNode(self $parent)
830
    {
831 4
        return $this->appendOrPrependTo($parent, true);
832
    }
833
834
    /**
835
     * @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...
836
     * @param  bool  $prepend
837
     *
838
     * @return self
839
     */
840 44
    public function appendOrPrependTo(self $parent, $prepend = false)
841
    {
842 44
        $this->assertNodeExists($parent)
843 44
             ->assertNotDescendant($parent);
844
845 40
        $this->setParent($parent)->dirtyBounds();
846
847 40
        return $this->setNodeAction('appendOrPrepend', $parent, $prepend);
848
    }
849
850
    /**
851
     * Insert self after a node.
852
     *
853
     * @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...
854
     *
855
     * @return self
856
     */
857 28
    public function afterNode(self $node)
858
    {
859 28
        return $this->beforeOrAfterNode($node, true);
860
    }
861
862
    /**
863
     * Insert self before node.
864
     *
865
     * @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...
866
     *
867
     * @return self
868
     */
869 8
    public function beforeNode(self $node)
870
    {
871 8
        return $this->beforeOrAfterNode($node);
872 3
    }
873
874
    /**
875
     * @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...
876
     * @param  bool  $after
877
     *
878
     * @return self
879
     */
880 36
    public function beforeOrAfterNode(self $node, $after = false)
881
    {
882 36
        $this->assertNodeExists($node)->assertNotDescendant($node);
883
884 32
        if ( ! $this->isSiblingOf($node)) {
885 12
            $this->setParent($node->getRelationValue('parent'));
886 9
        }
887
888 32
        $this->dirtyBounds();
889
890 32
        return $this->setNodeAction('beforeOrAfter', $node, $after);
891
    }
892
893
    /**
894
     * Insert self after a node and save.
895
     *
896
     * @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...
897
     *
898
     * @return bool
899
     */
900 16
    public function insertAfterNode(self $node)
901
    {
902 16
        return $this->afterNode($node)->save();
903
    }
904
905
    /**
906
     * Insert self before a node and save.
907
     *
908
     * @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...
909
     *
910
     * @return bool
911
     */
912 4
    public function insertBeforeNode(self $node)
913
    {
914 4
        if ( ! $this->beforeNode($node)->save()) return false;
915
916
        // We'll update the target node since it will be moved
917 4
        $node->refreshNode();
918
919 4
        return true;
920
    }
921
922
    /**
923
     * @param  int  $lft
924
     * @param  int  $rgt
925
     * @param  int  $parentId
926
     *
927
     * @return self
928
     */
929 8
    public function rawNode($lft, $rgt, $parentId)
930
    {
931 8
        $this->setLft($lft)->setRgt($rgt)->setParentId($parentId);
932
933 8
        return $this->setNodeAction('raw');
934
    }
935
936
    /**
937
     * Move node up given amount of positions.
938
     *
939
     * @param  int  $amount
940
     *
941
     * @return bool
942
     */
943 4
    public function up($amount = 1)
944
    {
945 4
        $sibling = $this->prevSiblings()
946 4
                        ->defaultOrder('desc')
947 4
                        ->skip($amount - 1)
948 4
                        ->first();
949
950 4
        if ( ! $sibling) return false;
951
952 4
        return $this->insertBeforeNode($sibling);
953
    }
954
955
    /**
956
     * Move node down given amount of positions.
957
     *
958
     * @param  int  $amount
959
     *
960
     * @return bool
961
     */
962 16
    public function down($amount = 1)
963
    {
964 16
        $sibling = $this->nextSiblings()
965 16
                        ->defaultOrder()
966 16
                        ->skip($amount - 1)
967 16
                        ->first();
968
969 16
        if ( ! $sibling) return false;
970
971 16
        return $this->insertAfterNode($sibling);
972
    }
973
974
    /**
975
     * Insert node at specific position.
976
     *
977
     * @param  int  $position
978
     *
979
     * @return bool
980
     */
981 65
    protected function insertAt($position)
982
    {
983 65
        ++static::$actionsPerformed;
984
985 65
        $result = $this->exists
986 58
            ? $this->moveNode($position)
987 65
            : $this->insertNode($position);
988
989 65
        return $result;
990
    }
991
992
    /**
993
     * Move a node to the new position.
994
     *
995
     * @param  int  $position
996
     *
997
     * @return int
998
     */
999 37
    protected function moveNode($position)
1000
    {
1001 37
        $updated = $this->newNestedSetQuery()
1002 37
                        ->moveNode($this->getKey(), $position) > 0;
1003
1004 37
        if ($updated) $this->refreshNode();
1005
1006 37
        return $updated;
1007
    }
1008
1009
    /**
1010
     * Insert new node at specified position.
1011
     *
1012
     * @param  int  $position
1013
     *
1014
     * @return bool
1015
     */
1016 32
    protected function insertNode($position)
1017
    {
1018 32
        $this->newNestedSetQuery()->makeGap($position, 2);
1019
1020 32
        $height = $this->getNodeHeight();
1021
1022 32
        $this->setLft($position);
1023 32
        $this->setRgt($position + $height - 1);
1024
1025 32
        return true;
1026
    }
1027
1028
    /**
1029
     * Update the tree when the node is removed physically.
1030
     */
1031 19
    protected function deleteDescendants()
1032
    {
1033 19
        $lft = $this->getLft();
1034 19
        $rgt = $this->getRgt();
1035
1036 19
        $method = ($this->usesSoftDelete() && $this->forceDeleting)
1037 18
            ? 'forceDelete'
1038 19
            : 'delete';
1039
1040 19
        $this->descendants()->{$method}();
1041
1042 19
        if ($this->hardDeleting()) {
1043 15
            $height = $rgt - $lft + 1;
1044
1045 15
            $this->newNestedSetQuery()->makeGap($rgt + 1, -$height);
1046
1047
            // In case if user wants to re-create the node
1048 15
            $this->makeRoot();
1049
1050 15
            static::$actionsPerformed++;
1051 12
        }
1052 19
    }
1053
1054
    /**
1055
     * Restore the descendants.
1056
     *
1057
     * @param  mixed  $deletedAt
1058
     */
1059 4
    protected function restoreDescendants($deletedAt)
1060
    {
1061 4
        $this->descendants()
1062 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...
1063 4
            ->applyScopes()
1064 4
            ->restore();
1065 4
    }
1066
1067
    /**
1068
     * Create a new Eloquent query builder for the model.
1069
     *
1070
     * @param  \Illuminate\Database\Query\Builder  $query
1071
     *
1072
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1073
     */
1074 280
    public function newEloquentBuilder($query)
1075
    {
1076 280
        return new QueryBuilder($query);
1077
    }
1078
1079
    /**
1080
     * Get a new base query that includes deleted nodes.
1081
     *
1082
     * @param  string|null $table
1083
     *
1084
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1085
     */
1086 111
    public function newNestedSetQuery($table = null)
1087
    {
1088 111
        $builder = $this->usesSoftDelete()
1089 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...
1090 111
            : $this->newQuery();
1091
1092 111
        return $this->applyNestedSetScope($builder, $table);
1093
    }
1094
1095
    /**
1096
     * @param  string|null  $table
1097
     *
1098
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1099
     */
1100 135
    public function newScopedQuery($table = null)
1101
    {
1102 135
        return $this->applyNestedSetScope($this->newQuery(), $table);
1103
    }
1104
1105
    /**
1106
     * @param  \Illuminate\Database\Eloquent\Builder  $query
1107
     * @param  string                                 $table
1108
     *
1109
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1110
     */
1111 201
    public function applyNestedSetScope($query, $table = null)
1112
    {
1113 201
        if ( ! $scoped = $this->getScopeAttributes()) {
1114 160
            return $query;
1115
        }
1116
1117 41
        if ( ! $table) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $table of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1118 41
            $table = $this->getTable();
1119 33
        }
1120
1121 41
        foreach ($scoped as $attribute) {
1122 41
            $query->where("$table.$attribute", '=', $this->getAttributeValue($attribute));
1123 33
        }
1124
1125 41
        return $query;
1126
    }
1127
1128
    /**
1129
     * @return array
1130
     */
1131 160
    protected function getScopeAttributes()
1132
    {
1133 160
        return null;
1134
    }
1135
1136
    /**
1137
     * @param  array  $attributes
1138
     *
1139
     * @return self
1140
     */
1141 12
    public static function scoped(array $attributes)
1142
    {
1143 12
        $instance = new static;
1144
1145 12
        $instance->setRawAttributes($attributes);
1146
1147 12
        return $instance->newScopedQuery();
1148
    }
1149
1150
    /**
1151
     * Create a new Eloquent Collection instance.
1152
     *
1153
     * @param  array  $models
1154
     *
1155
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
1156
     */
1157 227
    public function newCollection(array $models = [])
1158
    {
1159 227
        return new Collection($models);
1160
    }
1161
1162
    /**
1163
     * Save a new model and return the instance.
1164
     *
1165
     * Use `children` key on `$attributes` to create child nodes.
1166
     *
1167
     * @param  array  $attributes
1168
     * @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...
1169
     *
1170
     * @return static
1171
     */
1172 12
    public static function create(array $attributes = [], self $parent = null)
1173
    {
1174 12
        $children = array_pull($attributes, 'children');
1175 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...
1176
1177 12
        if ($parent) {
1178 4
            $instance->appendToNode($parent);
1179 3
        }
1180
1181 12
        $instance->save();
1182
1183
        // Now create children
1184 11
        $relation = new EloquentCollection;
1185
1186 11
        foreach ((array) $children as $child) {
1187 4
            $relation->add($child = static::create($child, $instance));
1188
1189 4
            $child->setRelation('parent', $instance);
1190 9
        }
1191
1192 11
        return $instance->setRelation('children', $relation);
1193
    }
1194
1195
    /* ------------------------------------------------------------------------------------------------
1196
     |  Check Functions
1197
     | ------------------------------------------------------------------------------------------------
1198
     */
1199
    /**
1200
     * Get whether node is root.
1201
     *
1202
     * @return bool
1203
     */
1204 16
    public function isRoot()
1205
    {
1206 16
        return is_null($this->getParentId());
1207
    }
1208
1209
    /**
1210
     * Get whether a node is a descendant of other node.
1211
     *
1212
     * @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...
1213
     *
1214
     * @return bool
1215
     */
1216 76
    public function isDescendantOf(self $other)
1217
    {
1218
        return (
1219 76
            $this->getLft() > $other->getLft() &&
1220 70
            $this->getLft() < $other->getRgt()
1221 57
        );
1222
    }
1223
1224
    /**
1225
     * Get whether the node is immediate children of other node.
1226
     *
1227
     * @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...
1228
     *
1229
     * @return bool
1230
     */
1231 4
    public function isChildOf(self $other)
1232
    {
1233 4
        return $this->getParentId() == $other->getKey();
1234
    }
1235
1236
    /**
1237
     * Get whether the node is a sibling of another node.
1238
     *
1239
     * @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...
1240
     *
1241
     * @return bool
1242
     */
1243 32
    public function isSiblingOf(self $other)
1244
    {
1245 32
        return $this->getParentId() == $other->getParentId();
1246
    }
1247
1248
    /**
1249
     * Get whether the node is an ancestor of other node, including immediate parent.
1250
     *
1251
     * @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...
1252
     *
1253
     * @return bool
1254
     */
1255 4
    public function isAncestorOf(self $other)
1256
    {
1257 4
        return $other->isDescendantOf($this);
1258
    }
1259
1260
    /**
1261
     * Get whether the node has moved since last save.
1262
     *
1263
     * @return bool
1264
     */
1265 24
    public function hasMoved()
1266
    {
1267 24
        return $this->moved;
1268
    }
1269
1270
    /**
1271
     * Check if the model uses soft delete.
1272
     *
1273
     * @return bool
1274
     */
1275 280
    public static function usesSoftDelete()
1276
    {
1277 280
        static $softDelete;
1278
1279 280
        if (is_null($softDelete)) {
1280 8
            $instance = new static;
1281
1282 8
            return $softDelete = method_exists($instance, 'withTrashed');
1283
        }
1284
1285 280
        return $softDelete;
1286
    }
1287
1288
    /**
1289
     * Get whether user is intended to delete the model from database entirely.
1290
     *
1291
     * @return bool
1292
     */
1293 19
    protected function hardDeleting()
1294
    {
1295 19
        return ! $this->usesSoftDelete() || $this->forceDeleting;
1296
    }
1297
1298
    /* ------------------------------------------------------------------------------------------------
1299
     |  Assertion Functions
1300
     | ------------------------------------------------------------------------------------------------
1301
     */
1302
    /**
1303
     * Assert that the node is not a descendant.
1304
     *
1305
     * @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...
1306
     *
1307
     * @return self
1308
     */
1309 76
    protected function assertNotDescendant(self $node)
1310
    {
1311 76
        if ($node == $this || $node->isDescendantOf($this)) {
1312 8
            throw new LogicException('Node must not be a descendant.');
1313
        }
1314
1315 68
        return $this;
1316
    }
1317
1318
    /**
1319
     * Assert node exists.
1320
     *
1321
     * @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...
1322
     *
1323
     * @return self
1324
     */
1325 76
    protected function assertNodeExists(self $node)
1326
    {
1327 76
        if ( ! $node->getLft() || ! $node->getRgt()) {
1328
            throw new LogicException('Node must exists.');
1329
        }
1330
1331 76
        return $this;
1332
    }
1333
}
1334