Support the ongoing development of Laravel.io →
Article Hero Image

Access Route Model-Bound Models with "#[RouteParameter]"

9 Dec, 2024 6 min read

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! 🚀

Last updated 3 days ago.

driesvints, webtoaster liked this article

2
Like this article? Let the author know and give them a clap!
ash-jc-allen (Ash Allen) I'm a freelance Laravel web developer from Preston, UK. I maintain the Ash Allen Design blog and get to work on loads of cool and exciting projects 🚀

Other articles you might like

Article Hero Image December 13th 2024

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...

Read article
Article Hero Image December 13th 2024

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...

Read article
Article Hero Image December 5th 2024

How to set up Laravel Magic Link?

User authentication is crucial for making web applications secure and easy to use. Traditionally, pa...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.