Mastering Nested Resources with Laravel's Scoped Route Binding

Mastering Nested Resources with Laravel's Scoped Route Binding

When working with nested resources in Laravel, ensuring that child resources belong to their respective parents is crucial for maintaining data integrity and security. Laravel's scoped implicit model binding feature provides an elegant solution to this common challenge. Let's dive into how you can leverage this powerful feature in your Laravel applications.

Understanding Scoped Resource Routes

Scoped resource routes in Laravel allow you to automatically scope nested bindings, ensuring that the resolved child model belongs to the parent model. This feature not only enhances security but also allows for more SEO-friendly and readable URLs.

Basic Implementation

To implement scoped resource routes, use the scoped method when defining your nested resource:

use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class)->scoped([
    'comment' => 'slug',
]);

This route definition creates a scoped nested resource that can be accessed with URLs like:

/photos/{photo}/comments/{comment:slug}

How It Works

When you use a custom keyed implicit binding as a nested route parameter, Laravel automatically scopes the query to retrieve the nested model by its parent. It uses conventions to guess the relationship name on the parent model.

In our example, Laravel assumes that the Photo model has a relationship named comments (the plural of the route parameter name) which is used to retrieve the Comment model.

In the Controller

Your controller methods will reflect this scoped binding:

class PhotoCommentController extends Controller
{
    public function show(Photo $photo, Comment $comment)
    {
        // $comment is already scoped to $photo
        return view('comments.show', compact('photo', 'comment'));
    }
}

Laravel automatically ensures that the $comment belongs to the $photo without any additional code.

Customizing the Key

You can customize the key used for retrieving the nested resource:

Route::resource('users.posts', PostController::class)->scoped([
    'post' => 'slug',
]);

This allows for URLs like /users/{user}/posts/{post:slug}, where posts are retrieved by their slug.

Real-World Example: Blog with Categories and Posts

Let's consider a blog system where posts belong to categories:

// routes/web.php
Route::resource('categories.posts', CategoryPostController::class)->scoped([
    'post' => 'slug',
]);

// app/Models/Category.php
class Category extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

// app/Models/Post.php
class Post extends Model
{
    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}

// app/Http/Controllers/CategoryPostController.php
class CategoryPostController extends Controller
{
    public function show(Category $category, Post $post)
    {
        // $post is automatically scoped to $category
        return view('posts.show', compact('category', 'post'));
    }
}

With this setup, a URL like /categories/tech/posts/laravel-scoped-routes would automatically ensure that the "laravel-scoped-routes" post belongs to the "tech" category.

Handling Non-Existent Relationships

If a child resource doesn't belong to the parent, Laravel will automatically return a 404 response. This saves you from manually checking the relationship in each controller method.

If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!

Subscribe to Harris Raftopoulos

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe