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

Request Validation Bundle Laravel Package

mkoprek/request-validation-bundle

A validation bundle for PHP applications that applies rules to incoming HTTP request data, helping you reject malformed or missing input before it reaches your controllers. Define rules once and get consistent validation behavior and error responses across your request handling.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the name carefully before you composer require. This is a bundle, which in Symfony parlance means it targets Symfony's HTTP kernel — not Laravel. There is no service provider, no config/request-validation.php, and no FormRequest analogue. If you are a Laravel developer, your first decision is whether you're actually working in a Symfony codebase (or a Laravel app that has pulled in Symfony components piecemeal) before you go any further.

Then read the 3.0.1 release note before you write a single route. It says "Subscriber enabled only for /api paths." That single line changes the contract of the package from "validation happens" to "validation happens, but only if your route looks like an API route." Everything else in this assessment follows from it.

Minimal steps (Symfony host app):

  1. composer require mkoprek/request-validation-bundle:3.0.1 — pin the exact version. A 3.x line now exists, which means 2.x → 3.x had a major bump you have no changelog for. Then open vendor/mkoprek/request-validation-bundle/composer.json and check the require block: whatever symfony/framework-bundle constraint it declares is the ceiling on your app's Symfony version. If you're on 7.x and it pins ^5.4|^6.0, stop here.
  2. Register the bundle in config/bundles.php (Mkoprek\RequestValidationBundle\RequestValidationBundle::class => ['all' => true]) — confirm the FQCN from the package's src/ root, since the exact namespace casing is the kind of thing that silently breaks autoloading.
  3. Grep src/ for the string /api before anything else. You need to find whether the /api restriction is a hardcoded literal in the subscriber's getSubscribedEvents/supports()/onKernelRequest, or a bundle config value (request_validation.path_prefix or similar) under config/. This determines whether you adopt the package or fight it. If it's hardcoded and your routes live at /v1/..., /graphql, or a locale-prefixed /en/api/..., the package is now unusable for you without patching.
  4. Look at the attribute/annotation class and the subscriber that acts on it. That pair, plus the path guard you just found, defines the entire contract.
  5. Check tests/ for a functional test that boots a kernel, hits a path, and asserts a response. Given at least three releases now exist, look for one that specifically asserts the subscriber does not fire outside /api — if that test is missing, the guard is untested and you should assume the matching logic is naive str_starts_with on the raw request URI.
  6. Smoke-test two endpoints, not one: a POST /api/... that should 422, and a POST /anything-else that should be untouched. Do both on day one.

First use case — the one that justifies adoption: a POST /api/orders endpoint that accepts a JSON body, fails fast with a structured 422 instead of hand-rolled if (!isset($payload['sku'])) blocks in the controller. In Laravel terms you're replacing an inline Validator::make() call with a declarative, reusable rule set attached to the request payload. Note that the package's own /api scoping now lines up with Laravel's default api middleware group prefix, so if you're porting an API surface across, your routes probably already match. If the bundle does anything less than that cleanly, stop — Symfony 6.3+ ships #[MapRequestPayload] natively and you don't need a zero-star dependency to get it.

Implementation Patterns

Payload-as-DTO, validated by attribute. The pattern these bundles almost universally implement: define a plain PHP class with typed public properties, decorate it with the package's validation attribute, and type-hint it in the controller action. Symfony's argument resolver instantiates and validates it, throwing on violation before your controller body runs. This is the closest thing to a Laravel FormRequest — the difference is that a FormRequest is the request, whereas here you get a hydrated DTO and the raw Request separately. Keep the DTO free of persistence logic; map it to your entity/service inside the controller.

Let the /api scope be your web/API boundary. The 3.0.1 behaviour is, read charitably, a feature: your HTML form controllers keep doing Symfony's native Form component validation, and only JSON API traffic goes through attribute-driven payload validation. Lean into it. Keep every validated DTO behind routes that genuinely live under /api, and if you have a second API surface (/v1, /admin/api, /internal) decide now whether to (a) mount it under /api, (b) open a second subscription via config if one exists, or (c) wrap those controllers in a tiny custom argument resolver that reuses the same attribute. Do not discover this per-endpoint; it becomes inconsistent validation across your app, which is worse than no validation.

Declarative rules, imperative fallback. Use the attribute for shape and type (required, string, length, enum, nested objects). Push cross-field and database-dependent rules — "SKU must exist," "end date after start date" — into a Symfony Constraint validator or a service call after hydration. Don't fight the framework to express relational rules declaratively; the ergonomics degrade sharply.

Validation groups as your sometimes/Rule::when equivalent. Most implementations forward a groups option. Use it for create-vs-update divergence (password required on create, optional on patch) rather than duplicating DTOs. Verify the bundle actually honors groups — some thin wrappers hardcode the Default group and silently ignore the parameter.

Normalize the failure response once. Whatever exception the bundle throws (ValidationFailedException or a package-specific type), register a single kernel exception listener that converts it into your house error envelope. Laravel devs will instinctively expect {message, errors: {field: [..]}}; Symfony's ConstraintViolationList gives you getPropertyPath() and getMessage(), so write the mapper explicitly and put it under test. Do this on day one — otherwise every controller grows its own error handling.

One DTO per operation, not per resource. CreateOrderPayload, UpdateOrderPayload. Shared nested value objects (Address, Money) get their own attribute-decorated classes and compose. This mirrors how you'd split StoreOrderRequest / UpdateOrderRequest and keeps validation groups from becoming a matrix.

Write the routing assertion as a test, not a manual check. A three-line WebTestCase: POST /api/orders with a bad body asserts 422; POST /orders with the same body asserts 200/controller-behaviour. That test is your alarm when the subscriber's path matching changes again in 3.0.2.

Integration tip for Laravel-side readers: if you're bridging Symfony validation into a Laravel app, skip this bundle and use illuminate/validation with spatie/laravel-data, or FormRequest directly. You'd be adopting a kernel-coupled bundle for one class's worth of behavior.

Gotchas and Tips

The new /api-only behaviour is the highest-probability footgun in the whole package. Three distinct variants of the same failure:

  • Silent no-op: your route is /orders not /api/orders, the subscriber never runs, and the DTO arrives with nulls and no exception. Looks like validation "passed."
  • Prefix false-negative: /api/v1/orders almost certainly matches a /api prefix check, but /en/api/orders, /apiary/orders, and a Symfony app deployed under a subdirectory with a base path can all behave unexpectedly depending on whether the check uses getPathInfo() or getRequestUri(). Test your real path shape, not a sanitised one.
  • Prefix false-positive: /apixyz/orders matching a naive str_contains/loose str_starts_with — validate that the guard is segment-aware. Also note this shipped in a patch release. If 3.0.0 enabled the subscriber globally and 3.0.1 narrowed it, anyone who pinned ^3.0 and deployed 3.0.0-to-3.0.1 lost validation on non-/api routes without a major version warning. Pin 3.0.1 exactly and read 3.0.0's notes if you can find them.

Non-HTTP contexts will hit the guard too. CLI commands, Messenger workers, and sub-requests (forward(), fragments, ESI) may not have a meaningful path. If the subscriber's path check dereferences the request URI without a null-safety guard, you get either a fatal or a silently skipped validation in a queued job that "worked" in the browser. If you validate payloads inside handlers, keep a plain Validator call there rather than relying on the attribute.

Treat adoption as a liability decision, not a convenience one. No star count, no dependent count, and no downstream usage are present in the metadata supplied here — meaning you have zero external evidence anyone has shaken out these edge cases. There is no community to answer questions. Concretely: pin the exact version in composer.json (not ^), commit composer.lock, and if the package is more than a thin wrapper, plan to vendor it into src/Support/ and own it. MIT means you legally can — and the /api hardcoding is exactly the kind of thing you'll end up patching anyway.

Version drift is the most likely failure mode. A bundle whose lineage started in 2023 and now sits at 3.0.1 against a current Symfony will hit deprecations in the argument resolver / Request APIs first, then hard breaks. Run the test suite locally after install; a package with no CI badges and no dependents has likely never been run on your Symfony minor.

Type coercion surprises. JSON "123" vs 123, empty string vs absent key, and null vs missing — bundles differ on whether they coerce before validating. Assert these three cases explicitly in a test; it's where "works in Postman, breaks in prod" lives. The /api guard compounds this: if the request goes through an API gateway that rewrites paths, the path your kernel sees may already be normalised or prefixed.

Error-shape mismatch will break your frontend. If your JS client was written against Laravel's {errors: {field: []}} and this returns Symfony's serialized violation list, every form breaks at once. Map it centrally (see above) and version the contract.

Debugging: use the Symfony profiler's Validator panel to see which constraints ran and in which group — far faster than dump() inside the resolver. If a rule "isn't firing," check three things in this order: (1) is the request path under /api, (2) is the group active, (3) is the property path correct. After 3.0.1, cause (1) is more likely than the other two combined.

Extension points worth knowing: custom Constraint + ConstraintValidator for domain rules, a kernel exception listener for response shaping, and the resolver's priority/ordering when other bundles (or #[MapRequestPayload]) also resolve controller arguments. That last one is the sneaky source of "my DTO is sometimes null" — and now it interacts with the path guard: a native resolver with higher priority will win on /api routes whether or not this bundle's subscriber fires.

Have an exit. Before adopting, write the three-line fallback: Symfony 6.3+ #[MapRequestPayload] with the native Validator, or a 40-line custom argument resolver that you can scope to any path prefix you like rather than one hardcoded to /api. If the fallback is genuinely that short — and for this feature set it usually is — the dependency is buying you very little and costing you an unmaintained supply-chain surface. The 3.0.1 release is a reminder that this package's behaviour can change under you in a patch bump; the fallback is also your rollback plan.

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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings