1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Umbrellio\LTree\Helpers; |
||||||
6 | |||||||
7 | use Illuminate\Database\Eloquent\Collection; |
||||||
8 | use Illuminate\Database\Eloquent\Model; |
||||||
9 | use InvalidArgumentException; |
||||||
10 | use Umbrellio\Common\Contracts\AbstractPresenter; |
||||||
11 | use Umbrellio\LTree\Collections\LTreeCollection; |
||||||
12 | use Umbrellio\LTree\Interfaces\LTreeInterface; |
||||||
13 | use Umbrellio\LTree\Interfaces\ModelInterface; |
||||||
14 | |||||||
15 | /** |
||||||
16 | * @property LTreeInterface|ModelInterface|Model $model |
||||||
17 | */ |
||||||
18 | class LTreeNode extends AbstractPresenter |
||||||
19 | { |
||||||
20 | protected $parent; |
||||||
21 | protected $children; |
||||||
22 | |||||||
23 | public function __construct($model = null) |
||||||
24 | { |
||||||
25 | parent::__construct($model); |
||||||
26 | } |
||||||
27 | |||||||
28 | public function isRoot(): bool |
||||||
29 | { |
||||||
30 | return $this->model === null; |
||||||
31 | } |
||||||
32 | |||||||
33 | public function getParent(): ?self |
||||||
34 | { |
||||||
35 | return $this->parent; |
||||||
36 | } |
||||||
37 | |||||||
38 | public function setParent(?self $parent): void |
||||||
39 | { |
||||||
40 | $this->parent = $parent; |
||||||
41 | } |
||||||
42 | |||||||
43 | public function addChild(self $node): void |
||||||
44 | { |
||||||
45 | $this |
||||||
46 | ->getChildren() |
||||||
47 | ->add($node); |
||||||
48 | $node->setParent($this); |
||||||
49 | } |
||||||
50 | |||||||
51 | public function getChildren(): Collection |
||||||
52 | { |
||||||
53 | if (!$this->children) { |
||||||
54 | $this->children = new Collection(); |
||||||
55 | } |
||||||
56 | return $this->children; |
||||||
57 | } |
||||||
58 | |||||||
59 | public function countDescendants(): int |
||||||
60 | { |
||||||
61 | return $this |
||||||
62 | ->getChildren() |
||||||
63 | ->reduce( |
||||||
64 | static function (int $count, self $node) { |
||||||
65 | return $count + $node->countDescendants(); |
||||||
66 | }, |
||||||
67 | $this |
||||||
68 | ->getChildren() |
||||||
69 | ->count() |
||||||
70 | ); |
||||||
71 | } |
||||||
72 | |||||||
73 | public function findInTree(int $id): ?self |
||||||
74 | { |
||||||
75 | if (!$this->isRoot() && $this->model->getKey() === $id) { |
||||||
0 ignored issues
–
show
Bug
introduced
by
![]() The method
getKey() does not exist on Umbrellio\LTree\Interfaces\LTreeInterface . Since it exists in all sub-types, consider adding an abstract or default implementation to Umbrellio\LTree\Interfaces\LTreeInterface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
76 | return $this; |
||||||
77 | } |
||||||
78 | foreach ($this->getChildren() as $child) { |
||||||
79 | $result = $child->findInTree($id); |
||||||
80 | if ($result !== null) { |
||||||
81 | return $result; |
||||||
82 | } |
||||||
83 | } |
||||||
84 | return null; |
||||||
85 | } |
||||||
86 | |||||||
87 | public function each(callable $callback): void |
||||||
88 | { |
||||||
89 | if (!$this->isRoot()) { |
||||||
90 | $callback($this); |
||||||
91 | } |
||||||
92 | $this |
||||||
93 | ->getChildren() |
||||||
94 | ->each(static function (self $node) use ($callback) { |
||||||
95 | $node->each($callback); |
||||||
96 | }); |
||||||
97 | } |
||||||
98 | |||||||
99 | public function toCollection(): LTreeCollection |
||||||
100 | { |
||||||
101 | $collection = new LTreeCollection(); |
||||||
102 | $this->each(static function (self $item) use ($collection) { |
||||||
103 | $collection->add($item->model); |
||||||
104 | }); |
||||||
105 | return $collection; |
||||||
106 | } |
||||||
107 | |||||||
108 | public function pathAsString() |
||||||
109 | { |
||||||
110 | return $this->model ? $this->model->getLtreePath(LTreeInterface::AS_STRING) : null; |
||||||
0 ignored issues
–
show
The method
getLtreePath() does not exist on Illuminate\Database\Eloquent\Model . It seems like you code against a sub-type of Illuminate\Database\Eloquent\Model such as Umbrellio\LTree\tests\_data\Models\CategoryStub or Umbrellio\LTree\tests\_data\Models\ProductStub .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() The method
getLtreePath() does not exist on Umbrellio\LTree\Interfaces\ModelInterface . Since it exists in all sub-types, consider adding an abstract or default implementation to Umbrellio\LTree\Interfaces\ModelInterface .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
111 | } |
||||||
112 | |||||||
113 | public function toTreeArray(callable $callback) |
||||||
114 | { |
||||||
115 | return $this->fillTreeArray($this->getChildren(), $callback); |
||||||
116 | } |
||||||
117 | |||||||
118 | /** |
||||||
119 | * Usage sortTree(['name' =>'asc', 'category'=>'desc']) |
||||||
120 | * or callback with arguments ($a, $b) and return -1 | 0 | 1 |
||||||
121 | * @param array|callable $options |
||||||
122 | */ |
||||||
123 | public function sortTree($options) |
||||||
124 | { |
||||||
125 | $children = $this->getChildren(); |
||||||
126 | $callback = $options; |
||||||
127 | if (!is_callable($options)) { |
||||||
128 | $callback = $this->optionsToCallback($options); |
||||||
129 | } |
||||||
130 | $children->each(static function ($child) use ($callback) { |
||||||
131 | /** @var LTreeNode $child */ |
||||||
132 | $child->sortTree($callback); |
||||||
133 | }); |
||||||
134 | $this->children = $children |
||||||
135 | ->sort($callback) |
||||||
136 | ->values(); |
||||||
137 | } |
||||||
138 | |||||||
139 | private function fillTreeArray(iterable $nodes, callable $callback) |
||||||
140 | { |
||||||
141 | $data = []; |
||||||
142 | foreach ($nodes as $node) { |
||||||
143 | $item = $callback($node); |
||||||
144 | $children = $this->fillTreeArray($node->getChildren(), $callback); |
||||||
145 | $item['children'] = $children; |
||||||
146 | $data[] = $item; |
||||||
147 | } |
||||||
148 | return $data; |
||||||
149 | } |
||||||
150 | |||||||
151 | private function optionsToCallback(array $options): callable |
||||||
152 | { |
||||||
153 | return function ($a, $b) use ($options) { |
||||||
154 | foreach ($options as $property => $sort) { |
||||||
155 | if (!in_array(strtolower($sort), ['asc', 'desc'], true)) { |
||||||
156 | throw new InvalidArgumentException("Order '${sort}'' must be asc or desc"); |
||||||
157 | } |
||||||
158 | $order = strtolower($sort) === 'desc' ? -1 : 1; |
||||||
159 | $result = $a->{$property} <=> $b->{$property}; |
||||||
160 | if ($result !== 0) { |
||||||
161 | return $result * $order; |
||||||
162 | } |
||||||
163 | } |
||||||
164 | return 0; |
||||||
165 | }; |
||||||
166 | } |
||||||
167 | } |
||||||
168 |