So I have data structured like this:
id|parent_id|name
1 |null |foo
2 |1 |bar
3 |2 |baz
So basically foo->bar->ba
I've created a package that uses common table expressions (CTE) to implement recursive relationships: https://github.com/staudenmeir/laravel-adjacency-list
You can use the ancestors
relationship to get all parents of a model recursively:
class YourModel extends Model
{
use \Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
}
$allParents = YourModel::find($id)->ancestors;
An other way could be to use the etrepat/baum package, it's a Laravel implementation of the Nested set model. It's using an ordered tree that is faster and use non-recursive queries. While your data structured like this :
root
|_ Child 1
|_ Child 1.1
|_ Child 1.2
|_ Child 2
|_ Child 2.1
|_ Child 2.2
There are structured like this in nested set model :
___________________________________________________________________
| Root |
| ____________________________ ____________________________ |
| | Child 1 | | Child 2 | |
| | __________ _________ | | __________ _________ | |
| | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | |
1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14
| |___________________________| |___________________________| |
|___________________________________________________________________|
And inserting nodes is easy as :
$child1 = $root->children()->create(['name' => 'Child 1']);
I modified tiffanyhwang solution and turned it into a non-static method and included a attribute accessor to make it easier to get results.
public function ancestors()
{
$ancestors = $this->where('id', '=', $this->parent_id)->get();
while ($ancestors->last() && $ancestors->last()->parent_id !== null)
{
$parent = $this->where('id', '=', $ancestors->last()->parent_id)->get();
$ancestors = $ancestors->merge($parent);
}
return $ancestors;
}
and accessor to retrieve a collection of ancestors from model attribute
public function getAncestorsAttribute()
{
return $this->ancestors();
// or like this, if you want it the other way around
// return $this->ancestors()->reverse();
}
so now you can get ancestors like this:
$ancestors = $model->ancestors;
and since its a Collection, you can now easily do for example this:
echo $model->ancestors->implode('title',', ');
So after fiddling around with the merge()
method for the Collections
class:
public static function ancestors($id)
{
$ancestors = Model::where('id', '=', $id)->get();
while ($ancestors->last()->parent_id !== null)
{
$parent = Model::where('id', '=', $ancestors->last()->parent_id)->get();
$ancestors = $ancestors->merge($parent);
}
return $ancestors;
}
That will produce what I needed, however I believe it can be more cleaner so please feel free to edit it!
I modified ruuter answer to use relationships. If you have a parent() belongsTo relationship on the model you can use that one to remove the where clause, see below:
public function parents()
{
$parents = $this->parent()->get();
while ($parents->last() && $parents->last()->parent_id !== null) {
$parent = $parents->last()->parent()->get();
$parents = $parents->merge($parent);
}
return $parents;
}
And then you can access it:
public function allParents(): Collection
{
return $this->parents();
}