Laravel 11 Form validation rules with custom logic in Request with ErrorBag

For a project we need a form for saving subscribers.

You should be able to input 4 values: subscribers_f, subscribers_m, subscribers_d, and the sum of all subscribers.

You can also select a category from a dropdown.

For categories 2 and 3 you need to check that the actual sum (f+m+d) matches the “sum” input field.

If the sum does not match up, you should show a validation error next to the input field.

Thats how you do it:

Create a Request with

php artisan make:request StoreItemRequest

Use the new Request in your update/store controller actions. This is an example of a combined update/store action, it needs the id of the updated Item as a form field.

public function store(StoreItemRequest $request) {
$validated = $request->validated();
if($validated['id'] ?? false) {
$item = Item::find($validated['id']);
$item->update($validated);
} else {
$item = Item::create($validated);
}
return redirect()->route('item.show')->with('success','Item saved.');
}

Then you can create custom validation rules in your StoreItemRequest class like this:

class StoreItemRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'id' => 'integer',
            'category_id' => ['integer','required'],
            'subscribers_f' => ['integer','required'],
            'subscribers_m' => ['integer','required'],
            'subscribers_d' => ['integer','required'],
            'subscribers_sum' => ['integer',
                function (string $attribute, mixed $value, Closure $fail) {
                    if(in_array($this->category_id, [2,3])) {
                        if (($this->subscribers_f + $this->subscribers_m + $this->subscribers_d) != $this->subscribers_sum) {
                            $fail("Sum subscribers (".$this->subscribers_sum.") not equal to sum of f+m+d (".$this->subscribers_f + $this->subscribers_m + $this->subscribers_d.")");
                        }
                    }
                },
            ],
        ]
    }
}

In your blade view you can show the errors as usual (Note: This uses TailwindCSS):

{{html()->text('subscribers_sum')->value($item?->subscribers_sum?? 0)}}
@if($errors->has('subscribers_sum'))
    <div class="alert alert-danger alert-dismissible fade show sm:rounded-lg p-2" role="alert">{{$errors->first('subscribers_sum')}}</div>
@endif

If you want to prefill the Form values when updating, i recommend using livewire for your form and prefilling the values there in your mount() action. There you can implement a logic that respects old() and existing values.