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