|   1:  | <?php declare(strict_types=1); | 
|   2:  |  | 
|   3:  | namespace Salient\Core\Concern; | 
|   4:  |  | 
|   5:  | use Salient\Contract\Core\Entity\Treeable; | 
|   6:  | use Salient\Core\Reflection\ClassReflection; | 
|   7:  | use Salient\Utility\Exception\ShouldNotHappenException; | 
|   8:  | use InvalidArgumentException; | 
|   9:  |  | 
|  10:  |  | 
|  11:  |  | 
|  12:  |  | 
|  13:  |  | 
|  14:  |  | 
|  15:  | trait TreeableTrait | 
|  16:  | { | 
|  17:  |      | 
|  18:  |     private static array $TreeableProperties; | 
|  19:  |  | 
|  20:  |      | 
|  21:  |  | 
|  22:  |  | 
|  23:  |     public static function getRelationships(): array | 
|  24:  |     { | 
|  25:  |         return []; | 
|  26:  |     } | 
|  27:  |  | 
|  28:  |      | 
|  29:  |  | 
|  30:  |  | 
|  31:  |     public function getParent() | 
|  32:  |     { | 
|  33:  |         [$_parent] = self::$TreeableProperties[static::class] | 
|  34:  |             ?? self::getTreeableProperties(); | 
|  35:  |         return $this->{$_parent} ?? null; | 
|  36:  |     } | 
|  37:  |  | 
|  38:  |      | 
|  39:  |  | 
|  40:  |  | 
|  41:  |     public function getChildren(): array | 
|  42:  |     { | 
|  43:  |         [, $_children] = self::$TreeableProperties[static::class] | 
|  44:  |             ?? self::getTreeableProperties(); | 
|  45:  |         return $this->{$_children} ?? []; | 
|  46:  |     } | 
|  47:  |  | 
|  48:  |      | 
|  49:  |  | 
|  50:  |  | 
|  51:  |     public function setParent($parent) | 
|  52:  |     { | 
|  53:  |         [$_parent, $_children] = self::$TreeableProperties[static::class] | 
|  54:  |             ?? self::getTreeableProperties(); | 
|  55:  |  | 
|  56:  |         if ($parent === ($this->{$_parent} ?? null) && ( | 
|  57:  |             $parent === null | 
|  58:  |             || in_array($this, $parent->{$_children} ?? [], true) | 
|  59:  |         )) { | 
|  60:  |             return $this; | 
|  61:  |         } | 
|  62:  |  | 
|  63:  |         if (isset($this->{$_parent})) { | 
|  64:  |             $this->{$_parent}->{$_children} = array_values(array_filter( | 
|  65:  |                 $this->{$_parent}->{$_children} ?? [], | 
|  66:  |                 fn($child) => $child !== $this, | 
|  67:  |             )); | 
|  68:  |         } | 
|  69:  |  | 
|  70:  |         $this->{$_parent} = $parent; | 
|  71:  |         if ($parent !== null) { | 
|  72:  |             $parent->{$_children}[] = $this; | 
|  73:  |         } | 
|  74:  |         return $this; | 
|  75:  |     } | 
|  76:  |  | 
|  77:  |      | 
|  78:  |  | 
|  79:  |  | 
|  80:  |     public function addChild($child) | 
|  81:  |     { | 
|  82:  |         return $child->setParent($this); | 
|  83:  |     } | 
|  84:  |  | 
|  85:  |      | 
|  86:  |  | 
|  87:  |  | 
|  88:  |     public function removeChild($child) | 
|  89:  |     { | 
|  90:  |         if ($child->getParent() !== $this) { | 
|  91:  |             throw new InvalidArgumentException('Invalid child'); | 
|  92:  |         } | 
|  93:  |         return $child->setParent(null); | 
|  94:  |     } | 
|  95:  |  | 
|  96:  |      | 
|  97:  |  | 
|  98:  |  | 
|  99:  |     public function getDepth(): int | 
| 100:  |     { | 
| 101:  |         [$_parent] = self::$TreeableProperties[static::class] | 
| 102:  |             ?? self::getTreeableProperties(); | 
| 103:  |         $depth = 0; | 
| 104:  |         $parent = $this; | 
| 105:  |         while ($parent = $parent->{$_parent}) { | 
| 106:  |             $depth++; | 
| 107:  |         } | 
| 108:  |         return $depth; | 
| 109:  |     } | 
| 110:  |  | 
| 111:  |      | 
| 112:  |  | 
| 113:  |  | 
| 114:  |     public function countDescendants(): int | 
| 115:  |     { | 
| 116:  |         [, $_children] = self::$TreeableProperties[static::class] | 
| 117:  |             ?? self::getTreeableProperties(); | 
| 118:  |         return $this->doCountDescendants($_children); | 
| 119:  |     } | 
| 120:  |  | 
| 121:  |     private function doCountDescendants(string $_children): int | 
| 122:  |     { | 
| 123:  |          | 
| 124:  |         $children = $this->{$_children} ?? []; | 
| 125:  |         if (!$children) { | 
| 126:  |             return 0; | 
| 127:  |         } | 
| 128:  |         $count = 0; | 
| 129:  |         foreach ($children as $child) { | 
| 130:  |             $count += 1 + $child->doCountDescendants($_children); | 
| 131:  |         } | 
| 132:  |         return $count; | 
| 133:  |     } | 
| 134:  |  | 
| 135:  |      | 
| 136:  |  | 
| 137:  |  | 
| 138:  |     private static function getTreeableProperties(): array | 
| 139:  |     { | 
| 140:  |         $class = new ClassReflection(static::class); | 
| 141:  |         if ($class->isTreeable()) { | 
| 142:  |             return self::$TreeableProperties[static::class] = [ | 
| 143:  |                 $class->getParentProperty(), | 
| 144:  |                 $class->getChildrenProperty(), | 
| 145:  |             ]; | 
| 146:  |         } | 
| 147:  |  | 
| 148:  |          | 
| 149:  |         throw new ShouldNotHappenException(sprintf( | 
| 150:  |             '%s does not implement %s', | 
| 151:  |             static::class, | 
| 152:  |             Treeable::class, | 
| 153:  |         )); | 
| 154:  |          | 
| 155:  |     } | 
| 156:  | } | 
| 157:  |  |