Dev Notes

Software Development Resources by David Egan.

Nested Resource Routes in Laravel


Laravel, Routing
David Egan

To create a resource controller, run:

php artisan make:controller PostController --resource

This will create a controller with stubbed out methods for handling typical CRUD actions.

Resourceful Route to the Controller

The Laravel resourceful route goes hand-in-hand with the resource controller. To generate typical CRUD routes to the controller, add this line to routes/web.php (Laravel 5.3+):

Route::resource('posts', PostController);

This route declaration sets up multiple routes to the controller. You can view these routes by running php artisan route:list:

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

+--------+-----------+--------------------+---------------+---------------------------------------------+------------+

Nested Routes

Nested routes allow you to capture a relationship between resources within your routing. For example, resources from a Post model might be nested under a User model: users/{user}/posts/{post}. The resulting URL might look like this: http://example.com/users/1/posts/10.

To create nested routes, Laravel lets you use a dot notation. Sticking with the previous example:

Route::resource('users.posts', 'PostController');

This would create a set of routes for posts that include the user identifier. For example:

  • The ‘index’ route: http://example.com/users/1/posts
  • The ‘show’ route: http://example.com/users/1/posts/10

You can override the out-of-the-box routes that are set up with the resource() method, or add additional routes - you should add overrides before the resource method.

Nesting resources can make the URLs for your project unwieldy. They may also add complexity to your controllers - for example, the generated controller method stubs may require additional parameters, and you may need to pass additonal parameters when building forms.

Nested Resources: Forms

When building a form action in the Laravel Collective Forms & HTML package, you need to pass in the additional parameters that are needed to build the route.

Create New Resources

To set up a form action based on the route users/{user}/post/{post} which is used to store a new resource:

{{-- Blade template ----------------------------------------------------------}}
{{-- Open the form and use a named route for submissions ---------------------}}
{!! Form::open(['route'=>['users.posts.store', $user_id]]) !!}
    {{-- Include the form to keep things DRY ---------------------------------}}
    @include('posts.form', ['submitButtonText' => 'Create New Post'])
{!! Form::close() !!}

Note that only the user ID is required - this is a new post, so the post ID does not yet exist.

The store method on PostController might look like this:

    <?php
    /**
     * Store a newly created Post in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request, $user_id)
    {

        $user = User::findOrFail($user_id);
        $user->posts()->create($request->all());

    }

This relies on the user parameter being passed in through the URL. You could also not bother with this and do Auth::user()->posts()->create($request->all()); to create the new post instead.

Edit Existing Resources

To set the form action so that it spoofs a PATCH method to the users/{user}/posts/{post}/edit endpoint:

{{-- Blade template ----------------------------------------------------------}}
{!! Form::model($post, ['method'=> 'PATCH', 'route'=>['users.posts.update', $user_id, $post->id]]) !!}
    @include('posts.form', ['submitButtonText' => 'Edit Post'])
{!! Form::close() !!}

To update the edited resource, you could use a controller update method like this:

    <?php
    /**
     * Store a newly created Post in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $user_id
     * @param  int  $post_id
     * @return \Illuminate\Http\Response
     */
     public function update(Request $request, $user_id, $post_id)
     {
         $post = Post::findOrFail($post_id);

         $post->update($request->all());

         return redirect('posts');
     }

Note that the parameters you pass in need to be ordered as they are in the URL.

Checking Routes

If in doubt, check registered routes by running php artisan route:list - which outputs a helpful table showing route names along with URLs and controller methods.

References


comments powered by Disqus