Skip to content

Commit b2f6af8

Browse files
committed
Update
1 parent bc2ef05 commit b2f6af8

File tree

5 files changed

+99
-5
lines changed

5 files changed

+99
-5
lines changed

CHANGELOG.markdown

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
### 4.2.3
2+
3+
* Added `whereAncestorOrSelf`, `ancestorsAndSelf`, `descendantsOrSelf`,
4+
`whereDescendantOrSelf` helper methods
5+
* #186: rebuild tree removes nodes softly when model uses SoftDeletes trait
6+
* #191: added `whereIsLeaf` and `leaves` method, added `isLeaf` check on node
7+
18
### 4.1.0
29

310
* #75: Converted to trait. Following methods were renamed:
@@ -21,7 +28,7 @@
2128

2229
### 3.1.1
2330

24-
* Fixed #42: model becomes dirty before save when parent is changed and using `appendTo`,
31+
* Fixed #42: model becomes dirty before save when parent is changed and using `appendTo`,
2532
`prependTo`, `insertBefore`, `insertAfter`.
2633

2734
### 3.1.0

README.markdown

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ $result = Category::whereDescendantOf($node)->get();
375375
$result = Category::whereNotDescendantOf($node)->get();
376376
$result = Category::orWhereDescendantOf($node)->get();
377377
$result = Category::orWhereNotDescendantOf($node)->get();
378+
$result = Category::whereDescendantAndSelf($id)->get();
378379

379380
// Include target node into result set
380381
$result = Category::whereDescendantOrSelf($node)->get();
@@ -384,6 +385,7 @@ Ancestor constraints:
384385

385386
```php
386387
$result = Category::whereAncestorOf($node)->get();
388+
$result = Category::whereAncestorOrSelf($id)->get();
387389
```
388390

389391
`$node` can be either a primary key of the model or model instance.
@@ -490,6 +492,7 @@ Other checks:
490492
* `$node->isChildOf($other);`
491493
* `$node->isAncestorOf($other);`
492494
* `$node->isSiblingOf($other);`
495+
* `$node->isLeaf()`
493496

494497
### Checking consistency
495498

src/NodeTrait.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,14 @@ public function isRoot()
835835
return is_null($this->getParentId());
836836
}
837837

838+
/**
839+
* @return bool
840+
*/
841+
public function isLeaf()
842+
{
843+
return $this->getLft() + 1 == $this->getRgt();
844+
}
845+
838846
/**
839847
* Get the lft key name.
840848
*

src/QueryBuilder.php

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Kalnoy\Nestedset;
44

5+
use Carbon\Carbon;
56
use Illuminate\Database\Eloquent\Builder;
67
use Illuminate\Database\Eloquent\Model;
78
use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -77,10 +78,11 @@ public function whereIsRoot()
7778
* @since 2.0
7879
*
7980
* @param mixed $id
81+
* @param bool $andSelf
8082
*
8183
* @return $this
8284
*/
83-
public function whereAncestorOf($id)
85+
public function whereAncestorOf($id, $andSelf = false)
8486
{
8587
$keyName = $this->model->getKeyName();
8688

@@ -109,11 +111,23 @@ public function whereAncestorOf($id)
109111
$this->query->whereRaw("{$value} between {$lft} and {$rgt}");
110112

111113
// Exclude the node
112-
$this->where($keyName, '<>', $id);
114+
if ( ! $andSelf) {
115+
$this->where($keyName, '<>', $id);
116+
}
113117

114118
return $this;
115119
}
116120

121+
/**
122+
* @param $id
123+
*
124+
* @return QueryBuilder
125+
*/
126+
public function whereAncestorOrSelf($id)
127+
{
128+
return $this->whereAncestorOf($id, true);
129+
}
130+
117131
/**
118132
* Get ancestors of specified node.
119133
*
@@ -129,6 +143,17 @@ public function ancestorsOf($id, array $columns = array( '*' ))
129143
return $this->whereAncestorOf($id)->get($columns);
130144
}
131145

146+
/**
147+
* @param $id
148+
* @param array $columns
149+
*
150+
* @return \Kalnoy\Nestedset\Collection
151+
*/
152+
public function ancestorsAndSelf($id, array $columns = [ '*' ])
153+
{
154+
return $this->whereAncestorOf($id, true)->get($columns);
155+
}
156+
132157
/**
133158
* Add node selection statement between specified range.
134159
*
@@ -329,6 +354,26 @@ public function whereIsBefore($id, $boolean = 'and')
329354
return $this->whereIsBeforeOrAfter($id, '<', $boolean);
330355
}
331356

357+
/**
358+
* @return $this
359+
*/
360+
public function whereIsLeaf()
361+
{
362+
list($lft, $rgt) = $this->wrappedColumns();
363+
364+
return $this->whereRaw("$lft = $rgt - 1");
365+
}
366+
367+
/**
368+
* @param array $columns
369+
*
370+
* @return Collection
371+
*/
372+
public function leaves(array $columns = [ '*'])
373+
{
374+
return $this->whereIsLeaf()->get($columns);
375+
}
376+
332377
/**
333378
* Include depth level into the result.
334379
*
@@ -877,20 +922,32 @@ protected static function reorderNodes(array &$dictionary, &$fixed,
877922
*/
878923
public function rebuildTree(array $data, $delete = false)
879924
{
925+
if ($this->model->usesSoftDelete()) {
926+
$this->withTrashed();
927+
}
928+
880929
$existing = $this->get()->getDictionary();
881930
$dictionary = [];
882931

883932
$this->buildRebuildDictionary($dictionary, $data, $existing);
884933

885934
if ( ! empty($existing)) {
886-
if ($delete) {
935+
if ($delete && ! $this->model->usesSoftDelete()) {
887936
$this->model
888937
->newScopedQuery()
889938
->whereIn($this->model->getKeyName(), array_keys($existing))
890-
->forceDelete();
939+
->delete();
891940
} else {
892941
foreach ($existing as $model) {
893942
$dictionary[$model->getParentId()][] = $model;
943+
944+
if ($this->model->usesSoftDelete()) {
945+
if ( ! $model->{$model->getDeletedAtColumn()}) {
946+
$time = $this->model->fromDateTime($this->model->freshTimestamp());
947+
948+
$model->{$model->getDeletedAtColumn()} = $time;
949+
}
950+
}
894951
}
895952
}
896953
}

tests/NodeTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,10 +803,16 @@ public function testRebuildTreeWithDeletion()
803803
{
804804
Category::rebuildTree([ [ 'name' => 'all deleted' ] ], true);
805805

806+
$this->assertTreeNotBroken();
807+
806808
$nodes = Category::get();
807809

808810
$this->assertEquals(1, $nodes->count());
809811
$this->assertEquals('all deleted', $nodes->first()->name);
812+
813+
$nodes = Category::withTrashed()->get();
814+
815+
$this->assertTrue($nodes->count() > 1);
810816
}
811817

812818
/**
@@ -841,6 +847,19 @@ public function testSeveralNodesModelWork()
841847

842848
$duplicate->saveAsRoot();
843849
}
850+
851+
public function testWhereIsLeaf()
852+
{
853+
$categories = Category::leaves();
854+
855+
$this->assertEquals(7, $categories->count());
856+
$this->assertEquals('apple', $categories->first()->name);
857+
$this->assertTrue($categories->first()->isLeaf());
858+
859+
$category = Category::whereIsRoot()->first();
860+
861+
$this->assertFalse($category->isLeaf());
862+
}
844863
}
845864

846865
function all($items)

0 commit comments

Comments
 (0)