laravel-working-group/laravel-form-requests-improved

Supercharges your FormRequest class

dev-main 2025-01-17 21:33 UTC

This package is not auto-updated.

Last update: 2025-03-29 20:22:01 UTC


README

  1. Installation
  2. Compound validation rules
    1. Don't
    2. Do
    3. Available compound rules
  3. Fluent validation rules
  4. FormRequest union type dependency injection in controller methods
  5. Post-validation sanitization

Installation

composer install laravel-working-group/laravel-form-requests-improved

Compound validation rules

When you have a FormRequest like this,

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class MyFormRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'foo' => 'require_without:bar',
        ];
    }
}

it's usually not just this, but actually also the other way round:

public function rules(): array
{
    return [
        'foo' => 'require_without:bar',
        'bar' => 'require_without:foo',
    ];
}

often times even with the negated role for each:

public function rules(): array
{
    return [
        'foo' => [ 'require_without:bar', 'prohibits:bar' ],
        'bar' => [ 'require_without:foo', 'prohibits:foo' ],
    ];
}

this is quite a lot for actually just one rule and we can easily forget to add one or multiple of these but due to the nature of how FormRequests work, we can't really chance that. However, we can use syntactic sugar for this:

use LaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\FormRequest;

// ...

public function rules(): array
{
    return [
        self::prohibitEither('foo', 'bar'),
        self::requireEither('foo', 'bar'),
    ];
}

or even just:

public function rules(): array
{
    return [
        self::eitherOr('foo', 'bar'),
    ];
}

⚠️ WARNING\ Make sure to extend your request from LaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\FormRequest and not from Laravel's Illuminate\Foundation\Http\FormRequest. Don't worry, it itself extends from Laravel's FormRequest so you can do everything you can also do with Laravel's FormRequest and more.

If for some reason, you don't want are are unable to use LaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\FormRequest, you can alternatively use the LaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\Concerns\ValidateCompoundRules concern directly.

⚠️ WARNING\ Avoid using array_merge() or the + operator on these, because that would overwrite rules for the same field:

❌ Don't

public function rules(): array
{
    return [
        self::requireEither('foo', 'bar'),
        'foo' => 'string'
    ];
}

That would result in the final result being

[
    'foo' => 'string'
]

Because requireEither() generates the following:

[
    'foo' => 'require_without:bar',
    'bar' => 'require_without:foo',
]

which then would be overwritten by

[
    'foo' => 'string'
]

✅ Do

public function rules(): array
{
    return [
        self::requireEither('foo', 'bar'),
        [ 'foo' => 'string' ]
    ];
}

Internally, this will be merged by array_merged_recursive().

⚠️ WARNING\ The array_merged_recursive() call is not part of Illuminate\Foundation\Http\FormRequest. You need to use LaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\FormRequest for this. If you don't want or unable to use it, you need to overwrite the validationRules() method on your own.

Available compound rules

RuleCompound of
prohibitEitherprohibits
requireEitherrequired_without
eitherOrprohibits and required_without
beforeAfterbefore and after

Fluent validation rules

Ever wanted to define a rule neither with array nor string syntax but object-oriented? This can be done with fluent validation rules!

So, instead of this:

public function rules(): array
{
    return [
        'error' => [ 'string', 'required', 'max:30' ],
        'avatar' => [ 'dimensions:min_width=100' ],
    ];
}

you can do this:

use Illuminate\Validation\Rules\Dimensions;

use function LaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\image;
use function LaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\string;

// …

public function rules(): array
{
    return [
        'error' => string()->required()->length(max: 30),
        'avatar' => image()->dimensions(fn(Dimensions $dimensions) => $dimensions->minWidth(50)),
    ];
}

FormRequest union type dependency injection in controller methods

Ever had the case where a third-party service would call a webhook in your API but you would not know the exact format, because there would be one format if the action the service executed and informs you about was successful and an entirely different format when it failed?

In Laravel, you have no way to solve this cleanly. However, with this package, you can just use the service container attribute LaravelWorkingGroup\ImprovedFormRequests\Container\Attributes\FormRequest:

<?php

namespace App\Http\Controllers;

use LaravelWorkingGroup\ImprovedFormRequests\Container\Attributes\FormRequest;
use App\Http\Requests\FailedWebhook;
use App\Http\Requests\SuccessfulWebhook;
use Symfony\Component\HttpFoundation\Response;

class WebhookController
{
    public function webhook(
        #[FormRequest(FailedWebhook::class, SuccessfulWebhook::class)] $request
    )
    {
        // $request can be either `App\Http\Requests\FailedWebhook` or `App\Http\Requests\SuccessfulWebhook`
        // and can contain either `error` and `message`, or `id` and `name` in our case
        if ($request instanceof FailedWebhook) {
            return response("[{$request->error}] {$request->message}", Response::HTTP_BAD_REQUEST);
        }

        [ 'id' => $id, 'name' => $name ] = $request->validated();
    }
}

Post-validation sanitization

You can also sanitize your data after the validation has been successful and before the FormRequest is passed to the controller, so you can simply get data from the request without having to worry about the data type in the controller.

public function sanitizationRules(): bool|array
{
    return [
        'error_code' => 'int',
        'error_message' => 'string',
        'id' => 'model:user',
    ];
}

So this results in:

<?php
public function webhook(WebhookFormRequest $request)
{
    dd($request->all());
    /*
        [
            'error_code' => 500,
            'error_message' => \Illuminate\Support\Stringable('Unwanted'),
            'id' => \App\Models\User(1),
        ]
    */
}

You can even let the FormRequest infer the sanitization rule of a field for you:

public function rules(): array {
    return [
        'error_code' => 'integer',
    ];
}

public function sanitizationRules(): bool|array
{
    return [
        'error_code' => true,
    ];
}

This will infer 'int' as a sanitization rule and cast the value accordingly.

You can also let the FormRequest infer all sanitization rules like this:

public function sanitizationRules(): bool|array
{
    return true;
}