Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Darkdawg's avatar

Best way to handle nested model paths in Laravel?

I have a Page model without a path column. The path is derived from the slug and the nested parent_id hierarchy, and it’s rebuilt every time it’s accessed.

Example

/about/team/history
slug: "history"
parent slug: "team"
parent slug: "about"

I've enabled strict models, so I get lazy-loading exceptions unless I eager-load parents. The problem is:

  • The nesting depth is unknown (could be 1 level or 10 levels deep).
  • I could eager-load with something like with('parent.parent.parent...'), but that's brittle.
  • I'm aware of Model::automaticallyEagerLoadRelationships(), but I'm unsure if that's a good idea here.

Here's my current Page model approach:

public function parent(): BelongsTo
{
    return $this->belongsTo(self::class);
}

public function path(): string
{
    $segments = [];
    $page = $this;

    while ($page) {
        $segments[] = $page->slug;
        $page = $page->parent;
    }

    $segments = array_reverse($segments);

    return '/' . ltrim(implode('/', $segments), '/');
}

This works, but I call path() in many places (Search queries, Livewire components, etc.), so it feels like I'm paying for this computation over and over - plus it triggers parent loads.

My question

Is there a Laravel "industry standard" for this?

Should I stick with on-the-fly path building and recursive eager loading, or is the recommended approach to add a path column and keep it in sync with slug & parent_id (for example with an Observer)?

Or would a package like https://github.com/staudenmeir/laravel-adjacency-list or https://github.com/lazychaser/laravel-nestedset be the best bet?

If anyone has had a similar issue, I'd love to hear the different solutions you've come up with.

0 likes
5 replies
Glukinho's avatar

it feels like I'm paying for this computation over and over - plus it triggers parent loads

It feels to me too.

Besides that, you get unpredictable problems if parent is deleted or moved somewhere in hierarchy, your paths turn into a mess.

Tray2's avatar

What is it that you are trying to achieve here?

I guess you don't want to write each route manually, which I would suggest doing, that way it's easier to read. However, in your route file you can do something like

Route::get('/about/{group}/{action}', [AboutController::class, 'about']);

Then you should be able to handle it in the controller like so.

public function about($group, $action) 
{
	
}
Darkdawg's avatar

@Tray2 Not quite what I had in mind.

Imagine you have a backend where you can create pages. A form to create/edit them, a table to list them, and a search form to find them.

Instead of writing the full path string for every page, the path is fixed to the page slug and parent hierarchy.

The search form looks for matches in page paths among other things. For example, if you search for "about" it should find any page that includes this in its path, and in my example it should find the "history" because it is a decendant of the "about" page.

martinbean's avatar

@darkdawg I don’t think you’re going to find a “nice” way to eager-load an unknown number of levels with strict mode enabled, as the two are contradictory.

Your options are:

  1. Disable strict mode only for the query where you traverse a page’s parents.
  2. Use a different algorithm to represent your page hierarchy (such as nested set model) that allows you to find a given page’s parents in a single query, based on the given page’s “left” and “right” values.
Darkdawg's avatar

@martinbean Right, your alternative 2 might be what I'm looking for, I would have to to dig a bit into it.

1 like

Please or to participate in this conversation.