For anyone that doesn’t know or doesn’t use it, Laravel supports route model binding. So instead of doing this:
Route::get('/posts/{post}', function (int $postId) {
$post = Post::findOrFail($postId);
return $post;
});
You can do this:
Route::get('/posts/{post}', function (Post $post) {
return $post;
});
Making it easier to load models, as well as making your controller actions slimmer.
Laravel also supports scoped binding of route models, i.e a Comment can belong to a Post. So when Laravel queries for a comment, it will use the comments relationship on the Posts model to load the Comment, meaning you cannot access a comment belonging to another Post.
Route::get('/posts/{posts}/comments/{comment}', function (Post $post, Comment $comment) {
return $comment;
})->scopeBindings();
Now there is not an easy way to load a Post model using the User that is currently authenticated.
For example. You might want to check that the Post model requested belongs to the User that’s logged in. Previously you might have done something like the following:
Route::get('posts/{post}', function (Post $post) {
abort_unless($post->user_id === auth()->user()->getAuthIdentifier());
return $post;
});
or
Route::get('posts/{id}', function ($id) {
$post = Post::where('user_id', auth()->user()->getAuthIdentifier())->findOrFail($id);
return $post;
});
Updated — 2023–07–10 — I missed this originally but using a Policy class could also be used.
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*/
public function update(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}
Laravel allows you to add this functionality using using route macros or creating your own route binding. If you really wanted to you could even write a Middleware to handle it.
To make this easier to setup I’ve created a package you can install using Composer, so let’s dive in.
I created a package called Laravel Auth Route Bindings, released under Smashed Egg, which you can find on Github. At the time of writing this article it is currently at version 0.1.0.
Installation
To install the package simply run:
composer require smashed-egg/laravel-auth-route-bindings
Once installed, there is no other configuration to set, you can now register your own route model bindings.
Usage
You should define your model bindings at the beginning of the boot method of your RouteServiceProvider.
For example:
use App\Models\Post;
use Illuminate\Support\Facades\Route;
/**
* Define your route model bindings, pattern filters, etc.
*/
public function boot(): void
{
Route::modelAuth('post', Post::class);
// ...
}
You can also specify the foreign key for the User (defaults to user_id):
Route::modelAuth('post', Post::class, 'user_id');
As well as the table field that should used when querying (defaults to id)
Route::modelAuth('post', Post::class, 'user_id', 'id');
And then you can use in your routes declarations the same way as you use other model bindings:
Route::get('posts/{post}', function (Post $post) {
return $post;
});
Scoped Bindings
You can even use it with scoped bindings:
Route::get('posts/{post}/comments/{comment}', function (Post $post, Comment $comment) {
//..
})->scopeBindings();
So looking at the models in reverse, the Comment must belong to the Post and the Post must belong to the authenticated User.
Custom Keys
You can even override the key that should be used to lookup a model.
Route::get('posts/{post:slug}', function (Post $post) {
//..
});
Route::get('posts/{post:slug}/comments/{comment}', function (Post $post, Comment $comment) {
//..
})->scopeBindings();
Pros/Cons vs Policies
Pros
- You are keeping the logic in one place. Querying for the model and checking if it belongs to the user as part of the same query.
- You’re not uncessarily hydrating a model. If you care about micro optimisations.
Cons
- As the logic is hidden it can seem magic. Might be hard to debug.
Conclusion
And thats how to setup route model binding against an authenticated user.
I’m hoping to release version 1 of the package soon, so I would love people to test, give any feedback or suggestions.