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 }