1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SleepingOwl\Admin\Repository; |
4
|
|
|
|
5
|
|
|
use Exception; |
6
|
|
|
use Illuminate\Support\Collection; |
7
|
|
|
use SleepingOwl\Admin\Contracts\TreeRepositoryInterface; |
8
|
|
|
|
9
|
|
|
class TreeRepository extends BaseRepository implements TreeRepositoryInterface |
10
|
|
|
{ |
11
|
|
|
/** |
12
|
|
|
* Extrepat/Baum tree type |
13
|
|
|
* https://github.com/etrepat/baum. |
14
|
|
|
*/ |
15
|
|
|
const TreeTypeBaum = 0; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Lasychaser/Laravel-nestedset tree type |
19
|
|
|
* https://github.com/lazychaser/laravel-nestedset. |
20
|
|
|
*/ |
21
|
|
|
const TreeTypeKalnoy = 1; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Simple tree type (with `parent_id` and `order` fields). |
25
|
|
|
*/ |
26
|
|
|
const TreeTypeSimple = 2; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* Tree type. |
30
|
|
|
* @var int |
31
|
|
|
*/ |
32
|
|
|
protected $type; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Parent field name. |
36
|
|
|
* @var string |
37
|
|
|
*/ |
38
|
|
|
protected $parentField = 'parent_id'; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Order field name. |
42
|
|
|
* @var string |
43
|
|
|
*/ |
44
|
|
|
protected $orderField = 'order'; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Root parent id value. |
48
|
|
|
* @var null |
49
|
|
|
*/ |
50
|
|
|
protected $rootParentId = null; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* @param string $class |
54
|
|
|
* |
55
|
|
|
* @return $this |
56
|
|
|
*/ |
57
|
|
|
public function setClass($class) |
58
|
|
|
{ |
59
|
|
|
parent::setClass($class); |
60
|
|
|
|
61
|
|
|
$this->detectType(); |
62
|
|
|
|
63
|
|
|
return $this; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Get tree structure. |
68
|
|
|
* |
69
|
|
|
* @param \Illuminate\Database\Eloquent\Collection $collection |
70
|
|
|
* |
71
|
|
|
* @return mixed |
72
|
|
|
*/ |
73
|
|
|
public function getTree(\Illuminate\Database\Eloquent\Collection $collection) |
74
|
|
|
{ |
75
|
|
|
switch ($this->getType()) { |
76
|
|
|
case static::TreeTypeBaum: |
77
|
|
|
return $collection->toHierarchy(); |
78
|
|
|
break; |
|
|
|
|
79
|
|
|
|
80
|
|
|
case static::TreeTypeKalnoy: |
81
|
|
|
return $collection->toTree(); |
82
|
|
|
break; |
|
|
|
|
83
|
|
|
|
84
|
|
|
case static::TreeTypeSimple: |
85
|
|
|
return $this->createSimpleTree(); |
86
|
|
|
break; |
|
|
|
|
87
|
|
|
} |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Get or set tree type. |
92
|
|
|
* |
93
|
|
|
* @return int |
94
|
|
|
*/ |
95
|
|
|
public function getType() |
96
|
|
|
{ |
97
|
|
|
return $this->type; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @param int $type |
102
|
|
|
* |
103
|
|
|
* @return $this |
104
|
|
|
*/ |
105
|
|
|
public function setType($type) |
106
|
|
|
{ |
107
|
|
|
$this->type = $type; |
108
|
|
|
|
109
|
|
|
return $this; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* @param int $type |
114
|
|
|
* |
115
|
|
|
* @return bool |
116
|
|
|
*/ |
117
|
|
|
public function isType($type) |
118
|
|
|
{ |
119
|
|
|
return $this->type == $type; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Get parent field name. |
124
|
|
|
* |
125
|
|
|
* @return string |
126
|
|
|
*/ |
127
|
|
|
public function getParentField() |
128
|
|
|
{ |
129
|
|
|
return $this->parentField; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param string $parentField |
134
|
|
|
* |
135
|
|
|
* @return $this |
136
|
|
|
*/ |
137
|
|
|
public function setParentField($parentField) |
138
|
|
|
{ |
139
|
|
|
$this->parentField = $parentField; |
140
|
|
|
|
141
|
|
|
return $this; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Get order field name. |
146
|
|
|
* |
147
|
|
|
* @return string |
148
|
|
|
*/ |
149
|
|
|
public function getOrderField() |
150
|
|
|
{ |
151
|
|
|
return $this->orderField; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* @param string $orderField |
156
|
|
|
* |
157
|
|
|
* @return $this |
158
|
|
|
*/ |
159
|
|
|
public function setOrderField($orderField) |
160
|
|
|
{ |
161
|
|
|
$this->orderField = $orderField; |
162
|
|
|
|
163
|
|
|
return $this; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Get or set parent field name. |
168
|
|
|
* |
169
|
|
|
* @return string |
170
|
|
|
*/ |
171
|
|
|
public function getRootParentId() |
172
|
|
|
{ |
173
|
|
|
return $this->rootParentId; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* @param string $rootParentId |
178
|
|
|
* |
179
|
|
|
* @return $this |
180
|
|
|
*/ |
181
|
|
|
public function setRootParentId($rootParentId) |
182
|
|
|
{ |
183
|
|
|
$this->rootParentId = $rootParentId; |
|
|
|
|
184
|
|
|
|
185
|
|
|
return $this; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Reorder tree by $data value. |
190
|
|
|
* |
191
|
|
|
* @param $data |
192
|
|
|
*/ |
193
|
|
|
public function reorder(array $data) |
194
|
|
|
{ |
195
|
|
|
if ($this->isType(static::TreeTypeSimple)) { |
196
|
|
|
$this->recursiveReorderSimple($data, $this->getRootParentId()); |
197
|
|
|
} else { |
198
|
|
|
$left = 1; |
199
|
|
|
foreach ($data as $root) { |
200
|
|
|
$left = $this->recursiveReorder($root, null, $left); |
201
|
|
|
} |
202
|
|
|
} |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** |
206
|
|
|
* Move tree node in nested-set tree type. |
207
|
|
|
* |
208
|
|
|
* @param $id |
209
|
|
|
* @param $parentId |
210
|
|
|
* @param $left |
211
|
|
|
* @param $right |
212
|
|
|
*/ |
213
|
|
|
protected function move($id, $parentId, $left, $right) |
214
|
|
|
{ |
215
|
|
|
$instance = $this->find($id); |
216
|
|
|
$attributes = $instance->getAttributes(); |
|
|
|
|
217
|
|
|
$attributes[$this->getLeftColumn($instance)] = $left; |
218
|
|
|
$attributes[$this->getRightColumn($instance)] = $right; |
219
|
|
|
$attributes[$this->getParentColumn($instance)] = $parentId; |
220
|
|
|
$instance->setRawAttributes($attributes); |
|
|
|
|
221
|
|
|
$instance->save(); |
|
|
|
|
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Get left column name. |
226
|
|
|
* |
227
|
|
|
* @param $instance |
228
|
|
|
* |
229
|
|
|
* @return mixed |
230
|
|
|
* @throws Exception |
231
|
|
|
*/ |
232
|
|
|
protected function getLeftColumn($instance) |
233
|
|
|
{ |
234
|
|
|
$methods = [ |
235
|
|
|
'getLeftColumnName', |
236
|
|
|
'getLftName', |
237
|
|
|
]; |
238
|
|
|
|
239
|
|
|
return $this->callMethods($instance, $methods); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Get right column name. |
244
|
|
|
* |
245
|
|
|
* @param $instance |
246
|
|
|
* |
247
|
|
|
* @return mixed |
248
|
|
|
* @throws Exception |
249
|
|
|
*/ |
250
|
|
|
protected function getRightColumn($instance) |
251
|
|
|
{ |
252
|
|
|
$methods = [ |
253
|
|
|
'getRightColumnName', |
254
|
|
|
'getRgtName', |
255
|
|
|
]; |
256
|
|
|
|
257
|
|
|
return $this->callMethods($instance, $methods); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Get parent column name. |
262
|
|
|
* |
263
|
|
|
* @param $instance |
264
|
|
|
* |
265
|
|
|
* @return mixed |
266
|
|
|
* @throws Exception |
267
|
|
|
*/ |
268
|
|
|
protected function getParentColumn($instance) |
269
|
|
|
{ |
270
|
|
|
$methods = [ |
271
|
|
|
'getParentColumnName', |
272
|
|
|
'getParentIdName', |
273
|
|
|
]; |
274
|
|
|
|
275
|
|
|
return $this->callMethods($instance, $methods); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Detect tree type. |
280
|
|
|
* @return $this |
281
|
|
|
*/ |
282
|
|
|
protected function detectType() |
283
|
|
|
{ |
284
|
|
|
$model = $this->getModel(); |
285
|
|
|
|
286
|
|
|
// Check for package baum/baum |
287
|
|
|
if ($model instanceof \Baum\Node) { |
|
|
|
|
288
|
|
|
return $this->setType(static::TreeTypeBaum); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
// Check for package kalnoy/nestedset |
292
|
|
|
if (class_exists('Kalnoy\Nestedset\Node') and $model instanceof \Kalnoy\Nestedset\Node) { |
|
|
|
|
293
|
|
|
return $this->setType(static::TreeTypeKalnoy); |
294
|
|
|
} elseif (function_exists('trait_uses_recursive') and $traits = trait_uses_recursive($model) and in_array('Kalnoy\Nestedset\NodeTrait', $traits)) { |
|
|
|
|
295
|
|
|
return $this->setType(static::TreeTypeKalnoy); |
296
|
|
|
} elseif ($traits = class_uses($model) and in_array('Kalnoy\Nestedset\NodeTrait', $traits)) { |
|
|
|
|
297
|
|
|
return $this->setType(static::TreeTypeKalnoy); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
return $this->setType(static::TreeTypeSimple); |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Call several methods and get first result. |
305
|
|
|
* |
306
|
|
|
* @param $instance |
307
|
|
|
* @param $methods |
308
|
|
|
* |
309
|
|
|
* @return mixed |
310
|
|
|
* @throws Exception |
311
|
|
|
*/ |
312
|
|
|
protected function callMethods($instance, $methods) |
313
|
|
|
{ |
314
|
|
|
foreach ($methods as $method) { |
315
|
|
|
if (method_exists($instance, $method)) { |
316
|
|
|
return $instance->$method(); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
throw new Exception('Tree type not supported'); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Recursive reorder nested-set tree type. |
325
|
|
|
* |
326
|
|
|
* @param $root |
327
|
|
|
* @param $parentId |
328
|
|
|
* @param $left |
329
|
|
|
* |
330
|
|
|
* @return mixed |
331
|
|
|
*/ |
332
|
|
|
protected function recursiveReorder($root, $parentId, $left) |
333
|
|
|
{ |
334
|
|
|
$right = $left + 1; |
335
|
|
|
$children = array_get($root, 'children', []); |
336
|
|
|
foreach ($children as $child) { |
337
|
|
|
$right = $this->recursiveReorder($child, $root['id'], $right); |
338
|
|
|
} |
339
|
|
|
$this->move($root['id'], $parentId, $left, $right); |
340
|
|
|
$left = $right + 1; |
341
|
|
|
|
342
|
|
|
return $left; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Recursive reoder simple tree type. |
347
|
|
|
* |
348
|
|
|
* @param $data |
349
|
|
|
* @param $parentId |
350
|
|
|
*/ |
351
|
|
|
protected function recursiveReorderSimple(array $data, $parentId) |
352
|
|
|
{ |
353
|
|
|
foreach ($data as $order => $item) { |
354
|
|
|
$id = $item['id']; |
355
|
|
|
|
356
|
|
|
$instance = $this->find($id); |
357
|
|
|
$instance->{$this->getParentField()} = $parentId; |
358
|
|
|
$instance->{$this->getOrderField()} = $order; |
359
|
|
|
$instance->save(); |
|
|
|
|
360
|
|
|
|
361
|
|
|
if (isset($item['children'])) { |
362
|
|
|
$this->recursiveReorderSimple($item['children'], $id); |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* Get children for simple tree type structure. |
369
|
|
|
* |
370
|
|
|
* @param $collection |
371
|
|
|
* @param $id |
372
|
|
|
* |
373
|
|
|
* @return Collection |
374
|
|
|
*/ |
375
|
|
|
protected function getChildren($collection, $id) |
376
|
|
|
{ |
377
|
|
|
$parentField = $this->getParentField(); |
378
|
|
|
$result = []; |
379
|
|
|
foreach ($collection as $instance) { |
380
|
|
|
if ((int) $instance->$parentField != $id) { |
381
|
|
|
continue; |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
$instance->setRelation('children', $this->getChildren($collection, $instance->getKey())); |
385
|
|
|
$result[] = $instance; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
return new Collection($result); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Create simple tree type structure. |
393
|
|
|
* @return static |
394
|
|
|
*/ |
395
|
|
|
protected function createSimpleTree() |
396
|
|
|
{ |
397
|
|
|
$collection = $this |
|
|
|
|
398
|
|
|
->getQuery() |
399
|
|
|
->orderBy($this->getParentField(), 'asc') |
400
|
|
|
->orderBy($this->getOrderField(), 'asc') |
401
|
|
|
->get(); |
402
|
|
|
|
403
|
|
|
return $this->getChildren( |
404
|
|
|
$collection, |
405
|
|
|
$this->getRootParentId() |
406
|
|
|
); |
407
|
|
|
} |
408
|
|
|
} |
409
|
|
|
|
The break statement is not necessary if it is preceded for example by a return statement:
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.