Completed
Push — master ( 5a4264...db73cc )
by ARCANEDEV
06:52
created

NodeTrait::saveAsRoot()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
ccs 4
cts 5
cp 0.8
rs 9.4285
cc 3
eloc 4
nc 2
nop 0
crap 3.072
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|null
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 ($model) {
71
            /** @var self $model */
72 96
            $model->getConnection()->beginTransaction();
0 ignored issues
show
Documentation Bug introduced by
The method getConnection 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...
73
74 96
            return $model->callPendingAction();
0 ignored issues
show
Documentation Bug introduced by
The method callPendingAction 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...
75 280
        });
76
77
        static::saved(function ($model) {
78
            /** @var self $model */
79 92
            $model->getConnection()->commit();
0 ignored issues
show
Documentation Bug introduced by
The method getConnection 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...
80 280
        });
81
82
        static::deleting(function ($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...
83
            /** @var self $model */
84 20
            $model->getConnection()->beginTransaction();
0 ignored issues
show
Documentation Bug introduced by
The method getConnection 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...
85
86
            // We will need fresh data to delete node safely
87 20
            $model->refreshNode();
0 ignored issues
show
Documentation Bug introduced by
The method refreshNode 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...
88 280
        });
89
90
        static::deleted(function ($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...
91
            /** @var self $model */
92 20
            $model->deleteDescendants();
0 ignored issues
show
Documentation Bug introduced by
The method deleteDescendants 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...
93
94 20
            $model->getConnection()->commit();
0 ignored issues
show
Documentation Bug introduced by
The method getConnection 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...
95 280
        });
96
97 280
        if (static::usesSoftDelete()) {
98
            static::restoring(function ($model) {
99
                /** @var self $model */
100 4
                $model->getConnection()->beginTransaction();
0 ignored issues
show
Documentation Bug introduced by
The method getConnection 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...
101
102 4
                static::$deletedAt = $model->{$model->getDeletedAtColumn()};
0 ignored issues
show
Bug introduced by
The method getDeletedAtColumn() does not exist on Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder. Did you maybe mean delete()?

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...
103 236
            });
104
105 236
            static::restored(function ($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...
106
                /** @var self $model */
107 4
                $model->restoreDescendants(static::$deletedAt);
0 ignored issues
show
Documentation Bug introduced by
The method restoreDescendants 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...
108
109 4
                $model->getConnection()->commit();
0 ignored issues
show
Documentation Bug introduced by
The method getConnection 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...
110 236
            });
111 177
        }
112 280
    }
113
114
    /* ------------------------------------------------------------------------------------------------
115
     |  Eloquent Functions
116
     | ------------------------------------------------------------------------------------------------
117
     */
118
    /**
119
     * Get the database connection for the model.
120
     *
121
     * @return \Illuminate\Database\Connection
122
     */
123
    abstract public function getConnection();
124
125
    /**
126
     * Get the table associated with the model.
127
     *
128
     * @return string
129
     */
130
    abstract public function getTable();
131
132
    /**
133
     * Get the value of the model's primary key.
134
     *
135
     * @return mixed
136
     */
137
    abstract public function getKey();
138
139
    /**
140
     * Get the primary key for the model.
141
     *
142
     * @return string
143
     */
144
    abstract public function getKeyName();
145
146
    /**
147
     * Get a plain attribute (not a relationship).
148
     *
149
     * @param  string  $key
150
     *
151
     * @return mixed
152
     */
153
    abstract public function getAttributeValue($key);
154
155
    /**
156
     * Set the array of model attributes. No checking is done.
157
     *
158
     * @param  array  $attributes
159
     * @param  bool   $sync
160
     *
161
     * @return self
162
     */
163
    abstract public function setRawAttributes(array $attributes, $sync = false);
164
165
    /**
166
     * Set the specific relationship in the model.
167
     *
168
     * @param  string  $relation
169
     * @param  mixed   $value
170
     *
171
     * @return self
172
     */
173
    abstract public function setRelation($relation, $value);
174
175
    /**
176
     * Get a relationship.
177
     *
178
     * @param  string  $key
179
     *
180
     * @return mixed
181
     */
182
    abstract public function getRelationValue($key);
183
184
    /**
185
     * Create a new instance of the given model.
186
     *
187
     * @param  array  $attributes
188
     * @param  bool   $exists
189
     *
190
     * @return self
191
     */
192
    abstract public function newInstance($attributes = [], $exists = false);
193
194
    /**
195
     * Determine if the model or given attribute(s) have been modified.
196
     *
197
     * @param  array|string|null  $attributes
198
     *
199
     * @return bool
200
     */
201
    abstract public function isDirty($attributes = null);
202
203
    /**
204
     * Fill the model with an array of attributes.
205
     *
206
     * @param  array  $attributes
207
     *
208
     * @return self
209
     *
210
     * @throws \Illuminate\Database\Eloquent\MassAssignmentException
211
     */
212
    abstract public function fill(array $attributes);
213
214
    /**
215
     * Save the model to the database.
216
     *
217
     * @param  array  $options
218
     *
219
     * @return bool
220
     */
221
    abstract public function save(array $options = []);
222
223
    /**
224
     * Get a new query builder for the model's table.
225
     *
226
     * @return \Illuminate\Database\Eloquent\Builder
227
     */
228
    abstract public function newQuery();
229
230
    /* ------------------------------------------------------------------------------------------------
231
     |  Relationships
232
     | ------------------------------------------------------------------------------------------------
233
     */
234
    /**
235
     * Relation to the parent.
236
     *
237
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
238
     */
239 16
    public function parent()
240
    {
241 16
        return $this->belongsTo(get_class($this), $this->getParentIdName())
242 16
            ->setModel($this);
243
    }
244
245
    /**
246
     * Relation to children.
247
     *
248
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
249
     */
250 16
    public function children()
251
    {
252 16
        return $this->hasMany(get_class($this), $this->getParentIdName())
253 16
            ->setModel($this);
254
    }
255
256
    /**
257
     * Get query for descendants of the node.
258
     *
259
     * @return \Arcanedev\LaravelNestedSet\Eloquent\DescendantsRelation
260
     */
261 44
    public function descendants()
262
    {
263 44
        return new DescendantsRelation($this->newScopedQuery(), $this);
0 ignored issues
show
Documentation introduced by
$this->newScopedQuery() is of type object<Illuminate\Database\Query\Builder>, but the function expects a object<Arcanedev\Laravel...\Eloquent\QueryBuilder>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
264
    }
265
266
    /**
267
     * Get query for siblings of the node.
268
     *
269
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
270
     */
271 8
    public function siblings()
272
    {
273 8
        return $this->newScopedQuery()
274 8
            ->where($this->getKeyName(), '<>', $this->getKey())
275 8
            ->where($this->getParentIdName(), '=', $this->getParentId());
276
    }
277
278
    /* ------------------------------------------------------------------------------------------------
279
     |  Getters & Setters
280
     | ------------------------------------------------------------------------------------------------
281
     */
282
    /**
283
     * Get the lft key name.
284
     *
285
     * @return string
286
     */
287 248
    public function getLftName()
288
    {
289 248
        return NestedSet::LFT;
290
    }
291
292
    /**
293
     * Get the rgt key name.
294
     *
295
     * @return string
296
     */
297 216
    public function getRgtName()
298
    {
299 216
        return NestedSet::RGT;
300
    }
301
302
    /**
303
     * Get the parent id key name.
304
     *
305
     * @return string
306
     */
307 164
    public function getParentIdName()
308
    {
309 164
        return NestedSet::PARENT_ID;
310
    }
311
312
    /**
313
     * Get the value of the model's lft key.
314
     *
315
     * @return int
316
     */
317 180
    public function getLft()
318
    {
319 180
        return $this->getAttributeValue($this->getLftName());
320
    }
321
322
    /**
323
     * Set the value of the model's lft key.
324
     *
325
     * @param  int  $value
326
     *
327
     * @return self
328
     */
329 92
    public function setLft($value)
330
    {
331 92
        $this->attributes[$this->getLftName()] = $value;
332
333 92
        return $this;
334
    }
335
336
    /**
337
     * Get the value of the model's rgt key.
338
     *
339
     * @return int
340
     */
341 120
    public function getRgt()
342
    {
343 120
        return $this->getAttributeValue($this->getRgtName());
344
    }
345
346
    /**
347
     * Set the value of the model's rgt key.
348
     *
349
     * @param  int  $value
350
     *
351
     * @return self
352
     */
353 92
    public function setRgt($value)
354
    {
355 92
        $this->attributes[$this->getRgtName()] = $value;
356
357 92
        return $this;
358
    }
359
360
    /**
361
     * Get the value of the model's parent id key.
362
     *
363
     * @return int
364
     */
365 96
    public function getParentId()
366
    {
367 96
        return $this->getAttributeValue($this->getParentIdName());
368
    }
369
370
    /**
371
     * Set the value of the model's parent id key.
372
     *
373
     * @param  int  $value
374
     *
375
     * @return self
376
     */
377 64
    public function setParentId($value)
378
    {
379 64
        $this->attributes[$this->getParentIdName()] = $value;
380
381 64
        return $this;
382
    }
383
384
    /**
385
     * Apply parent model.
386
     *
387
     * @param  \Illuminate\Database\Eloquent\Model|null  $value
388
     *
389
     * @return self
390
     */
391 56
    protected function setParent($value)
392
    {
393 56
        $this->setParentId($value ? $value->getKey() : null)
394 56
            ->setRelation('parent', $value);
395
396 56
        return $this;
397
    }
398
399
    /**
400
     * Set the value of model's parent id key.
401
     *
402
     * Behind the scenes node is appended to found parent node.
403
     *
404
     * @param  int  $value
405
     *
406
     * @throws Exception If parent node doesn't exists
407
     */
408 12
    public function setParentIdAttribute($value)
409
    {
410 12
        if ($this->getParentId() == $value) return;
411
412 12
        if ($value) {
413
            /** @var self $node */
414 12
            $node = $this->newScopedQuery()->findOrFail($value);
415
416 12
            $this->appendToNode($node);
417 9
        } else {
418 4
            $this->makeRoot();
419
        }
420 12
    }
421
422
    /**
423
     * Get the boundaries.
424
     *
425
     * @return array
426
     */
427 44
    public function getBounds()
428
    {
429 44
        return [$this->getLft(), $this->getRgt()];
430
    }
431
432
    /**
433
     * Set the lft and rgt boundaries to null.
434
     *
435
     * @return self
436
     */
437 68
    protected function dirtyBounds()
438
    {
439 68
        return $this->setLft(null)->setRgt(null);
440
    }
441
442
    /**
443
     * Returns node that is next to current node without constraining to siblings.
444
     * This can be either a next sibling or a next sibling of the parent node.
445
     *
446
     * @param  array  $columns
447
     *
448
     * @return self
449
     */
450
    public function getNextNode(array $columns = ['*'])
451
    {
452
        return $this->nextNodes()->defaultOrder()->first($columns);
453
    }
454
455
    /**
456
     * Returns node that is before current node without constraining to siblings.
457
     * This can be either a prev sibling or parent node.
458
     *
459
     * @param  array  $columns
460
     *
461
     * @return self
462
     */
463 4
    public function getPrevNode(array $columns = ['*'])
464
    {
465 4
        return $this->prevNodes()->defaultOrder('desc')->first($columns);
466
    }
467
468
    /**
469
     * Get the ancestors nodes.
470
     *
471
     * @param  array  $columns
472
     *
473
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection
474
     */
475 8
    public function getAncestors(array $columns = ['*'])
476
    {
477 8
        return $this->newScopedQuery()
478 8
            ->defaultOrder()
479 8
            ->ancestorsOf($this, $columns);
480
    }
481
482
    /**
483
     * Get the descendants nodes.
484
     *
485
     * @param  array  $columns
486
     *
487
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
488
     */
489 12
    public function getDescendants(array $columns = ['*'])
490
    {
491 12
        return $this->descendants()->get($columns);
0 ignored issues
show
Documentation Bug introduced by
The method get does not exist on object<Arcanedev\Laravel...nt\DescendantsRelation>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
492
    }
493
494
    /**
495
     * Get the siblings nodes.
496
     *
497
     * @param  array  $columns
498
     *
499
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
500
     */
501 8
    public function getSiblings(array $columns = ['*'])
502
    {
503 8
        return $this->siblings()->get($columns);
504
    }
505
506
    /**
507
     * Get the next siblings nodes.
508
     *
509
     * @param  array  $columns
510
     *
511
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
512
     */
513 8
    public function getNextSiblings(array $columns = ['*'])
514
    {
515 8
        return $this->nextSiblings()->get($columns);
516
    }
517
518
    /**
519
     * Get the previous siblings nodes.
520
     *
521
     * @param  array  $columns
522
     *
523
     * @return \Arcanedev\LaravelNestedSet\Eloquent\Collection|self[]
524
     */
525 8
    public function getPrevSiblings(array $columns = ['*'])
526
    {
527 8
        return $this->prevSiblings()->get($columns);
528
    }
529
530
    /**
531
     * Get the next sibling node.
532
     *
533
     * @param  array  $columns
534
     *
535
     * @return self
536
     */
537 4
    public function getNextSibling(array $columns = ['*'])
538
    {
539 4
        return $this->nextSiblings()->defaultOrder()->first($columns);
540
    }
541
542
    /**
543
     * Get the previous sibling node.
544
     *
545
     * @param  array  $columns
546
     *
547
     * @return self
548
     */
549 4
    public function getPrevSibling(array $columns = ['*'])
550
    {
551 4
        return $this->prevSiblings()->defaultOrder('desc')->first($columns);
552
    }
553
554
    /**
555
     * Get node height (rgt - lft + 1).
556
     *
557
     * @return int
558
     */
559 36
    public function getNodeHeight()
560
    {
561 36
        if ( ! $this->exists) return 2;
562
563 4
        return $this->getRgt() - $this->getLft() + 1;
564
    }
565
566
    /**
567
     * Get number of descendant nodes.
568
     *
569
     * @return int
570
     */
571 4
    public function getDescendantCount()
572
    {
573 4
        return (int) ceil($this->getNodeHeight() / 2) - 1;
574
    }
575
576
    /**
577
     * Get an attribute array of all arrayable relations.
578
     *
579
     * @return array
580
     */
581
    protected function getArrayableRelations()
582
    {
583
        $result = parent::getArrayableRelations();
584
585
        unset($result['parent']);
586
587
        return $result;
588
    }
589
590
    /**
591
     * Set an action.
592
     *
593
     * @param  string  $action
594
     *
595
     * @return self
596
     */
597 116
    protected function setNodeAction($action)
598
    {
599 116
        $this->pending = func_get_args();
600 116
        unset($action);
601
602 116
        return $this;
603
    }
604
605
    /**
606
     * @return bool
607
     */
608 8
    protected function actionRaw()
609
    {
610 8
        return true;
611
    }
612
613
    /**
614
     * Call pending action.
615
     *
616
     * @return null|false
617
     */
618 96
    protected function callPendingAction()
619
    {
620 96
        $this->moved = false;
621
622 96
        if ( ! $this->pending && ! $this->exists) {
623 20
            $this->makeRoot();
624 15
        }
625
626 96
        if ( ! $this->pending) return;
627
628 92
        $method        = 'action'.ucfirst(array_shift($this->pending));
629 92
        $parameters    = $this->pending;
630
631 92
        $this->pending = null;
632 92
        $this->moved   = call_user_func_array([$this, $method], $parameters);
633 92
    }
634
635
    /* ------------------------------------------------------------------------------------------------
636
     |  Other Functions
637
     | ------------------------------------------------------------------------------------------------
638
     */
639
    /**
640
     * Make a root node.
641
     */
642 32
    protected function actionRoot()
643
    {
644
        // Simplest case that do not affect other nodes.
645 32
        if ( ! $this->exists) {
646 20
            $cut = $this->getLowerBound() + 1;
647
648 20
            $this->setLft($cut);
649 20
            $this->setRgt($cut + 1);
650
651 20
            return true;
652
        }
653
654 12
        if ($this->isRoot()) return false;
655
656
657
        // Reset parent object
658 12
        $this->setParent(null);
659
660 12
        return $this->insertAt($this->getLowerBound() + 1);
661
    }
662
663
    /**
664
     * Get the lower bound.
665
     *
666
     * @return int
667
     */
668 32
    protected function getLowerBound()
669
    {
670 32
        return (int) $this->newNestedSetQuery()->max($this->getRgtName());
671
    }
672
673
    /**
674
     * Append or prepend a node to the parent.
675
     *
676
     * @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...
677
     * @param  bool  $prepend
678
     *
679
     * @return bool
680
     */
681 32
    protected function actionAppendOrPrepend(self $parent, $prepend = false)
682
    {
683 32
        $parent->refreshNode();
684
685 32
        $cut = $prepend ? $parent->getLft() + 1 : $parent->getRgt();
686
687 32
        if ( ! $this->insertAt($cut)) {
688
            return false;
689
        }
690
691 32
        $parent->refreshNode();
692
693 32
        return true;
694
    }
695
696
    /**
697
     * Insert node before or after another node.
698
     *
699
     * @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...
700
     * @param  bool  $after
701
     *
702
     * @return bool
703
     */
704 28
    protected function actionBeforeOrAfter(self $node, $after = false)
705
    {
706 28
        $node->refreshNode();
707
708 28
        return $this->insertAt($after ? $node->getRgt() + 1 : $node->getLft());
709
    }
710
711
    /**
712
     * Refresh node's crucial attributes.
713
     */
714 88
    public function refreshNode()
715
    {
716 88
        if ( ! $this->exists || static::$actionsPerformed === 0) return;
717
718 64
        $attributes = $this->newNestedSetQuery()->getNodeData($this->getKey());
719
720 64
        $this->attributes = array_merge($this->attributes, $attributes);
721 64
        $this->original   = array_merge($this->original,   $attributes);
722 64
    }
723
724
    /**
725
     * Get query for siblings after the node.
726
     *
727
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
728
     */
729 24
    public function nextSiblings()
730
    {
731 24
        return $this->nextNodes()
732 24
            ->where($this->getParentIdName(), '=', $this->getParentId());
733
    }
734
735
    /**
736
     * Get query for siblings before the node.
737
     *
738
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
739
     */
740 16
    public function prevSiblings()
741
    {
742 16
        return $this->prevNodes()
743 16
            ->where($this->getParentIdName(), '=', $this->getParentId());
744
    }
745
746
    /**
747
     * Get query for nodes after current node.
748
     *
749
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
750
     */
751 28
    public function nextNodes()
752
    {
753 28
        return $this->newScopedQuery()
754 28
            ->where($this->getLftName(), '>', $this->getLft());
755
    }
756
757
    /**
758
     * Get query for nodes before current node in reversed order.
759
     *
760
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
761
     */
762 20
    public function prevNodes()
763
    {
764 20
        return $this->newScopedQuery()
765 20
            ->where($this->getLftName(), '<', $this->getLft());
766
    }
767
768
    /**
769
     * Get query for ancestors to the node not including the node itself.
770
     *
771
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
772
     */
773 4
    public function ancestors()
774
    {
775 4
        return $this->newScopedQuery()
776 4
            ->whereAncestorOf($this)->defaultOrder();
777
    }
778
779
    /**
780
     * Make this node a root node.
781
     *
782
     * @return self
783
     */
784 48
    public function makeRoot()
785
    {
786 48
        return $this->setNodeAction('root');
787
    }
788
789
    /**
790
     * Save node as root.
791
     *
792
     * @return bool
793
     */
794 8
    public function saveAsRoot()
795
    {
796 8
        if ($this->exists && $this->isRoot()) {
797
            return true;
798 3
        }
799
800 8
        return $this->makeRoot()->save();
801
    }
802
803
    /**
804
     * Append and save a node.
805
     *
806
     * @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...
807
     *
808
     * @return bool
809
     */
810 16
    public function appendNode(self $node)
811
    {
812 16
        return $node->appendToNode($this)->save();
813
    }
814
815
    /**
816
     * Prepend and save a node.
817
     *
818
     * @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...
819
     *
820
     * @return bool
821
     */
822 4
    public function prependNode(self $node)
823
    {
824 4
        return $node->prependToNode($this)->save();
825
    }
826
827
    /**
828
     * Append a node to the new parent.
829
     *
830
     * @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...
831
     *
832
     * @return self
833
     */
834 40
    public function appendToNode(self $parent)
835
    {
836 40
        return $this->appendOrPrependTo($parent);
837
    }
838
839
    /**
840
     * Prepend a node to the new parent.
841
     *
842
     * @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...
843
     *
844
     * @return self
845
     */
846 4
    public function prependToNode(self $parent)
847
    {
848 4
        return $this->appendOrPrependTo($parent, true);
849
    }
850
851
    /**
852
     * @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...
853
     * @param  bool  $prepend
854
     *
855
     * @return self
856
     */
857 44
    public function appendOrPrependTo(self $parent, $prepend = false)
858
    {
859 44
        $this->assertNodeExists($parent)
860 44
             ->assertNotDescendant($parent);
861
862 40
        $this->setParent($parent)->dirtyBounds();
863
864 40
        return $this->setNodeAction('appendOrPrepend', $parent, $prepend);
865
    }
866
867
    /**
868
     * Insert self after a node.
869
     *
870
     * @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...
871
     *
872
     * @return self
873
     */
874 28
    public function afterNode(self $node)
875
    {
876 28
        return $this->beforeOrAfterNode($node, true);
877
    }
878
879
    /**
880
     * Insert self before node.
881
     *
882
     * @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...
883
     *
884
     * @return self
885
     */
886 8
    public function beforeNode(self $node)
887
    {
888 8
        return $this->beforeOrAfterNode($node);
889
    }
890
891
    /**
892
     * @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...
893
     * @param  bool  $after
894
     *
895
     * @return self
896
     */
897 36
    public function beforeOrAfterNode(self $node, $after = false)
898
    {
899 36
        $this->assertNodeExists($node)->assertNotDescendant($node);
900
901 32
        if ( ! $this->isSiblingOf($node)) {
902 12
            $this->setParent($node->getRelationValue('parent'));
903 9
        }
904
905 32
        $this->dirtyBounds();
906
907 32
        return $this->setNodeAction('beforeOrAfter', $node, $after);
908
    }
909
910
    /**
911
     * Insert self after a node and save.
912
     *
913
     * @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...
914
     *
915
     * @return bool
916
     */
917 16
    public function insertAfterNode(self $node)
918
    {
919 16
        return $this->afterNode($node)->save();
920
    }
921
922
    /**
923
     * Insert self before a node and save.
924
     *
925
     * @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...
926
     *
927
     * @return bool
928
     */
929 4
    public function insertBeforeNode(self $node)
930
    {
931 4
        if ( ! $this->beforeNode($node)->save()) return false;
932
933
        // We'll update the target node since it will be moved
934 4
        $node->refreshNode();
935
936 4
        return true;
937
    }
938
939
    /**
940
     * @param  int  $lft
941
     * @param  int  $rgt
942
     * @param  int  $parentId
943
     *
944
     * @return self
945
     */
946 8
    public function rawNode($lft, $rgt, $parentId)
947
    {
948 8
        $this->setLft($lft)->setRgt($rgt)->setParentId($parentId);
949
950 8
        return $this->setNodeAction('raw');
951
    }
952
953
    /**
954
     * Move node up given amount of positions.
955
     *
956
     * @param  int  $amount
957
     *
958
     * @return bool
959
     */
960 4
    public function up($amount = 1)
961
    {
962 4
        $sibling = $this->prevSiblings()
963 4
                        ->defaultOrder('desc')
964 4
                        ->skip($amount - 1)
965 4
                        ->first();
966
967 4
        if ( ! $sibling) return false;
968
969 4
        return $this->insertBeforeNode($sibling);
970
    }
971
972
    /**
973
     * Move node down given amount of positions.
974
     *
975
     * @param  int  $amount
976
     *
977
     * @return bool
978
     */
979 16
    public function down($amount = 1)
980
    {
981 16
        $sibling = $this->nextSiblings()
982 16
                        ->defaultOrder()
983 16
                        ->skip($amount - 1)
984 16
                        ->first();
985
986 16
        if ( ! $sibling) return false;
987
988 16
        return $this->insertAfterNode($sibling);
989
    }
990
991
    /**
992
     * Insert node at specific position.
993
     *
994
     * @param  int  $position
995
     *
996
     * @return bool
997
     */
998 68
    protected function insertAt($position)
999
    {
1000 68
        ++static::$actionsPerformed;
1001
1002 68
        $result = $this->exists
1003 61
            ? $this->moveNode($position)
1004 68
            : $this->insertNode($position);
1005
1006 68
        return $result;
1007
    }
1008
1009
    /**
1010
     * Move a node to the new position.
1011
     *
1012
     * @param  int  $position
1013
     *
1014
     * @return int
1015
     */
1016 40
    protected function moveNode($position)
1017
    {
1018 40
        $updated = $this->newNestedSetQuery()
1019 40
                        ->moveNode($this->getKey(), $position) > 0;
1020
1021 40
        if ($updated) $this->refreshNode();
1022
1023 40
        return $updated;
1024
    }
1025
1026
    /**
1027
     * Insert new node at specified position.
1028
     *
1029
     * @param  int  $position
1030
     *
1031
     * @return bool
1032
     */
1033 32
    protected function insertNode($position)
1034
    {
1035 32
        $this->newNestedSetQuery()->makeGap($position, 2);
1036
1037 32
        $height = $this->getNodeHeight();
1038
1039 32
        $this->setLft($position);
1040 32
        $this->setRgt($position + $height - 1);
1041
1042 32
        return true;
1043
    }
1044
1045
    /**
1046
     * Update the tree when the node is removed physically.
1047
     */
1048 20
    protected function deleteDescendants()
1049
    {
1050 20
        $lft = $this->getLft();
1051 20
        $rgt = $this->getRgt();
1052
1053 20
        $method = ($this->usesSoftDelete() && $this->forceDeleting)
1054 18
            ? 'forceDelete'
1055 20
            : 'delete';
1056
1057 20
        $this->descendants()->{$method}();
1058
1059 20
        if ($this->hardDeleting()) {
1060 16
            $height = $rgt - $lft + 1;
1061
1062 16
            $this->newNestedSetQuery()->makeGap($rgt + 1, -$height);
1063
1064
            // In case if user wants to re-create the node
1065 16
            $this->makeRoot();
1066
1067 16
            static::$actionsPerformed++;
1068 12
        }
1069 20
    }
1070
1071
    /**
1072
     * Restore the descendants.
1073
     *
1074
     * @param  mixed  $deletedAt
1075
     */
1076 4
    protected function restoreDescendants($deletedAt)
1077
    {
1078 4
        $this->descendants()
0 ignored issues
show
Documentation Bug introduced by
The method where does not exist on object<Arcanedev\Laravel...nt\DescendantsRelation>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1079 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...
1080 4
            ->applyScopes()
1081 4
            ->restore();
1082 4
    }
1083
1084
    /**
1085
     * Create a new Eloquent query builder for the model.
1086
     *
1087
     * @param  \Illuminate\Database\Query\Builder  $query
1088
     *
1089
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1090
     */
1091 280
    public function newEloquentBuilder($query)
1092
    {
1093 280
        return new QueryBuilder($query);
1094
    }
1095
1096
    /**
1097
     * Get a new base query that includes deleted nodes.
1098
     *
1099
     * @param  string|null $table
1100
     *
1101
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1102
     */
1103 116
    public function newNestedSetQuery($table = null)
1104
    {
1105 116
        $builder = $this->usesSoftDelete()
1106 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...
1107 116
            : $this->newQuery();
1108
1109 116
        return $this->applyNestedSetScope($builder, $table);
1110
    }
1111
1112
    /**
1113
     * @param  string|null  $table
1114
     *
1115
     * @return \Arcanedev\LaravelNestedSet\Eloquent\QueryBuilder
1116
     */
1117 136
    public function newScopedQuery($table = null)
1118
    {
1119 136
        return $this->applyNestedSetScope($this->newQuery(), $table);
0 ignored issues
show
Documentation introduced by
$this->newQuery() is of type object<Illuminate\Database\Eloquent\Builder>, but the function expects a object<Illuminate\Database\Query\Builder>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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