laravel-working-group / laravel-form-requests-improved
Supercharges your FormRequest class
Requires (Dev)
- orchestra/testbench: ^9.3
- phpunit/phpunit: ^11.4
This package is not auto-updated.
Last update: 2025-03-29 20:22:01 UTC
README
- Installation
- Compound validation rules
- Fluent validation rules
- FormRequest union type dependency injection in controller methods
- 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'sIlluminate\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 ofIlluminate\Foundation\Http\FormRequest
. You need to useLaravelWorkingGroup\ImprovedFormRequests\Foundation\Http\FormRequest
for this. If you don't want or unable to use it, you need to overwrite thevalidationRules()
method on your own.
Available compound rules
Rule | Compound of |
---|---|
prohibitEither | prohibits |
requireEither | required_without |
eitherOr | prohibits and required_without |
beforeAfter | before 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;
}