stackminu
stackminu

Reputation: 781

Laravel Get ancestors (URL)

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 by "/").

level1/level2/level3

But in an efficient way without a package like "laravel-nestedset ".

I have implemented it like this.

public function parent()
{
    return $this->belongsTo('Collection', 'parent_id');
}

public function getParentsAttribute()
{
    $parents = collect([]);

    $parent = $this->parent;

    while(!is_null($parent)) {
        $parents->push($parent);
        $parent = $parent->parent;
    }

    return $parents;
}

Any other way to do it efficiently and separated by "/" ?

Upvotes: 4

Views: 2188

Answers (3)

EL-Amrani Youssef
EL-Amrani Youssef

Reputation: 11

You can Define Recursive Relationships in your Model :

public function parents()
{
   return $this->belongsTo(Model::class, 'parent_id');
}
  • It indicates that each category belongs to another category (its parent).

// Recursive relationship to fetch all ancestors

public function ancestors()
{
   return $this->parents()->with('ancestors');
}
  • It recursively fetches all parent categories of the current category.
  • It starts from the current category, fetches its parent, and then fetches the parent's parent, and so on.

this for Laravel Framework 11.16.0

Upvotes: 0

Nikolai Kiselev
Nikolai Kiselev

Reputation: 6603

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;

Upvotes: 1

Flame
Flame

Reputation: 7561

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:

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.

Upvotes: 2

Related Questions