Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Form Request Bundle Laravel Package

somnambulist/form-request-bundle

Symfony bundle bringing Laravel-style form requests to Symfony: encapsulate validation, authorization, and input handling in dedicated request classes that resolve automatically as controller arguments, keeping controllers thin and request logic reusable and testable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register. Pull the package with Composer and, unless a Flex recipe is available for your version, register the bundle manually:

composer require somnambulist/form-request-bundle
// config/bundles.php
return [
    // ...
    Somnambulist\Bundles\FormRequestBundle\FormRequestBundle::class => ['all' => true],
];

If the bundle is not registered, the argument resolver never runs and your type-hinted request will either fail to instantiate or be silently populated with no validation — the most common "why did nothing happen?" failure with this class of package.

Create your first request class. Extend the package's base FormRequest and implement rules() using Laravel-style pipe/dot rule strings. authorize() defaults to allowing the request; override it when you want policy checks colocated with validation:

namespace App\Request;

use Somnambulist\Bundles\FormRequestBundle\Http\FormRequest;

class CreateUserRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'email'    => 'required|email|max:255',
            'name'     => 'required|string|max:255',
            'password' => 'required|string|min:8|confirmed',
        ];
    }

    public function authorize(): bool
    {
        return $this->user()?->isAdmin() ?? false;
    }
}

Wire it into a controller by type-hinting the request class as an action argument. The package resolves it from the current Symfony Request, hydrates inputs, runs authorize() then validation, and hands you a populated object (or aborts before your code runs):

public function store(CreateUserRequest $request): Response
{
    $data = $request->validated(); // clean, whitelisted input
    // dispatch a command / persist $data
}

First use case / verification. Ship one endpoint behind the request class, then hit it with (a) a valid JSON payload, (b) a missing field, and (c) an unauthorized user, and inspect the response for each. That single round-trip tells you how the package renders validation failures (422 JSON vs. redirect-with-errors), what an authorization failure looks like, and whether confirmed-style rules behave as you expect.

Where to look first. The base FormRequest (methods rules, authorize, messages, attributes, validated, all, input) is your contract. Then read the argument resolver (to understand when it activates) and the rule parser (to see which Laravel rules are actually translated into Symfony constraints). The exception/error renderer is the third stop — that is what you customize for API error shape.

Implementation Patterns

One request class per action, not per resource. StoreUserRequest and UpdateUserRequest diverge quickly (required vs. sometimes, password vs. nullable|confirmed). Splitting them keeps rules declarative and avoids conditional if ($this->isMethod('PATCH')) branches in rules(). When PATCH fields overlap heavily, compose shared rule arrays via a small trait or static method rather than inheriting one request from another.

Treat the form request as a validation boundary, not a DTO. Use validated() to obtain the whitelisted subset, then immediately map to a command/DTO/value object and pass that into your handler. Do not leak the FormRequest into services or domain code — it couples everything to HTTP and makes the validated data shape implicit.

Put authorization in authorize() only when it is cheap and request-local. "Is this user allowed to create a post?" belongs there and keeps controllers thin. Anything needing a loaded entity (e.g., "can this user edit this post?") is better done with a Symfony voter after you have resolved the entity, or by fetching and denyAccessUnlessGranted inside the controller. Form-request authorize() runs before your action, so you cannot rely on entities you load in the action.

Use messages() and attributes() for human-facing surfaces. messages() keyed by field.rule and attributes() keyed by field name give you localized, branded errors without touching the parser. For APIs, skip custom messages and instead normalize the error payload once via an exception listener so every endpoint returns the same shape (e.g., RFC 7807 problems with a errors map).

Nested and array input. Dot-notation rules (items.*.sku => required|string) are the pattern to reach for; verify in the parser that wildcards are expanded and that array-level rules (items => required|array|min:1) are applied to the parent. For deeply nested or polymorphic payloads, validate the outer shell with the form request and the inner variants with a dedicated Symfony validator call or a Collection constraint — do not try to express everything in one rule string.

Testing pattern. Functional tests with WebTestCase asserting status (422/403/201) and the error body catch resolver and rendering regressions. Separately, unit-test each request by instantiating it with a constructed Symfony Request and asserting rules() and authorize() — cheap, fast, and documents the contract. Keep one "valid sample payload" fixture per request reused across tests so schema drift is visible.

Incremental migration. For an existing controller doing manual $validator->validate(...), move rules into a request class per action and switch the type-hint; leave the controller body untouched initially, then replace manual extraction with validated() in a second pass. This keeps diffs reviewable and isolates behavior changes to validation/error rendering.

Gotchas and Tips

  • Rule strings are a subset, not Laravel parity. Rules that need Laravel's container or DB connection — unique, exists, regex, custom Rule objects — often have no mapping or a subtly different one. Read the parser source and add a test per rule you rely on; a silently ignored rule is worse than a hard failure. When you need more, use a Symfony constraint or a callback rather than contorting a string.
  • Registration and type-hint resolution are the usual culprits. If validation appears to be skipped, check: bundle registered, argument is the concrete request class (not an interface or parent), and no other argument resolver is claiming it first. Symfony's own #[MapRequestPayload] / RequestPayloadValueResolver (6.3+) can collide conceptually — pick one approach per argument.
  • validated() vs all() / input(). all() may include CSRF tokens, _method, and extra client-sent fields. Always whitelist via validated() before persisting, and be aware validated() returns only keys with rules — fields you forgot to rule silently disappear.
  • Content negotiation changes the failure path. JSON requests typically get a 422 JSON body; HTML form posts typically redirect back with flashed errors. If you build an API but test with a browser, you will see redirects and misdiagnose the feature as broken. Force Accept: application/json in tests.
  • Authorization failure is not validation failure. Confirm what a false from authorize() produces (403 vs. a validation-style 422). If it differs from your app's policy expectations, convert policy checks to voters and keep authorize() returning true.
  • Method spoofing. A PATCH submitted as POST + _method=PATCH should still present isMethod('PATCH') semantics. If your rules() branch on method, verify through the real Symfony request rather than assuming.
  • Avoid DB or heavy work in rules(). It runs on every request to the endpoint; treat it as static configuration. If a rule must be data-dependent (e.g., allowed transitions), compute it once or move the check into a custom constraint.
  • File uploads and confirmed-style rules. Check explicitly how uploaded files are validated (UploadedFile constraints) and whether confirmed expects password_confirmation in the body. These are the two rules most likely to behave differently from Laravel expectations.
  • Small-package risk. With few stars and no dependents, pin the version, read the changelog before upgrades, and confirm Symfony-version support. For new code, also evaluate Symfony's native #[MapRequestPayload] + validator or a hand-rolled validator-in-controller approach; the form-request ergonomics are nice, but you should know the fallback if the package lags a Symfony major.
  • Extension points. Custom error rendering via a kernel exception listener is the cleanest hook; custom rules via Symfony constraints referenced from rules() (if supported) or by extending the parser. Keep local extensions in App\ namespace and covered by tests so a package upgrade surfaces breakage early.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core