NestedSet NodeTrait.php

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

 

posted on 2024-04-03 11:25  刘应杰  阅读(10)  评论(0编辑  收藏  举报

导航