Issues (34)

src/HasNestedSets.php (18 issues)

1
<?php
2
3
namespace Encima\Albero;
4
5
use Illuminate\Database\Eloquent\Model;
6
use Illuminate\Database\Eloquent\Builder;
7
use Encima\Albero\Extensions\Eloquent\Collection;
8
use Illuminate\Database\Eloquent\Relations\HasMany;
9
use Illuminate\Database\Eloquent\SoftDeletingScope;
10
use Illuminate\Database\Eloquent\Relations\BelongsTo;
11
use Illuminate\Database\Eloquent\ModelNotFoundException;
12
use Encima\Albero\Extensions\Query\Builder as QueryBuilder;
13
14
/**
15
 * Node
16
 *
17
 * This abstract class implements Nested Set functionality. A Nested Set is a
18
 * smart way to implement an ordered tree with the added benefit that you can
19
 * select all of their descendants with a single query. Drawbacks are that
20
 * insertion or move operations need more complex sql queries.
21
 *
22
 * Nested sets are appropiate when you want either an ordered tree (menus,
23
 * commercial categories, etc.) or an efficient way of querying big trees.
24
 */
25
trait HasNestedSets
26
{
27
    protected string $parentColumn = 'parent_id';
28
29
    protected string $leftColumn = 'left';
30
31
    protected string $rightColumn = 'right';
32
33
    protected string $depthColumn = 'depth';
34
35
    /** @var string|null */
36
    protected $orderColumn = null;
37
38
    /** @var int|string|bool|null */
39
    protected static $moveToNewParentId = null;
40
41
    protected array $scoped = [];
42
43
    /**
44
     * The "booting" method of the model.
45
     *
46
     * We'll use this method to register event listeners on a Node instance as
47
     * suggested in the beta documentation...
48
     *
49
     * TODO:
50
     *
51
     *    - Find a way to avoid needing to declare the called methods "public"
52
     *    as registering the event listeners *inside* this methods does not give
53
     *    us an object context.
54
     *
55
     * Events:
56
     *
57
     *    1. "creating": Before creating a new Node we'll assign a default value
58
     *    for the left and right indexes.
59
     *
60
     *    2. "saving": Before saving, we'll perform a check to see if we have to
61
     *    move to another parent.
62
     *
63
     *    3. "saved": Move to the new parent after saving if needed and re-set
64
     *    depth.
65
     *
66
     *    4. "deleting": Before delete we should prune all children and update
67
     *    the left and right indexes for the remaining nodes.
68
     *
69
     *    5. (optional) "restoring": Before a soft-delete node restore operation,
70
     *    shift its siblings.
71
     *
72
     *    6. (optional) "restore": After having restored a soft-deleted node,
73
     *    restore all of its descendants.
74
     *
75
     * @return void
76
     */
77
    protected static function bootHasNestedSets(): void
78
    {
79
        static::creating(function ($node) {
80
            $node->setDefaultLeftAndRight();
81
        });
82
83
        static::saving(function ($node) {
84
            $node->storeNewParent();
85
        });
86
87
        static::saved(function ($node) {
88
            $node->moveToNewParent();
89
            $node->setDepth();
90
        });
91
92
        static::deleting(function ($node) {
93
            $node->destroyDescendants();
94
        });
95
96
        if (static::softDeletesEnabled()) {
97
            static::restoring(function ($node) {
98
                $node->shiftSiblingsForRestore();
99
            });
100
101
            static::restored(function ($node) {
102
                $node->restoreDescendants();
103
            });
104
        }
105
    }
106
107
    /**
108
     * Get the table associated with the model.
109
     *
110
     * @return string
111
     */
112
    abstract public function getTable();
113
114
    /**
115
     * Get an attribute from the model.
116
     *
117
     * @param  string  $key
118
     * @return mixed
119
     */
120
    abstract public function getAttribute($key);
121
122
    /**
123
     * Set a given attribute on the model.
124
     *
125
     * @param  string  $key
126
     * @param  mixed  $value
127
     * @return mixed
128
     */
129
    abstract public function setAttribute($key, $value);
130
131
    /**
132
     * Determine if the model or any of the given attribute(s) have been modified.
133
     *
134
     * @param  array|string|null  $attributes
135
     * @return bool
136
     */
137
    abstract public function isDirty($attributes = null);
138
139
    /**
140
    * Define an inverse one-to-one or many relationship.
141
    *
142
    * @param  string  $related
143
    * @param  string|null  $foreignKey
144
    * @param  string|null  $ownerKey
145
    * @param  string|null  $relation
146
    * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
147
    */
148
    abstract public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null);
149
150
    /**
151
     * Define a one-to-many relationship.
152
     *
153
     * @param  string  $related
154
     * @param  string|null  $foreignKey
155
     * @param  string|null  $localKey
156
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
157
     */
158
    abstract public function hasMany($related, $foreignKey = null, $localKey = null);
159
160
    /**
161
     * Get a new query builder for the model's table.
162
     *
163
     * @return \Illuminate\Database\Eloquent\Builder
164
     */
165
    abstract public function newQuery();
166
167
    /**
168
     * Get the database connection for the model.
169
     *
170
     * @return \Illuminate\Database\Connection
171
     */
172
    abstract public function getConnection();
173
174
    /**
175
     * Get the value of the model's primary key.
176
     *
177
     * @return mixed
178
     */
179
    abstract public function getKey();
180
181
    /**
182
     * Get the primary key for the model.
183
     *
184
     * @return string
185
     */
186
    abstract public function getKeyName();
187
188
    /**
189
    * Get the parent column name.
190
    *
191
    * @return string
192
    */
193
    public function getParentColumnName(): string
194
    {
195
        return $this->parentColumn;
196
    }
197
198
    /**
199
    * Get the table qualified parent column name.
200
    *
201
    * @return string
202
    */
203
    public function getQualifiedParentColumnName(): string
204
    {
205
        return $this->getTable().'.'.$this->getParentColumnName();
206
    }
207
208
    /**
209
    * Get the value of the models "parent_id" field.
210
    *
211
    * @return int|string
212
    */
213
    public function getParentId()
214
    {
215
        return $this->getAttribute($this->getparentColumnName());
216
    }
217
218
    /**
219
     * Get the "left" field column name.
220
     *
221
     * @return string
222
     */
223
    public function getLeftColumnName(): string
224
    {
225
        return $this->leftColumn;
226
    }
227
228
    /**
229
     * Get the table qualified "left" field column name.
230
     *
231
     * @return string
232
     */
233
    public function getQualifiedLeftColumnName(): string
234
    {
235
        return $this->getTable().'.'.$this->getLeftColumnName();
236
    }
237
238
    /**
239
     * Get the value of the model's "left" field.
240
     *
241
     * @return int
242
     */
243
    public function getLeft(): int
244
    {
245
        return $this->getAttribute($this->getLeftColumnName());
246
    }
247
248
    /**
249
     * Get the "right" field column name.
250
     *
251
     * @return string
252
     */
253
    public function getRightColumnName(): string
254
    {
255
        return $this->rightColumn;
256
    }
257
258
    /**
259
     * Get the table qualified "right" field column name.
260
     *
261
     * @return string
262
     */
263
    public function getQualifiedRightColumnName(): string
264
    {
265
        return $this->getTable().'.'.$this->getRightColumnName();
266
    }
267
268
    /**
269
     * Get the value of the model's "right" field.
270
     *
271
     * @return int
272
     */
273
    public function getRight(): int
274
    {
275
        return $this->getAttribute($this->getRightColumnName());
276
    }
277
278
    /**
279
     * Get the "depth" field column name.
280
     *
281
     * @return string
282
     */
283
    public function getDepthColumnName(): string
284
    {
285
        return $this->depthColumn;
286
    }
287
288
    /**
289
     * Get the table qualified "depth" field column name.
290
     *
291
     * @return string
292
     */
293
    public function getQualifiedDepthColumnName(): string
294
    {
295
        return $this->getTable().'.'.$this->getDepthColumnName();
296
    }
297
298
    /**
299
     * Get the model's "depth" value.
300
     *
301
     * @return int
302
     */
303
    public function getDepth(): ?int
304
    {
305
        return $this->getAttribute($this->getDepthColumnName());
306
    }
307
308
    /**
309
     * Get the "order" field column name.
310
     *
311
     * @return string
312
     */
313
    public function getOrderColumnName(): string
314
    {
315
        return is_null($this->orderColumn) ? $this->getLeftColumnName() : $this->orderColumn;
316
    }
317
318
    /**
319
     * Get the table qualified "order" field column name.
320
     *
321
     * @return string
322
     */
323
    public function getQualifiedOrderColumnName(): string
324
    {
325
        return $this->getTable().'.'.$this->getOrderColumnName();
326
    }
327
328
    /**
329
     * Get the model's "order" value.
330
     *
331
     * @return mixed
332
     */
333
    public function getOrder()
334
    {
335
        return $this->getAttribute($this->getOrderColumnName());
336
    }
337
338
    /**
339
     * Get the column names which define our scope
340
     *
341
     * @return array
342
     */
343
    public function getScopedColumns(): array
344
    {
345
        return (array) $this->scoped;
346
    }
347
348
    /**
349
     * Get the qualified column names which define our scope
350
     *
351
     * @return array
352
     */
353
    public function getQualifiedScopedColumns(): array
354
    {
355
        if (!$this->isScoped()) {
356
            return $this->getScopedColumns();
357
        }
358
359
        $prefix = $this->getTable().'.';
360
361
        return array_map(function ($c) use ($prefix) {
362
            return $prefix.$c;
363
        }, $this->getScopedColumns());
364
    }
365
366
    /**
367
     * Returns wether this particular node instance is scoped by certain fields
368
     * or not.
369
     *
370
     * @return boolean
371
     */
372
    public function isScoped(): bool
373
    {
374
        return !!(count($this->getScopedColumns()) > 0);
375
    }
376
377
    /**
378
    * Parent relation (self-referential) 1-1.
379
    *
380
    * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
381
    */
382
    public function parent(): BelongsTo
383
    {
384
        return $this->belongsTo(get_class($this), $this->getParentColumnName());
385
    }
386
387
    /**
388
    * Children relation (self-referential) 1-N.
389
    *
390
    * @return \Illuminate\Database\Eloquent\Relations\HasMany
391
    */
392
    public function children(): HasMany
393
    {
394
        return $this->hasMany(get_class($this), $this->getParentColumnName())
395
                ->orderBy($this->getOrderColumnName());
396
    }
397
398
    /**
399
     * Get a new "scoped" query builder for the Node's model.
400
     *
401
     * @param  bool  $excludeDeleted
402
     * @return \Illuminate\Database\Eloquent\Builder|static
403
     */
404
    public function newNestedSetQuery(): Builder
405
    {
406
        $builder = $this->newQuery()->orderBy($this->getQualifiedOrderColumnName());
407
408
        if ($this->isScoped()) {
409
            foreach ($this->scoped as $scopeFld) {
410
                $builder->where($scopeFld, '=', $this->{$scopeFld});
411
            }
412
        }
413
414
        return $builder;
415
    }
416
417
    /**
418
     * Overload new Collection
419
     *
420
     * @param array $models
421
     * @return \Encima\Albero\Extensions\Eloquent\Collection
422
     */
423
    public function newCollection(array $models = []): Collection
424
    {
425
        return new Collection($models);
426
    }
427
428
    /**
429
     * Get all of the nodes from the database.
430
     *
431
     * @param  array  $columns
432
     * @return \Encima\Albero\Extensions\Eloquent\Collection|static[]
433
     */
434
    public static function all($columns = ['*']): Collection
435
    {
436
        $instance = new static();
437
438
        return $instance->newQuery()
439
                    ->orderBy($instance->getQualifiedOrderColumnName())
440
                    ->get($columns);
441
    }
442
443
    /**
444
     * Returns the first root node.
445
     *
446
     * @return \Illuminate\Database\Eloquent\Model
447
     */
448
    public static function root(): Model
449
    {
450
        return static::roots()->first();
451
    }
452
453
    /**
454
     * Static query scope. Returns a query scope with all root nodes.
455
     *
456
     * @return \Illuminate\Database\Query\Builder
457
     */
458
    public static function roots(): Builder
459
    {
460
        $instance = new static();
461
462
        return $instance->newQuery()
463
                    ->whereNull($instance->getParentColumnName())
464
                    ->orderBy($instance->getQualifiedOrderColumnName());
465
    }
466
467
    /**
468
     * Static query scope. Returns a query scope with all nodes which are at
469
     * the end of a branch.
470
     *
471
     * @return \Illuminate\Database\Query\Builder
472
     */
473
    public static function allLeaves(): Builder
474
    {
475
        $instance = new static();
476
477
        $grammar = $instance->getConnection()->getQueryGrammar();
478
479
        $rgtCol = $grammar->wrap($instance->getQualifiedRightColumnName());
480
        $lftCol = $grammar->wrap($instance->getQualifiedLeftColumnName());
481
482
        return $instance->newQuery()
483
            ->whereRaw($rgtCol.' - '.$lftCol.' = 1')
484
            ->orderBy($instance->getQualifiedOrderColumnName());
485
    }
486
487
    /**
488
     * Static query scope. Returns a query scope with all nodes which are at
489
     * the middle of a branch (not root and not leaves).
490
     *
491
     * @return \Illuminate\Database\Query\Builder
492
     */
493
    public static function allTrunks(): Builder
494
    {
495
        $instance = new static();
496
497
        $grammar = $instance->getConnection()->getQueryGrammar();
498
499
        $rgtCol = $grammar->wrap($instance->getQualifiedRightColumnName());
500
        $lftCol = $grammar->wrap($instance->getQualifiedLeftColumnName());
501
502
        return $instance->newQuery()
503
            ->whereNotNull($instance->getParentColumnName())
504
            ->whereRaw($rgtCol.' - '.$lftCol.' != 1')
505
            ->orderBy($instance->getQualifiedOrderColumnName());
506
    }
507
508
    /**
509
     * Checks wether the underlying Nested Set structure is valid.
510
     *
511
     * @return boolean
512
     */
513
    public static function isValidNestedSet(): bool
514
    {
515
        $validator = new SetValidator(new static());
0 ignored issues
show
new static() of type Encima\Albero\HasNestedSets is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $node of Encima\Albero\SetValidator::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

515
        $validator = new SetValidator(/** @scrutinizer ignore-type */ new static());
Loading history...
516
517
        return $validator->passes();
518
    }
519
520
    /**
521
     * Rebuilds the structure of the current Nested Set.
522
     *
523
     * @param  bool $force
524
     * @return void
525
     */
526
    public static function rebuild($force = false): void
527
    {
528
        $builder = new SetBuilder(new static());
0 ignored issues
show
new static() of type Encima\Albero\HasNestedSets is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $node of Encima\Albero\SetBuilder::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

528
        $builder = new SetBuilder(/** @scrutinizer ignore-type */ new static());
Loading history...
529
530
        $builder->rebuild($force);
531
    }
532
533
    /**
534
     * Maps the provided tree structure into the database.
535
     *
536
     * @param   array|\Illuminate\Support\Contracts\ArrayableInterface
537
     * @return  boolean
538
     */
539
    public static function buildTree($nodeList): bool
540
    {
541
        return (new static())->makeTree($nodeList);
542
    }
543
544
    /**
545
     * Query scope which extracts a certain node object from the current query
546
     * expression.
547
     *
548
     * @return \Illuminate\Database\Query\Builder
549
     */
550
    public function scopeWithoutNode($query, $node): Builder
551
    {
552
        return $query->where($node->getKeyName(), '!=', $node->getKey());
553
    }
554
555
    /**
556
     * Extracts current node (self) from current query expression.
557
     *
558
     * @return \Illuminate\Database\Query\Builder
559
     */
560
    public function scopeWithoutSelf($query): Builder
561
    {
562
        return $this->scopeWithoutNode($query, $this);
563
    }
564
565
    /**
566
     * Extracts first root (from the current node p-o-v) from current query
567
     * expression.
568
     *
569
     * @return \Illuminate\Database\Query\Builder
570
     */
571
    public function scopeWithoutRoot($query): Builder
572
    {
573
        return $this->scopeWithoutNode($query, $this->getRoot());
574
    }
575
576
    /**
577
     * Provides a depth level limit for the query.
578
     *
579
     * @param   query   \Illuminate\Database\Query\Builder
580
     * @param   limit   integer
581
     * @return  \Illuminate\Database\Query\Builder
582
     */
583
    public function scopeLimitDepth($query, $limit): Builder
584
    {
585
        $depth = $this->exists ? $this->getDepth() : $this->getLevel();
586
        $max = $depth + $limit;
587
        $scopes = [$depth, $max];
588
589
        return $query->whereBetween($this->getDepthColumnName(), [min($scopes), max($scopes)]);
590
    }
591
592
    /**
593
     * Returns true if this is a root node.
594
     *
595
     * @return boolean
596
     */
597
    public function isRoot(): bool
598
    {
599
        return is_null($this->getParentId());
600
    }
601
602
    /**
603
     * Returns true if this is a leaf node (end of a branch).
604
     *
605
     * @return boolean
606
     */
607
    public function isLeaf(): bool
608
    {
609
        return $this->exists && ($this->getRight() - $this->getLeft() == 1);
610
    }
611
612
    /**
613
     * Returns true if this is a trunk node (not root or leaf).
614
     *
615
     * @return boolean
616
     */
617
    public function isTrunk(): bool
618
    {
619
        return !$this->isRoot() && !$this->isLeaf();
620
    }
621
622
    /**
623
     * Returns true if this is a child node.
624
     *
625
     * @return boolean
626
     */
627
    public function isChild(): bool
628
    {
629
        return !$this->isRoot();
630
    }
631
632
    /**
633
     * Returns the root node starting at the current node.
634
     *
635
     * @return \Illuminate\Database\Eloquent\Model
636
     */
637
    public function getRoot(): Model
638
    {
639
        if ($this->exists) {
640
            return $this->ancestorsAndSelf()->whereNull($this->getParentColumnName())->first();
641
        }
642
        $parentId = $this->getParentId();
643
644
        if (!is_null($parentId) && $currentParent = static::find($parentId)) {
0 ignored issues
show
The condition is_null($parentId) is always false.
Loading history...
645
            return $currentParent->getRoot();
646
        }
647
648
        return $this;
649
    }
650
651
    /**
652
     * Instance scope which targes all the ancestor chain nodes including
653
     * the current one.
654
     *
655
     * @return \Illuminate\Database\Eloquent\Builder
656
     */
657
    public function ancestorsAndSelf(): Builder
658
    {
659
        return $this->newNestedSetQuery()
660
                ->where($this->getLeftColumnName(), '<=', $this->getLeft())
661
                ->where($this->getRightColumnName(), '>=', $this->getRight());
662
    }
663
664
    /**
665
     * Get all the ancestor chain from the database including the current node.
666
     *
667
     * @param  array  $columns
668
     * @return \Illuminate\Database\Eloquent\Collection
669
     */
670
    public function getAncestorsAndSelf($columns = ['*']): Collection
671
    {
672
        return $this->ancestorsAndSelf()->get($columns);
673
    }
674
675
    /**
676
     * Get all the ancestor chain from the database including the current node
677
     * but without the root node.
678
     *
679
     * @param  array  $columns
680
     * @return \Illuminate\Database\Eloquent\Collection
681
     */
682
    public function getAncestorsAndSelfWithoutRoot($columns = ['*']): Collection
683
    {
684
        return $this->ancestorsAndSelf()->withoutRoot()->get($columns);
685
    }
686
687
    /**
688
     * Instance scope which targets all the ancestor chain nodes excluding
689
     * the current one.
690
     *
691
     * @return \Illuminate\Database\Eloquent\Builder
692
     */
693
    public function ancestors(): Builder
694
    {
695
        return $this->ancestorsAndSelf()->withoutSelf();
696
    }
697
698
    /**
699
     * Get all the ancestor chain from the database excluding the current node.
700
     *
701
     * @param  array  $columns
702
     * @return \Illuminate\Database\Eloquent\Collection
703
     */
704
    public function getAncestors($columns = ['*']): Collection
705
    {
706
        return $this->ancestors()->get($columns);
707
    }
708
709
    /**
710
     * Get all the ancestor chain from the database excluding the current node
711
     * and the root node (from the current node's perspective).
712
     *
713
     * @param  array  $columns
714
     * @return \Illuminate\Database\Eloquent\Collection
715
     */
716
    public function getAncestorsWithoutRoot($columns = ['*']): Collection
717
    {
718
        return $this->ancestors()->withoutRoot()->get($columns);
719
    }
720
721
    /**
722
     * Instance scope which targets all children of the parent, including self.
723
     *
724
     * @return \Illuminate\Database\Eloquent\Builder
725
     */
726
    public function siblingsAndSelf(): Builder
727
    {
728
        return $this->newNestedSetQuery()
729
                ->where($this->getParentColumnName(), $this->getParentId());
730
    }
731
732
    /**
733
     * Get all children of the parent, including self.
734
     *
735
     * @param  array  $columns
736
     * @return \Illuminate\Database\Eloquent\Collection
737
     */
738
    public function getSiblingsAndSelf($columns = ['*']): Collection
739
    {
740
        return $this->siblingsAndSelf()->get($columns);
741
    }
742
743
    /**
744
     * Instance scope targeting all children of the parent, except self.
745
     *
746
     * @return \Illuminate\Database\Eloquent\Builder
747
     */
748
    public function siblings(): Builder
749
    {
750
        return $this->siblingsAndSelf()->withoutSelf();
751
    }
752
753
    /**
754
     * Return all children of the parent, except self.
755
     *
756
     * @param  array  $columns
757
     * @return \Illuminate\Database\Eloquent\Collection
758
     */
759
    public function getSiblings($columns = ['*']): Collection
760
    {
761
        return $this->siblings()->get($columns);
762
    }
763
764
    /**
765
     * Instance scope targeting all of its nested children which do not have
766
     * children.
767
     *
768
     * @return \Illuminate\Database\Query\Builder
769
     */
770
    public function leaves(): Builder
771
    {
772
        $grammar = $this->getConnection()->getQueryGrammar();
773
774
        $rgtCol = $grammar->wrap($this->getQualifiedRightColumnName());
775
        $lftCol = $grammar->wrap($this->getQualifiedLeftColumnName());
776
777
        return $this->descendants()
778
                ->whereRaw($rgtCol.' - '.$lftCol.' = 1');
779
    }
780
781
    /**
782
     * Return all of its nested children which do not have children.
783
     *
784
     * @param  array  $columns
785
     * @return \Illuminate\Database\Eloquent\Collection
786
     */
787
    public function getLeaves($columns = ['*']): Collection
788
    {
789
        return $this->leaves()->get($columns);
790
    }
791
792
    /**
793
     * Instance scope targeting all of its nested children which are between the
794
     * root and the leaf nodes (middle branch).
795
     *
796
     * @return \Illuminate\Database\Query\Builder
797
     */
798
    public function trunks(): Builder
799
    {
800
        $grammar = $this->getConnection()->getQueryGrammar();
801
802
        $rgtCol = $grammar->wrap($this->getQualifiedRightColumnName());
803
        $lftCol = $grammar->wrap($this->getQualifiedLeftColumnName());
804
805
        return $this->descendants()
806
                ->whereNotNull($this->getQualifiedParentColumnName())
807
                ->whereRaw($rgtCol.' - '.$lftCol.' != 1');
808
    }
809
810
    /**
811
     * Return all of its nested children which are trunks.
812
     *
813
     * @param  array  $columns
814
     * @return \Illuminate\Database\Eloquent\Collection
815
     */
816
    public function getTrunks($columns = ['*']): Collection
817
    {
818
        return $this->trunks()->get($columns);
819
    }
820
821
    /**
822
     * Scope targeting itself and all of its nested children.
823
     *
824
     * @return \Illuminate\Database\Query\Builder
825
     */
826
    public function descendantsAndSelf(): Builder
827
    {
828
        return $this->newNestedSetQuery()
829
                ->where($this->getLeftColumnName(), '>=', $this->getLeft())
830
                ->where($this->getLeftColumnName(), '<', $this->getRight());
831
    }
832
833
    /**
834
     * Retrieve all nested children an self.
835
     *
836
     * @param  array  $columns
837
     * @return \Illuminate\Database\Eloquent\Collection
838
     */
839
    public function getDescendantsAndSelf($columns = ['*']): Collection
840
    {
841
        if (is_array($columns)) {
842
            return $this->descendantsAndSelf()->get($columns);
843
        }
844
845
        $arguments = func_get_args();
846
847
        $limit = intval(array_shift($arguments));
848
        $columns = array_shift($arguments) ?: ['*'];
849
850
        return $this->descendantsAndSelf()->limitDepth($limit)->get($columns);
851
    }
852
853
    /**
854
     * Set of all children & nested children.
855
     *
856
     * @return \Illuminate\Database\Query\Builder
857
     */
858
    public function descendants(): Builder
859
    {
860
        return $this->descendantsAndSelf()->withoutSelf();
861
    }
862
863
    /**
864
     * Retrieve all of its children & nested children.
865
     *
866
     * @param  array  $columns
867
     * @return \Illuminate\Database\Eloquent\Collection
868
     */
869
    public function getDescendants($columns = ['*']): Collection
870
    {
871
        if (is_array($columns)) {
872
            return $this->descendants()->get($columns);
873
        }
874
875
        $arguments = func_get_args();
876
877
        $limit = intval(array_shift($arguments));
878
        $columns = array_shift($arguments) ?: ['*'];
879
880
        return $this->descendants()->limitDepth($limit)->get($columns);
881
    }
882
883
    /**
884
     * Set of "immediate" descendants (aka children), alias for the children relation.
885
     *
886
     * @return \Illuminate\Database\Query\Builder
887
     */
888
    public function immediateDescendants(): Builder
889
    {
890
        return $this->children();
891
    }
892
893
    /**
894
     * Retrive all of its "immediate" descendants.
895
     *
896
     * @param array   $columns
897
     * @return \Illuminate\Database\Eloquent\Collection
898
     */
899
    public function getImmediateDescendants($columns = ['*']): Collection
900
    {
901
        return $this->children()->get($columns);
902
    }
903
904
    /**
905
    * Returns the level of this node in the tree.
906
    * Root level is 0.
907
    *
908
    * @return int
909
    */
910
    public function getLevel(): int
911
    {
912
        if (is_null($this->getParentId())) {
0 ignored issues
show
The condition is_null($this->getParentId()) is always false.
Loading history...
913
            return 0;
914
        }
915
916
        return $this->computeLevel();
917
    }
918
919
    /**
920
     * Returns true if node is a descendant.
921
     *
922
     * @param \Illuminate\Database\Eloquent\Model
923
     * @return boolean
924
     */
925
    public function isDescendantOf($other): bool
926
    {
927
        return (
928
      $this->getLeft() > $other->getLeft() &&
929
      $this->getLeft() < $other->getRight() &&
930
      $this->inSameScope($other)
931
    );
932
    }
933
934
    /**
935
     * Returns true if node is self or a descendant.
936
     *
937
     * @param \Illuminate\Database\Eloquent\Model
938
     * @return boolean
939
     */
940
    public function isSelfOrDescendantOf($other): bool
941
    {
942
        return (
943
      $this->getLeft() >= $other->getLeft() &&
944
      $this->getLeft() < $other->getRight() &&
945
      $this->inSameScope($other)
946
    );
947
    }
948
949
    /**
950
     * Returns true if node is an ancestor.
951
     *
952
     * @param \Illuminate\Database\Eloquent\Model
953
     * @return boolean
954
     */
955
    public function isAncestorOf($other): bool
956
    {
957
        return (
958
      $this->getLeft() < $other->getLeft() &&
959
      $this->getRight() > $other->getLeft() &&
960
      $this->inSameScope($other)
961
    );
962
    }
963
964
    /**
965
     * Returns true if node is self or an ancestor.
966
     *
967
     * @param \Illuminate\Database\Eloquent\Model
968
     * @return boolean
969
     */
970
    public function isSelfOrAncestorOf($other): bool
971
    {
972
        return (
973
      $this->getLeft() <= $other->getLeft() &&
974
      $this->getRight() > $other->getLeft() &&
975
      $this->inSameScope($other)
976
    );
977
    }
978
979
    /**
980
     * Returns the first sibling to the left.
981
     *
982
     * @return \Illuminate\Database\Eloquent\Model
983
     */
984
    public function getLeftSibling(): ?Model
985
    {
986
        return $this->siblings()
987
                ->where($this->getLeftColumnName(), '<', $this->getLeft())
988
                ->orderBy($this->getOrderColumnName(), 'desc')
989
                ->get()
990
                ->last();
991
    }
992
993
    /**
994
     * Returns the first sibling to the right.
995
     *
996
     * @return \Illuminate\Database\Eloquent\Model
997
     */
998
    public function getRightSibling(): ?Model
999
    {
1000
        return $this->siblings()
1001
                ->where($this->getLeftColumnName(), '>', $this->getLeft())
1002
                ->first();
1003
    }
1004
1005
    /**
1006
     * Find the left sibling and move to left of it.
1007
     *
1008
     * @return \Illuminate\Database\Eloquent\Model
1009
     */
1010
    public function moveLeft(): Model
1011
    {
1012
        return $this->moveToLeftOf($this->getLeftSibling());
1013
    }
1014
1015
    /**
1016
     * Find the right sibling and move to the right of it.
1017
     *
1018
     * @return \Illuminate\Database\Eloquent\Model
1019
     */
1020
    public function moveRight(): Model
1021
    {
1022
        return $this->moveToRightOf($this->getRightSibling());
1023
    }
1024
1025
    /**
1026
     * Move to the node to the left of ...
1027
     *
1028
     * @return \Illuminate\Database\Eloquent\Model
1029
     */
1030
    public function moveToLeftOf($node): Model
1031
    {
1032
        return $this->moveTo($node, 'left');
1033
    }
1034
1035
    /**
1036
     * Move to the node to the right of ...
1037
     *
1038
     * @return \Illuminate\Database\Eloquent\Model
1039
     */
1040
    public function moveToRightOf($node): Model
1041
    {
1042
        return $this->moveTo($node, 'right');
1043
    }
1044
1045
    /**
1046
     * Alias for moveToRightOf
1047
     *
1048
     * @return \Illuminate\Database\Eloquent\Model
1049
     */
1050
    public function makeNextSiblingOf($node): Model
1051
    {
1052
        return $this->moveToRightOf($node);
1053
    }
1054
1055
    /**
1056
     * Alias for moveToRightOf
1057
     *
1058
     * @return \Illuminate\Database\Eloquent\Model
1059
     */
1060
    public function makeSiblingOf($node): Model
1061
    {
1062
        return $this->moveToRightOf($node);
1063
    }
1064
1065
    /**
1066
     * Alias for moveToLeftOf
1067
     *
1068
     * @return \Illuminate\Database\Eloquent\Model
1069
     */
1070
    public function makePreviousSiblingOf($node): Model
1071
    {
1072
        return $this->moveToLeftOf($node);
1073
    }
1074
1075
    /**
1076
     * Make the node a child of ...
1077
     *
1078
     * @return \Illuminate\Database\Eloquent\Model
1079
     */
1080
    public function makeChildOf($node): self
1081
    {
1082
        return $this->moveTo($node, 'child');
1083
    }
1084
1085
    /**
1086
     * Make the node the first child of ...
1087
     *
1088
     * @return \Illuminate\Database\Eloquent\Model
1089
     */
1090
    public function makeFirstChildOf($node): Model
1091
    {
1092
        if ($node->children()->count() == 0) {
1093
            return $this->makeChildOf($node);
1094
        }
1095
1096
        return $this->moveToLeftOf($node->children()->first());
1097
    }
1098
1099
    /**
1100
     * Make the node the last child of ...
1101
     *
1102
     * @return \Illuminate\Database\Eloquent\Model
1103
     */
1104
    public function makeLastChildOf($node): Model
1105
    {
1106
        return $this->makeChildOf($node);
1107
    }
1108
1109
    /**
1110
     * Make current node a root node.
1111
     *
1112
     * @return \Illuminate\Database\Eloquent\Model
1113
     */
1114
    public function makeRoot(): Model
1115
    {
1116
        return $this->moveTo($this, 'root');
0 ignored issues
show
$this of type Encima\Albero\HasNestedSets is incompatible with the type Illuminate\Database\Eloquent\Model|integer expected by parameter $target of Encima\Albero\HasNestedSets::moveTo(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1116
        return $this->moveTo(/** @scrutinizer ignore-type */ $this, 'root');
Loading history...
1117
    }
1118
1119
    /**
1120
     * Equals?
1121
     *
1122
     * @param \Illuminate\Database\Eloquent\Model
1123
     * @return boolean
1124
     */
1125
    public function equals($node): bool
1126
    {
1127
        return ($this == $node);
1128
    }
1129
1130
    /**
1131
     * Checkes if the given node is in the same scope as the current one.
1132
     *
1133
     * @param \Illuminate\Database\Eloquent\Model
1134
     * @return boolean
1135
     */
1136
    public function inSameScope($other): bool
1137
    {
1138
        foreach ($this->getScopedColumns() as $fld) {
1139
            if ($this->{$fld} != $other->{$fld}) {
1140
                return false;
1141
            }
1142
        }
1143
1144
        return true;
1145
    }
1146
1147
    /**
1148
     * Checks wether the given node is a descendant of itself. Basically, whether
1149
     * its in the subtree defined by the left and right indices.
1150
     *
1151
     * @param \Illuminate\Database\Eloquent\Model
1152
     * @return boolean
1153
     */
1154
    public function insideSubtree($node): bool
1155
    {
1156
        return (
1157
            $this->getLeft() >= $node->getLeft() &&
1158
            $this->getLeft() <= $node->getRight() &&
1159
            $this->getRight() >= $node->getLeft() &&
1160
            $this->getRight() <= $node->getRight()
1161
        );
1162
    }
1163
1164
    /**
1165
     * Sets default values for left and right fields.
1166
     *
1167
     * @return void
1168
     */
1169
    public function setDefaultLeftAndRight(): void
1170
    {
1171
        $withHighestRight = $this->newNestedSetQuery()->reOrderBy($this->getRightColumnName(), 'desc')->take(1)->sharedLock()->first();
1172
1173
        $maxRgt = 0;
1174
        if (!is_null($withHighestRight)) {
1175
            $maxRgt = $withHighestRight->getRight();
1176
        }
1177
1178
        $this->setAttribute($this->getLeftColumnName(), $maxRgt + 1);
1179
        $this->setAttribute($this->getRightColumnName(), $maxRgt + 2);
1180
    }
1181
1182
    /**
1183
     * Store the parent_id if the attribute is modified so as we are able to move
1184
     * the node to this new parent after saving.
1185
     *
1186
     * @return void
1187
     */
1188
    public function storeNewParent(): void
1189
    {
1190
        if ($this->isDirty($this->getParentColumnName()) && ($this->exists || !$this->isRoot())) {
1191
            static::$moveToNewParentId = $this->getParentId();
1192
        } else {
1193
            static::$moveToNewParentId = false;
1194
        }
1195
    }
1196
1197
    /**
1198
     * Move to the new parent if appropiate.
1199
     *
1200
     * @return void
1201
     */
1202
    public function moveToNewParent(): void
1203
    {
1204
        $pid = static::$moveToNewParentId;
1205
1206
        if (is_null($pid)) {
1207
            $this->makeRoot();
1208
        } elseif ($pid !== false) {
1209
            $this->makeChildOf($pid);
1210
        }
1211
    }
1212
1213
    /**
1214
     * Sets the depth attribute
1215
     *
1216
     * @return \Illuminate\Database\Eloquent\Model
1217
     */
1218
    public function setDepth(): Model
1219
    {
1220
        $self = $this;
1221
1222
        $this->getConnection()->transaction(function () use ($self) {
1223
            $self->reload();
1224
1225
            $level = $self->getLevel();
1226
1227
            $self->newNestedSetQuery()->where($self->getKeyName(), '=', $self->getKey())->update([$self->getDepthColumnName() => $level]);
1228
            $self->setAttribute($self->getDepthColumnName(), $level);
1229
        });
1230
1231
        return $this;
1232
    }
1233
1234
    /**
1235
     * Sets the depth attribute for the current node and all of its descendants.
1236
     *
1237
     * @return \Illuminate\Database\Eloquent\Model
1238
     */
1239
    public function setDepthWithSubtree(): Model
1240
    {
1241
        $self = $this;
1242
1243
        $this->getConnection()->transaction(function () use ($self) {
1244
            $self->reload();
1245
1246
            $self->descendantsAndSelf()->select($self->getKeyName())->lockForUpdate()->get();
1247
1248
            $oldDepth = !is_null($self->getDepth()) ? $self->getDepth() : 0;
1249
1250
            $newDepth = $self->getLevel();
1251
1252
            $self->newNestedSetQuery()->where($self->getKeyName(), '=', $self->getKey())->update([$self->getDepthColumnName() => $newDepth]);
1253
            $self->setAttribute($self->getDepthColumnName(), $newDepth);
1254
1255
            $diff = $newDepth - $oldDepth;
1256
            if (!$self->isLeaf() && $diff != 0) {
1257
                $self->descendants()->increment($self->getDepthColumnName(), $diff);
1258
            }
1259
        });
1260
1261
        return $this;
1262
    }
1263
1264
    /**
1265
     * Prunes a branch off the tree, shifting all the elements on the right
1266
     * back to the left so the counts work.
1267
     *
1268
     * @return void;
1269
     */
1270
    public function destroyDescendants(): void
1271
    {
1272
        if (is_null($this->getRight()) || is_null($this->getLeft())) {
0 ignored issues
show
The condition is_null($this->getLeft()) is always false.
Loading history...
1273
            return;
1274
        }
1275
1276
        $self = $this;
1277
1278
        $this->getConnection()->transaction(function () use ($self) {
1279
            $self->reload();
1280
1281
            $lftCol = $self->getLeftColumnName();
1282
            $rgtCol = $self->getRightColumnName();
1283
            $lft = $self->getLeft();
1284
            $rgt = $self->getRight();
1285
1286
            // Apply a lock to the rows which fall past the deletion point
1287
            $self->newNestedSetQuery()->where($lftCol, '>=', $lft)->select($self->getKeyName())->lockForUpdate()->get();
1288
1289
            // Prune children
1290
            $self->newNestedSetQuery()->where($lftCol, '>', $lft)->where($rgtCol, '<', $rgt)->delete();
1291
1292
            // Update left and right indexes for the remaining nodes
1293
            $diff = $rgt - $lft + 1;
1294
1295
            $self->newNestedSetQuery()->where($lftCol, '>', $rgt)->decrement($lftCol, $diff);
1296
            $self->newNestedSetQuery()->where($rgtCol, '>', $rgt)->decrement($rgtCol, $diff);
1297
        });
1298
    }
1299
1300
    /**
1301
     * "Makes room" for the the current node between its siblings.
1302
     *
1303
     * @return void
1304
     */
1305
    public function shiftSiblingsForRestore(): void
1306
    {
1307
        if (is_null($this->getRight()) || is_null($this->getLeft())) {
0 ignored issues
show
The condition is_null($this->getLeft()) is always false.
Loading history...
1308
            return;
1309
        }
1310
1311
        $self = $this;
1312
1313
        $this->getConnection()->transaction(function () use ($self) {
1314
            $lftCol = $self->getLeftColumnName();
1315
            $rgtCol = $self->getRightColumnName();
1316
            $lft = $self->getLeft();
1317
            $rgt = $self->getRight();
1318
1319
            $diff = $rgt - $lft + 1;
1320
1321
            $self->newNestedSetQuery()->where($lftCol, '>=', $lft)->increment($lftCol, $diff);
1322
            $self->newNestedSetQuery()->where($rgtCol, '>=', $lft)->increment($rgtCol, $diff);
1323
        });
1324
    }
1325
1326
    /**
1327
     * Restores all of the current node's descendants.
1328
     *
1329
     * @return void
1330
     */
1331
    public function restoreDescendants(): void
1332
    {
1333
        if (is_null($this->getRight()) || is_null($this->getLeft())) {
0 ignored issues
show
The condition is_null($this->getLeft()) is always false.
Loading history...
1334
            return;
1335
        }
1336
1337
        $self = $this;
1338
1339
        $this->getConnection()->transaction(function () use ($self) {
1340
            $self->newNestedSetQuery()
1341
        ->withTrashed()
1342
        ->where($self->getLeftColumnName(), '>', $self->getLeft())
1343
        ->where($self->getRightColumnName(), '<', $self->getRight())
1344
        ->update([
1345
          $self->getDeletedAtColumn() => null,
0 ignored issues
show
It seems like getDeletedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1345
          $self->/** @scrutinizer ignore-call */ 
1346
                 getDeletedAtColumn() => null,
Loading history...
1346
          $self->getUpdatedAtColumn() => $self->{$self->getUpdatedAtColumn()},
0 ignored issues
show
It seems like getUpdatedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1346
          $self->/** @scrutinizer ignore-call */ 
1347
                 getUpdatedAtColumn() => $self->{$self->getUpdatedAtColumn()},
Loading history...
1347
        ]);
1348
        });
1349
    }
1350
1351
    /**
1352
     * Return an key-value array indicating the node's depth with $seperator
1353
     *
1354
     * @return Array
1355
     */
1356
    public static function getNestedList($column, $key = null, $seperator = ' '): array
1357
    {
1358
        $instance = new static();
1359
1360
        $key = $key ?: $instance->getKeyName();
1361
        $depthColumn = $instance->getDepthColumnName();
1362
1363
        $nodes = $instance->newNestedSetQuery()->get()->toArray();
1364
1365
        return array_combine(array_map(function ($node) use ($key) {
1366
            return $node[$key];
1367
        }, $nodes), array_map(function ($node) use ($seperator, $depthColumn, $column) {
1368
            return str_repeat($seperator, $node[$depthColumn]).$node[$column];
1369
        }, $nodes));
1370
    }
1371
1372
    /**
1373
     * Maps the provided tree structure into the database using the current node
1374
     * as the parent. The provided tree structure will be inserted/updated as the
1375
     * descendancy subtree of the current node instance.
1376
     *
1377
     * @param   array|\Illuminate\Support\Contracts\ArrayableInterface
1378
     * @return  boolean
1379
     */
1380
    public function makeTree($nodeList): bool
1381
    {
1382
        $mapper = new SetMapper($this);
0 ignored issues
show
$this of type Encima\Albero\HasNestedSets is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $node of Encima\Albero\SetMapper::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1382
        $mapper = new SetMapper(/** @scrutinizer ignore-type */ $this);
Loading history...
1383
1384
        return $mapper->map($nodeList);
1385
    }
1386
1387
    /**
1388
     * Main move method. Here we handle all node movements with the corresponding
1389
     * lft/rgt index updates.
1390
     *
1391
     * @param \Illuminate\Database\Eloquent\Model|int $target
1392
     * @param string        $position
1393
     * @return \Illuminate\Database\Eloquent\Model
1394
     */
1395
    protected function moveTo($target, $position): self
1396
    {
1397
        return Move::to($this, $target, $position);
0 ignored issues
show
$this of type Encima\Albero\HasNestedSets is incompatible with the type Illuminate\Database\Eloquent\Model expected by parameter $node of Encima\Albero\Move::to(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1397
        return Move::to(/** @scrutinizer ignore-type */ $this, $target, $position);
Loading history...
1398
    }
1399
1400
    /**
1401
     * Compute current node level. If could not move past ourseleves return
1402
     * our ancestor count, otherwhise get the first parent level + the computed
1403
     * nesting.
1404
     *
1405
     * @return integer
1406
     */
1407
    protected function computeLevel(): int
1408
    {
1409
        list($node, $nesting) = $this->determineDepth($this);
1410
1411
        if ($node->equals($this)) {
1412
            return $this->ancestors()->count();
1413
        }
1414
1415
        return $node->getLevel() + $nesting;
1416
    }
1417
1418
    /**
1419
     * Return an array with the last node we could reach and its nesting level
1420
     *
1421
     * @param   Baum\Node $node
0 ignored issues
show
The type Encima\Albero\Baum\Node was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1422
     * @param   integer   $nesting
1423
     * @return  array
1424
     */
1425
    protected function determineDepth($node, $nesting = 0): array
1426
    {
1427
        // Traverse back up the ancestry chain and add to the nesting level count
1428
        while ($parent = $node->parent()->first()) {
1429
            $nesting = $nesting + 1;
1430
1431
            $node = $parent;
1432
        }
1433
1434
        return [$node, $nesting];
1435
    }
1436
1437
    /**
1438
     * Reloads the model from the database.
1439
     *
1440
     * @return \Illuminate\Database\Eloquent\Model
1441
     *
1442
     * @throws ModelNotFoundException
1443
     */
1444
    public function reload(): Model
1445
    {
1446
        if ($this->exists || ($this->areSoftDeletesEnabled() && $this->trashed())) {
0 ignored issues
show
It seems like trashed() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1446
        if ($this->exists || ($this->areSoftDeletesEnabled() && $this->/** @scrutinizer ignore-call */ trashed())) {
Loading history...
1447
            $fresh = $this->getFreshInstance();
1448
1449
            if (is_null($fresh)) {
1450
                throw (new ModelNotFoundException())->setModel(get_called_class());
1451
            }
1452
1453
            $this->setRawAttributes($fresh->getAttributes(), true);
0 ignored issues
show
The method setRawAttributes() does not exist on Encima\Albero\HasNestedSets. Did you maybe mean setAttribute()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1453
            $this->/** @scrutinizer ignore-call */ 
1454
                   setRawAttributes($fresh->getAttributes(), true);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1454
1455
            $this->setRelations($fresh->getRelations());
0 ignored issues
show
It seems like setRelations() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1455
            $this->/** @scrutinizer ignore-call */ 
1456
                   setRelations($fresh->getRelations());
Loading history...
1456
1457
            $this->exists = $fresh->exists;
1458
        } else {
1459
            // Revert changes if model is not persisted
1460
            $this->attributes = $this->original;
1461
        }
1462
1463
1464
        return $this;
1465
    }
1466
1467
    /**
1468
     * Get the observable event names.
1469
     *
1470
     * @return array
1471
     */
1472
    public function getObservableEvents(): array
1473
    {
1474
        return array_merge(['moving', 'moved'], parent::getObservableEvents());
1475
    }
1476
1477
    /**
1478
     * Register a moving model event with the dispatcher.
1479
     *
1480
     * @param  Closure|string  $callback
0 ignored issues
show
The type Encima\Albero\Closure was not found. Did you mean Closure? If so, make sure to prefix the type with \.
Loading history...
1481
     * @return void
1482
     */
1483
    public static function moving($callback, $priority = 0): void
1484
    {
1485
        static::registerModelEvent('moving', $callback, $priority);
1486
    }
1487
1488
    /**
1489
     * Register a moved model event with the dispatcher.
1490
     *
1491
     * @param  Closure|string  $callback
1492
     * @return void
1493
     */
1494
    public static function moved($callback, $priority = 0): void
1495
    {
1496
        static::registerModelEvent('moved', $callback, $priority);
1497
    }
1498
1499
    /**
1500
     * Get a new query builder instance for the connection.
1501
     *
1502
     * @return \Encima\Albero\Extensions\Query\Builder
1503
     */
1504
    protected function newBaseQueryBuilder(): QueryBuilder
1505
    {
1506
        $conn = $this->getConnection();
1507
1508
        $grammar = $conn->getQueryGrammar();
1509
1510
        return new QueryBuilder($conn, $grammar, $conn->getPostProcessor());
1511
    }
1512
1513
    /**
1514
     * Returns a fresh instance from the database.
1515
     *
1516
     * @return \Illuminate\Database\Eloquent\Model
1517
     */
1518
    protected function getFreshInstance(): ?Model
1519
    {
1520
        if ($this->areSoftDeletesEnabled()) {
1521
            return static::withTrashed()->find($this->getKey());
1522
        }
1523
1524
        return static::find($this->getKey());
1525
    }
1526
1527
    /**
1528
     * Returns wether soft delete functionality is enabled on the model or not.
1529
     *
1530
     * @return boolean
1531
     */
1532
    public function areSoftDeletesEnabled(): bool
1533
    {
1534
        // To determine if there's a global soft delete scope defined we must
1535
        // first determine if there are any, to workaround a non-existent key error.
1536
        $globalScopes = $this->getGlobalScopes();
0 ignored issues
show
It seems like getGlobalScopes() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1536
        /** @scrutinizer ignore-call */ 
1537
        $globalScopes = $this->getGlobalScopes();
Loading history...
1537
1538
        if (count($globalScopes) === 0) {
1539
            return false;
1540
        }
1541
1542
        // Now that we're sure that the calling class has some kind of global scope
1543
        // we check for the SoftDeletingScope existance
1544
        return static::hasGlobalScope(new SoftDeletingScope());
1545
    }
1546
1547
    /**
1548
     * Static method which returns wether soft delete functionality is enabled
1549
     * on the model.
1550
     *
1551
     * @return boolean
1552
     */
1553
    public static function softDeletesEnabled(): bool
1554
    {
1555
        return (new static())->areSoftDeletesEnabled();
1556
    }
1557
}
1558