Skip to content

Conversation

@bastien-phi
Copy link
Contributor

Accessing route parameter outside the Controller, in the FormRequest for example, can be done in various ways. In order to have correct IDE completion and good static analysis, we can do :

class UpdatePost extends FormRequest
{
    public function authorize(#[CurrentUser] User $user): bool
    {
        /** @var Post $post */
        $post = $this->route('post');
        
        return $post->user_id === $user->id;
    }
    
    public function rules(): array
    {
        /** @var Post $post */
        $post = $this->route('post');

        return [
            'slug' => ['required', 'string', Rule::unique(Post::class, 'slug')->ignore($post->id);
            // ...
        ];
    }
}
/** 
 * @property-read Post $post
 */
class UpdatePost extends FormRequest
{
    public function authorize(#[CurrentUser] User $user): bool
    {
        return $this->post->user_id === $user->id;
    }
    
    public function rules(): array
    {
        return [
            'slug' => ['required', 'string', Rule::unique(Post::class, 'slug')->ignore($this->post->id);
            // ...
        ];
    }
}

Both ways feel a bit tricky to me and not very elegant IMHO.

That's why I propose the introduction of the RouteParameter attribute which can streamline the syntax, leveraging existing ContextualAttribute and the container :

class UpdatePost extends FormRequest
{
    public function authorize(#[CurrentUser] User $user, #[RouteParameter('post')] Post $post): bool
    {
        return $post->user_id === $user->id;
    }
    
    public function rules(#[RouteParameter('post')] Post $post): array
    {
        return [
            'slug' => ['required', 'string', Rule::unique(Post::class, 'slug')->ignore($post->id);
            // ...
        ];
    }
}

The attribute can be used on any type of route parameter : Model, enum, string, ...

@taylorotwell taylorotwell merged commit 3818c2a into laravel:11.x Oct 11, 2024
33 checks passed
@bastien-phi bastien-phi deleted the route_parameter_attribute branch October 11, 2024 16:03
@bastien-phi
Copy link
Contributor Author

Thanks !

timacdonald pushed a commit to timacdonald/framework that referenced this pull request Oct 15, 2024
@andrey-helldar
Copy link
Contributor

andrey-helldar commented Nov 7, 2024

Everything is great, but it lacks support for the prepareForValidation method:

Declaration of App\Http\Requests\Private\Contest\UpdateRequest::prepareForValidation(App\Models\Contest $contest): void
must be compatible with Illuminate\Foundation\Http\FormRequest::prepareForValidation() 
class UpdateRequest extends Request
{
    public function rules(#[RouteParameter('contest')] Contest $contest): array
    {
        return [
            // some rules
        ];
    }

    protected function prepareForValidation(#[RouteParameter('contest')] Contest $contest): void
    {
        $this->merge([
            'slug' => $this->firstOrSecond('slug', 'title'),
        ]);

        $this->mergeIfMissing([
            'params' => [
                'winnersCount' => $contest->params?->winnersCount
                    ?? DefaultCount::WINNERS,

                'audienceWinnersCount' => $contest->params?->audienceWinnersCount
                    ?? DefaultCount::AUDIENCE_WINNERS,
            ],
        ]);
    }
}

Crutch for that:

class UpdateRequest extends Request
{
    public function rules(#[RouteParameter('contest')] Contest $contest): array
    {
        return [
            // some rules
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge([
            'slug' => $this->firstOrSecond('slug', 'title'),
        ]);

        /** @var Contest $contest */
        $contest = $this->route('contest');

        $this->mergeIfMissing([
            'params' => [
                'winnersCount' => $contest->params?->winnersCount
                    ?? DefaultCount::WINNERS,

                'audienceWinnersCount' => $contest->params?->audienceWinnersCount
                    ?? DefaultCount::AUDIENCE_WINNERS,
            ],
        ]);
    }
}

@medabkari
Copy link
Contributor

@bastien-phi Hey man, is this feature still available? When I dd() the attribute argument, I get an empty Model. I followed the syntax proposed above exactly.

@andrey-helldar
Copy link
Contributor

andrey-helldar commented Oct 18, 2025

@bastien-phi Hey man, is this feature still available? When I dd() the attribute argument, I get an empty Model. I followed the syntax proposed above exactly.

Check if the SubstituteBindings middleware is active on the route you need.

@medabkari
Copy link
Contributor

medabkari commented Oct 18, 2025

Check if the SubstituteBindings middleware is active on the route you need.

@andrey-helldar It is active, Have you used it before, and worked properly? I don't know why I'm getting an empty Model when using it.

@bastien-phi
Copy link
Contributor Author

Please provide your route definition, controller method and the usage you make of the attribute

@andrey-helldar
Copy link
Contributor

andrey-helldar commented Oct 18, 2025

@andrey-helldar It is active, Have you used it before, and worked properly? I don't know why I'm getting an empty Model when using it.

Yes, it works for me. BUT I haven't run the composer update console command on this project in a while. The code in my comment is copy-pasted (I removed the unnecessary parts, leaving the important ones for the example).

@medabkari
Copy link
Contributor

Please provide your route definition, controller method and the usage you make of the attribute

Route definition:

Route::patch('/company/{company}', [CompanyController::class, 'update']);

Controller method:

public function update(CompanyRequest $request, Company $company) {}

FormRequest rules() method:

use App\Models\Company\Company;

public function rules(#[RouteParameter('company')] Company $company)
{   
    // Returns empty model
    dd($company);

    // Returns actual binded modal
    dd($this->route()->parameter('company'));
}

@rodrigopedra
Copy link
Contributor

@medabkari I created this repo with some test code for your issue:

https://github.com/rodrigopedra/pr-53080

You can install it by running:

git clone https://github.com/rodrigopedra/pr-53080.git
cd pr-53080
composer run setup

And then run:

composer run dev

To start the dev server.

As of now, it works as expected.

Can you please take a look and provide any info on what you are doing differently?

@medabkari
Copy link
Contributor

@rodrigopedra Thank you for the repo, I'll check it out, but from my earlier reply, do you see anything that could've caused the issue?

@rodrigopedra
Copy link
Contributor

do you see anything that could've caused the issue?

Not really. I tried to mimic closely to your report.

@medabkari
Copy link
Contributor

do you see anything that could've caused the issue?

Not really. I tried to mimic closely to your report.

I will try to push the framework to a further version, and see if the issue persists, maybe it's a bug. Based on the description above of the feature, I don't think something's wrong from my end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants