Binding Relevant Models to Routes in Nested Laravel Resources

Route model binding is a great and well-known feature of the Laravel framework. But, when we work with nested resources, we might write a custom rule explicitly that ensures the child models can be load with the correct parent model. Let’s see!

A Concrete Example

Let’s say we have a Post and a Comment model. The posts can have many comment models and a comment model can belong to only one post model. Basic belongsTo() and hasMany() relationships.

Now let’s see the controller’s for the models, and let’s say it’s an admin layer, not the front end:

// Posts
Route::resource('posts', 'PostsController');

// Comments
Route::resource('posts.comments', 'CommentsController');

Our routes are typical nested resource routes, clean and simple REST endpoints. That’s being said, let’s take a look at how to load a comment that belongs to a post:

/posts/{post}/comments/{comment}

Now let’s say we use route model binding in the CommentsController and it means we load the comment model automatically by its id. The problem here is that the model will be loaded even if we rewrite the URL and change the post but keep the comment parameter in the route.

It means we can view, edit, so on comments even if the current post ‘namespace’ is not the one that owns the comment. It can be quite tricky, just imagine that while you hit a PATCH request to update the comment and also you may change your post model in the controller, you can update the wrong post.

So how should it be? Let’s say, if the comment does not belong to the given post, we return a 404 response, like if the model would not exist.

The Custom Explicit Binding Logic

There are many ways to sort this issue out – you can use middleware or put the logic directly in the controller – but let’s use the route-model binding here. I personally think it’s the cleanest approach.

Route::bind('comment', function ($comment, $route) {
    return Comment::where('post_id', $route->parameter('post'))->findOrFail($comment);
});

You can see, we can pass the $route object as the second parameter of the closure, so at this point, we have access to the other segments of the current routes.

From here, we can scope the comments for the current post only and just pass the id of the comment. Also, using findOrFail() here will automatically trigger the 404 error if the comment is not found in the current scope. Clean and easy solution, yet closes some dangerous backdoors in our application.

Special thanks for the following recource(s): Icon made by DinosoftLabs from www.flaticon.com
Need a web developer? Maybe we can help, get in touch!