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!