N'Bayramberdiyev
N'Bayramberdiyev

Reputation: 3620

Laravel 7 - Scoping problem in Nested Resource Route

Routes:

I have a nested resource route definition like this:

Route::resource('posts.comments', 'CommentController');

That produces the following routes:

+--------+-----------+--------------------------------------+------------------------+------------------------------------------------+------------+
| Domain | Method    | URI                                  | Name                   | Action                                         | Middleware |
+--------+-----------+--------------------------------------+------------------------+------------------------------------------------+------------+
|        | GET|HEAD  | posts/{post}/comments                | posts.comments.index   | App\Http\Controllers\CommentController@index   | web        |
|        | POST      | posts/{post}/comments                | posts.comments.store   | App\Http\Controllers\CommentController@store   | web        |
|        | GET|HEAD  | posts/{post}/comments/create         | posts.comments.create  | App\Http\Controllers\CommentController@create  | web        |
|        | GET|HEAD  | posts/{post}/comments/{comment}      | posts.comments.show    | App\Http\Controllers\CommentController@show    | web        |
|        | PUT|PATCH | posts/{post}/comments/{comment}      | posts.comments.update  | App\Http\Controllers\CommentController@update  | web        |
|        | DELETE    | posts/{post}/comments/{comment}      | posts.comments.destroy | App\Http\Controllers\CommentController@destroy | web        |
|        | GET|HEAD  | posts/{post}/comments/{comment}/edit | posts.comments.edit    | App\Http\Controllers\CommentController@edit    | web        |
+--------+-----------+--------------------------------------+------------------------+------------------------------------------------+------------+

Relationships (In Models):

Post model:

public function comments()
{
    return $this->hasMany(Comment::class);
}

Comment model:

public function post()
{
    return $this->belongsTo(Post::class);
}

Dummy Data (In Tables):

posts table:

+----+--------+-----------------------------+---------------------+---------------------+
| id | title  | body                        | created_at          | updated_at          |
+----+--------+-----------------------------+---------------------+---------------------+
| 1  | Post 1 | This is the body of Post 1. | 2020-07-29 11:20:53 | 2020-07-29 11:20:53 |
| 2  | Post 2 | This is the body of Post 2. | 2020-07-29 11:21:13 | 2020-07-29 11:21:13 |
+----+--------+-----------------------------+---------------------+---------------------+

comments table:

+----+---------+-----------------------------+---------------------+---------------------+
| id | post_id | body                        | created_at          | updated_at          |
+----+---------+-----------------------------+---------------------+---------------------+
| 1  | 1       | The comment for the Post 1. | 2020-07-29 11:22:27 | 2020-07-29 11:22:27 |
| 2  | 2       | The comment for the Post 2. | 2020-07-29 11:22:32 | 2020-07-29 11:22:32 |
+----+---------+-----------------------------+---------------------+---------------------+

In the docs:

When using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent.

So, {comment} is supposed to be child of {post}. But when I hit /posts/1/comments/2, it retrieves comment with an id of 2 which belongs to the post with an id of 2. The expected result would be NotFoundHttpException.

It works fine when I define the routes individually like this:

Route::get('/posts/{post}/comments/{comment:id}', 'CommentController@show');

Why is this happening?

Also tried to customize the default key name in both Post and Comment models:

public function getRouteKeyName()
{
    return 'id';
}

But no luck.

Any help would be appreciated.

Upvotes: 8

Views: 3184

Answers (2)

Nayeem
Nayeem

Reputation: 119

Laravel 8 and 9

To achieve your goal you can use laravel scope resource route feature. For example:

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

Change slug to id if you do not want to use the custom key. See the doc here. You can also try the following:

use App\Http\Controllers\PhotoCommentController;
 
Route::resource('photos.comments', PhotoCommentController::class)->scoped();

You can find the source for scoped method here.

Upvotes: 2

N'Bayramberdiyev
N'Bayramberdiyev

Reputation: 3620

Did some digging and reached a conclusion after reading Illuminate\Routing\PendingResourceRegistration.php class from the source code. I have to use custom keyed implicit binding to make it work as I expect.

Route::resource() method takes (optional) third argument which is an associative array. So, I need to override the route parameter name via parameters key using this argument.

Route::resource('posts.comments', 'CommentController', [
    'parameters' => ['comments' => 'comment:id'],
]);

or

Route::resource('posts.comments', 'CommentController')->parameters([
    'comments' => 'comment:id',
]);

It works either way.

Upvotes: 7

Related Questions