Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[11.x] Introduce RouteParameter attribute #53080

Merged
merged 1 commit into from
Oct 11, 2024

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,
            ],
        ]);
    }
}

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.

3 participants