Skip to content

Commit efd1144

Browse files
committed
#79: implemented scoping
1 parent 4c9b748 commit efd1144

File tree

7 files changed

+330
-48
lines changed

7 files changed

+330
-48
lines changed

CHANGELOG.markdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
* #82: Fixing tree now handles case when nodes pointing non-existing parent
1212
* The number of missing parent is now returned when using `countErrors`
13+
* #79: implemented scoping feature
1314

1415
### 3.1.1
1516

README.markdown

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ __Contents:__
1818
- [Documentation](#documentation)
1919
- [Inserting nodes](#inserting-nodes)
2020
- [Retrieving nodes](#retrieving-nodes)
21+
- [Scoping](#scoping)
2122
- [Consistency checking & fixing](#checking-consistency)
2223
- [Requirements](#requirements)
2324
- [Installation](#installation)
@@ -436,6 +437,28 @@ are set for every node.
436437
Node::fixTree();
437438
```
438439

440+
### Scoping
441+
442+
Imagine you have `Menu` model and `MenuItems`. There is a one-to-many relationship
443+
set up between these models. `MenuItem` has `menu_id` attribute for joining models
444+
together. `MenuItem` incorporates nested sets. It is obvious that you would want to
445+
process each tree separately based on `menu_id` attribute. In order to do so, you
446+
need to specify this attribute as scope attribute:
447+
448+
```php
449+
protected function getScopeAttributes()
450+
{
451+
return [ 'menu_id' ];
452+
}
453+
```
454+
455+
But now in order to execute some custom query for retrieving nodes, you need to
456+
provide attributes that are used for scoping:
457+
458+
```php
459+
MenuItem::scoped([ 'menu_id' => 5 ])->withDepth()->get();
460+
```
461+
439462
Requirements
440463
------------
441464

src/NodeTrait.php

Lines changed: 84 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Database\Eloquent\Relations\BelongsTo;
99
use Illuminate\Database\Eloquent\Relations\HasMany;
1010
use Illuminate\Database\Eloquent\SoftDeletingScope;
11+
use Illuminate\Database\Query\Builder;
1112
use LogicException;
1213

1314
trait NodeTrait
@@ -164,7 +165,7 @@ protected function actionRoot()
164165
*/
165166
protected function getLowerBound()
166167
{
167-
return (int)$this->newServiceQuery()->max($this->getRgtName());
168+
return (int)$this->newNestedSetQuery()->max($this->getRgtName());
168169
}
169170

170171
/**
@@ -226,32 +227,21 @@ public function refreshNode()
226227
{
227228
if ( ! $this->exists || static::$actionsPerformed === 0) return;
228229

229-
$attributes = $this->newServiceQuery()->getNodeData($this->getKey());
230+
$attributes = $this->newNestedSetQuery()->getNodeData($this->getKey());
230231

231232
$this->attributes = array_merge($this->attributes, $attributes);
232233
$this->original = array_merge($this->original, $attributes);
233234
}
234235

235-
/**
236-
* Get the root node.
237-
*
238-
* @param array $columns
239-
*
240-
* @return self
241-
*/
242-
static public function root(array $columns = ['*'])
243-
{
244-
return static::whereIsRoot()->first($columns);
245-
}
246-
247236
/**
248237
* Relation to the parent.
249238
*
250239
* @return BelongsTo
251240
*/
252241
public function parent()
253242
{
254-
return $this->belongsTo(get_class($this), $this->getParentIdName());
243+
return $this->belongsTo(get_class($this), $this->getParentIdName())
244+
->setModel($this);
255245
}
256246

257247
/**
@@ -261,7 +251,8 @@ public function parent()
261251
*/
262252
public function children()
263253
{
264-
return $this->hasMany(get_class($this), $this->getParentIdName());
254+
return $this->hasMany(get_class($this), $this->getParentIdName())
255+
->setModel($this);
265256
}
266257

267258
/**
@@ -271,7 +262,7 @@ public function children()
271262
*/
272263
public function descendants()
273264
{
274-
return $this->newQuery()->whereDescendantOf($this->getKey());
265+
return $this->newScopedQuery()->whereDescendantOf($this->getKey());
275266
}
276267

277268
/**
@@ -296,20 +287,14 @@ public function siblings($dir = null)
296287
break;
297288

298289
default:
299-
$query = $this->newQuery()
290+
$query = $this->newScopedQuery()
300291
->defaultOrder()
301292
->where($this->getKeyName(), '<>', $this->getKey());
302293

303294
break;
304295
}
305296

306-
$parentId = $this->getParentId();
307-
308-
if (is_null($parentId)) {
309-
$query->whereNull($this->getParentIdName());
310-
} else {
311-
$query->where($this->getParentIdName(), '=', $parentId);
312-
}
297+
$query->where($this->getParentIdName(), '=', $this->getParentId());
313298

314299
return $query;
315300
}
@@ -341,7 +326,7 @@ public function prevSiblings()
341326
*/
342327
public function nextNodes()
343328
{
344-
return $this->newQuery()
329+
return $this->newScopedQuery()
345330
->whereIsAfter($this->getKey())
346331
->defaultOrder();
347332
}
@@ -353,7 +338,7 @@ public function nextNodes()
353338
*/
354339
public function prevNodes()
355340
{
356-
return $this->newQuery()
341+
return $this->newScopedQuery()
357342
->whereIsBefore($this->getKey())
358343
->reversed();
359344
}
@@ -365,7 +350,7 @@ public function prevNodes()
365350
*/
366351
public function ancestors()
367352
{
368-
return $this->newQuery()
353+
return $this->newScopedQuery()
369354
->whereAncestorOf($this->getKey())
370355
->defaultOrder();
371356
}
@@ -588,7 +573,7 @@ protected function insertAt($position)
588573
*/
589574
protected function moveNode($position)
590575
{
591-
$updated = $this->newServiceQuery()
576+
$updated = $this->newNestedSetQuery()
592577
->moveNode($this->getKey(), $position) > 0;
593578

594579
if ($updated) $this->refreshNode();
@@ -607,7 +592,7 @@ protected function moveNode($position)
607592
*/
608593
protected function insertNode($position)
609594
{
610-
$this->newServiceQuery()->makeGap($position, 2);
595+
$this->newNestedSetQuery()->makeGap($position, 2);
611596

612597
$height = $this->getNodeHeight();
613598

@@ -629,12 +614,15 @@ protected function deleteDescendants()
629614
? 'forceDelete'
630615
: 'delete';
631616

632-
$this->newQuery()->whereNodeBetween([ $lft + 1, $rgt ])->{$method}();
617+
$this->newQuery()
618+
->applyNestedSetScope()
619+
->whereNodeBetween([ $lft + 1, $rgt ])
620+
->{$method}();
633621

634622
if ($this->hardDeleting()) {
635623
$height = $rgt - $lft + 1;
636624

637-
$this->newServiceQuery()->makeGap($rgt + 1, -$height);
625+
$this->newNestedSetQuery()->makeGap($rgt + 1, -$height);
638626

639627
// In case if user wants to re-create the node
640628
$this->makeRoot();
@@ -651,6 +639,7 @@ protected function deleteDescendants()
651639
protected function restoreDescendants($deletedAt)
652640
{
653641
$this->newQuery()
642+
->applyNestedSetScope()
654643
->whereNodeBetween([ $this->getLft() + 1, $this->getRgt() ])
655644
->where($this->getDeletedAtColumn(), '>=', $deletedAt)
656645
->applyScopes()
@@ -674,9 +663,67 @@ public function newEloquentBuilder($query)
674663
*
675664
* @return QueryBuilder
676665
*/
677-
public function newServiceQuery()
666+
public function newNestedSetQuery($table = null)
667+
{
668+
$builder = $this->usesSoftDelete()
669+
? $this->withTrashed()
670+
: $this->newQuery();
671+
672+
return $this->applyNestedSetScope($builder, $table);
673+
}
674+
675+
/**
676+
* @return mixed
677+
*/
678+
public function newScopedQuery($table = null)
678679
{
679-
return $this->usesSoftDelete() ? $this->withTrashed() : $this->newQuery();
680+
return $this->applyNestedSetScope($this->newQuery(), $table);
681+
}
682+
683+
/**
684+
* @param mixed $query
685+
* @param string $table
686+
*
687+
* @return mixed
688+
*/
689+
public function applyNestedSetScope($query, $table = null)
690+
{
691+
if ( ! $scoped = $this->getScopeAttributes()) {
692+
return $query;
693+
}
694+
695+
if ( ! $table) {
696+
$table = $this->getTable();
697+
}
698+
699+
foreach ($scoped as $attribute) {
700+
$query->where($table.'.'.$attribute, '=',
701+
$this->getAttributeValue($attribute));
702+
}
703+
704+
return $query;
705+
}
706+
707+
/**
708+
* @return array
709+
*/
710+
protected function getScopeAttributes()
711+
{
712+
return null;
713+
}
714+
715+
/**
716+
* @param array $attributes
717+
*
718+
* @return self
719+
*/
720+
public static function scoped(array $attributes)
721+
{
722+
$instance = new static;
723+
724+
$instance->setRawAttributes($attributes);
725+
726+
return $instance->newScopedQuery();
680727
}
681728

682729
/**
@@ -752,7 +799,7 @@ public function setParentIdAttribute($value)
752799
if ($this->getParentId() == $value) return;
753800

754801
if ($value) {
755-
$this->appendToNode($this->newQuery()->findOrFail($value));
802+
$this->appendToNode($this->newScopedQuery()->findOrFail($value));
756803
} else {
757804
$this->makeRoot();
758805
}
@@ -863,7 +910,7 @@ public function getPrevNode(array $columns = array( '*' ))
863910
*/
864911
public function getAncestors(array $columns = array( '*' ))
865912
{
866-
return $this->newQuery()
913+
return $this->newScopedQuery()
867914
->defaultOrder()
868915
->ancestorsOf($this->getKey(), $columns);
869916
}
@@ -875,7 +922,7 @@ public function getAncestors(array $columns = array( '*' ))
875922
*/
876923
public function getDescendants(array $columns = array( '*' ))
877924
{
878-
return $this->newQuery()
925+
return $this->newScopedQuery()
879926
->defaultOrder()
880927
->descendantsOf($this->getKey(), $columns);
881928
}

0 commit comments

Comments
 (0)