In Laravel, I have a table which contains id, parent_id, slug (Self-referring),
When I have an ID, I need to get all its ancestors in a format like this (Separated b
After a little conversation in the comments I think this is a good solution:
// YourModel.php
// Add this line of you want the "parents" property to be populated all the time.
protected $appends = ['parents'];
public function getParentsAttribute()
{
$collection = collect([]);
$parent = $this->parent;
while($parent) {
$collection->push($parent);
$parent = $parent->parent;
}
return $collection;
}
Then you can retrieve your parents using:
YourModel::find(123)->parents
(collection instance)YourModel::find(123)->parents->implode('yourprop', '/')
(imploded to string, see https://laravel.com/docs/5.4/collections#method-implode)YourModel::find(123)->parents->reverse()->implode('yourprop', '/')
(reversed order https://laravel.com/docs/5.4/collections#method-reverse)As noted by Nikolai Kiselev https://stackoverflow.com/a/55103589/1346367 you may also combine it with this to save a few queries:
protected $with = ['parent.parent.parent'];
// or inline:
YourModel::find(123)->with(['parent.parent.parent']);
This preloads the parent on object load. If you decide not to use this, the parent is (lazy) loaded as soon as you call $yourModel->parent
.
If you know how many levels maximum could be nested you can use Eager Loading. Let's say if maximum depth is 3 levels you can do:
$model->with('parent.parent.parent');
You can also use recursion instead of loop.
public function getParentsAttribute()
{
if (!$this->parent) {
return collect([]);
}
return collect($this->parent->parents)->push($this->parent);
}
In case you want to add the first one object too (self) the full call will be:
$model->parents->push($model)->reverse->implode('attr_name', '/');
Which you can also wrap into attribute
public function getPathAttribute() {
return $model->parents->push($model)->reverse->implode('attr_name', '/');
}
And call like $model->path;