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

NodeTrait   D

Complexity

Total Complexity 113

Size/Duplication

Total Lines 1141
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 98.35%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 113
c 2
b 0
f 0
lcom 1
cbo 8
dl 0
loc 1141
ccs 298
cts 303
cp 0.9835
rs 4.4102

76 Methods

Rating   Name   Duplication   Size   Complexity  
B bootNodeTrait() 0 45 2
A parent() 0 5 1
A children() 0 5 1
A descendants() 0 4 1
A siblings() 0 6 1
A getLftName() 0 4 1
A getRgtName() 0 4 1
A getParentIdName() 0 4 1
A getLft() 0 4 1
A setLft() 0 6 1
A getRgt() 0 4 1
A setRgt() 0 6 1
A getParentId() 0 4 1
A setParentId() 0 6 1
A setParent() 0 7 2
A setParentIdAttribute() 0 13 3
A getBounds() 0 4 1
A getScopeAttributes() 0 4 1
A dirtyBounds() 0 4 1
A getNextNode() 0 4 1
A getPrevNode() 0 4 1
A getAncestors() 0 6 1
A getDescendants() 0 4 1
A getSiblings() 0 4 1
A getNextSiblings() 0 4 1
A getPrevSiblings() 0 4 1
A getNextSibling() 0 4 1
A getPrevSibling() 0 4 1
A getNodeHeight() 0 6 2
A getDescendantCount() 0 4 1
A setNodeAction() 0 7 1
A getLowerBound() 0 4 1
A callPendingAction() 0 16 4
A actionRaw() 0 4 1
A actionRoot() 0 20 3
A actionAppendOrPrepend() 0 14 3
A actionBeforeOrAfter() 0 6 2
A refreshNode() 0 9 3
A nextSiblings() 0 5 1
A prevSiblings() 0 5 1
A nextNodes() 0 5 1
A prevNodes() 0 5 1
A ancestors() 0 5 1
A makeRoot() 0 4 1
A saveAsRoot() 0 8 3
A appendNode() 0 4 1
A prependNode() 0 4 1
A appendToNode() 0 4 1
A prependToNode() 0 4 1
A appendOrPrependTo() 0 9 1
A afterNode() 0 4 1
A beforeNode() 0 4 1
A beforeOrAfterNode() 0 12 2
A insertAfterNode() 0 4 1
A insertBeforeNode() 0 9 2
A rawNode() 0 6 1
A up() 0 11 2
A down() 0 11 2
A insertAt() 0 10 2
A moveNode() 0 9 2
A insertNode() 0 11 1
B deleteDescendants() 0 22 4
A restoreDescendants() 0 7 1
A newNestedSetQuery() 0 8 2
A newScopedQuery() 0 4 1
A applyNestedSetScope() 0 16 4
A scoped() 0 8 1
A create() 0 22 3
A isRoot() 0 4 1
A isDescendantOf() 0 7 2
A isChildOf() 0 4 1
A isSiblingOf() 0 4 1
A isAncestorOf() 0 4 1
A hasMoved() 0 4 1
A assertNotDescendant() 0 8 3
A assertNodeExists() 0 8 3

How to fix   Complexity   

Complex Class

Complex classes like NodeTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NodeTrait, and based on these observations, apply Extract Interface, too.

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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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 ARCANEDEV
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
    }