Access Route Model-Bound Models with "#[RouteParameter]"
Photo by Spencer Backman on Unsplash
Introduction
I've recently been using the new #[RouteParameter]
attribute in Laravel, and I've been really enjoying it. It solves an issue I've had with my form requests for quite a while and makes the code much cleaner, in my opinion.
In this article, I'll explain what the #[RouteParameter]
attribute is, how to use it, and the problem it solves.
What is the #[RouteParameter]
Attribute?
The #[Illuminate\Container\Attributes\RouteParameter]
attribute is a new PHP attribute contributed to Laravel by Bastien Philippe (@bastien-phi) in PR #53080. It was released in Laravel v11.28.0.
It allows you to resolve a route parameter directly in the method signatures of your form request methods. But what does this mean? Let's look at some examples of how you can use it.
Note: If you're not sure what PHP attributes are, I've got an article which explains what they are, how to use them, and how to create your own: A Guide to PHP Attributes.
I also have an article about the #[Override]
PHP attribute: The #[Override] Attribute in PHP.
How to Use the #[RouteParameter]
Attribute
The #[RouteParameter]
attribute can be used in the authorize
and rules
methods of your form requests.
Using the #[RouteParameter]
Attribute in the authorize
Method
Imagine we have an App\Http\Requests\UpdateArticleRequest
form request class that is used when updating an App\Models\Article
model. In our form request's authorize
method, we want to check if the authenticated user owns the article they are attempting to update. If they do, we'll allow access; otherwise, we'll deny access.
We'll assume this form request is used by a controller method which is accessed via the following route:
use App\Http\Controllers\ArticleController;
use Illuminate\Support\Facades\Route;
Route::patch('/articles/{article}', [ArticleController::class, 'update']);
Using the #[RouteParameter]
attribute, we can resolve the App\Models\Article
model from the route parameters directly in the method signature, just as we would with a controller method.
Our form request would look something like this:
declare(strict_types=1);
namespace App\Http\Requests;
use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;
final class UpdateArticleRequest extends FormRequest
{
public function authorize(
#[RouteParameter('article')] Article $article
): bool {
return $article->user->is($this->user());
}
// ...
}
As we can see, we've added an $article
parameter to the authorize
method and applied the #[RouteParameter('article')]
attribute to it. The "article"
string passed to the attribute is the name of the route parameter we want to resolve. We've also type-hinted the parameter with the App\Models\Article
model to ensure that the resolved route parameter is an instance of the model we expect.
As an example, if we visited articles/123
, the $article
variable would now be an instance of an App\Models\Article
for the article with the ID 123
.
By using this attribute, we can improve our integrated development environment's (IDE) and static-analysis tools' understanding of our code to provide better code completion and type checking.
Pretty cool, right?
Using the #[RouteParameter]
Attribute in the rules
Method
As well as using the #[RouteParameter]
attribute in the authorize
method, we can also use it in the rules
method of our form request.
For example, you might want to pass the model instance to a rule like this:
declare(strict_types=1);
namespace App\Http\Requests;
use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
final class UpdateArticleRequest extends FormRequest
{
// ...
public function rules(#[RouteParameter('article')] Article $article): array
{
return [
'slug' => Rule::unique('articles')->ignoreModel($article),
];
}
}
Errors When Using the #[RouteParameter]
Attribute
I like to avoid raw strings in my code as much as possible since I'm prone to typos. And let's be honest, bugs caused by typos are always the hardest to spot!
But if you make sure you're type-hinting the parameter with the model class you're expecting, you'll get an error message if you've made a typo in the attribute string. For example, let's say you accidentally typed "artiicle"
instead of "article"
:
declare(strict_types=1);
namespace App\Http\Requests;
use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;
final class UpdateArticleRequest extends FormRequest
{
public function authorize(#[RouteParameter('artiicle')] Article $article): bool
{
return $article->user->is($this->user());
}
// ...
}
Running the above would result in the following error:
App\Http\Requests\UpdateArticleRequest::authorize(): Argument #1 ($article) must be of type App\Models\Article, null given, called in /Users/ashallen/www/laravel-app/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php on line 36
The Problem the #[RouteParameter]
Attribute Solves
Now that we've seen how to use the #[RouteParameter]
attribute, let's look at the problem it solves. Maybe "problem" is too strong of a word, but it's something that's always felt like it needed a workaround to me.
In the past, when I needed to access route parameters in my form requests, I used the route
method on the request object to retrieve them. For example, in the authorize
method of our App\Http\Requests\UpdateArticleRequest
form request, I might have done something like this:
declare(strict_types=1);
namespace App\Http\Requests;
use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;
final class UpdateArticleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->route('article')->user->is($this->user());
}
// ...
}
Running the above code is completely valid, and the $this->route('article)
method call will return the App\Models\Article
model instance resolved from the route parameter. However, it doesn't provide enough information for my IDE or static-analysis tools to understand what the method is returning.
One of the ways I used to get around this was to create a private method on the form request that would return the resolved route parameter. For example:
declare(strict_types=1);
namespace App\Http\Requests;
use App\Models\Article;
use Illuminate\Container\Attributes\RouteParameter;
use Illuminate\Foundation\Http\FormRequest;
final class UpdateArticleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->article()->user->is($this->user());
}
private function article(): Article
{
return $this->route('article');
}
// ...
}
By doing this, it helps my IDE with auto-complete in the authorize
method, but all I'm really doing is shifting the problem to another method (the article()
method in this case). Of course, I can use docblocks to provide more information to my IDE, but I like to avoid them where possible since they can become outdated and are easy to forget to update.
For these reasons, I absolutely love the #[RouteParameter]
attribute. It's a small change, but it makes the code cleaner and easier to understand and keeps my IDE and static-analysis tools happy.
Conclusion
In this article, we've looked at the #[RouteParameter]
attribute in Laravel. We've seen how to use it in the authorize
and rules
methods of form requests and how it solves the problem of retrieving route parameters in these methods.
If you enjoyed reading this post, you might be interested in my 220+ page ebook, "Battle Ready Laravel", which covers similar topics in more depth.
Or, you might want to check out my other 440+ page ebook, "Consuming APIs in Laravel", which teaches you how to use Laravel to consume APIs from other services.
If you'd like to be updated each time I publish a new post, feel free to sign up for my newsletter.
Keep on building awesome stuff! 🚀
driesvints, webtoaster liked this article
Other articles you might like
How to add WebAuthn Passkeys To Backpack Admin Panel
Want to make your Laravel Backpack admin panel more secure with a unique login experience for your a...
Quickest way to setup PHP Environment (Laravel Herd + MySql)
Setting up a local development environment can be a time taking hassle—whether it's using Docker or...
How to set up Laravel Magic Link?
User authentication is crucial for making web applications secure and easy to use. Traditionally, pa...
The Laravel portal for problem solving, knowledge sharing and community building.
The community