adamsafr/form-request-bundle
Symfony bundle bringing Laravel-style Form Requests: create custom request classes with validation rules that run before controller actions. Simple install via Composer/Flex, optional JSON error listeners for access denied, validation and JSON decode failures.
Installation:
composer require adamsafr/form-request-bundle
Enable the bundle in config/bundles.php (Symfony 4+) or AppKernel.php (older versions):
Adamsafr\FormRequestBundle\AdamsafrFormRequestBundle::class => ['all' => true],
Create a Form Request Class:
Extend Adamsafr\FormRequestBundle\Request\FormRequest and define validation rules in the rules() method:
namespace App\Request;
use Adamsafr\FormRequestBundle\Request\FormRequest;
class StoreUserRequest extends FormRequest
{
public function rules()
{
return [
'name' => 'required|string|max:255',
'email' => 'required|email',
];
}
}
Use in Controller: Inject the request class into your controller action:
use App\Request\StoreUserRequest;
use Symfony\Component\HttpFoundation\Request;
public function store(StoreUserRequest $request)
{
// Validation is automatically run; $request->isValid() checks status
$data = $request->validated(); // Returns validated data
// ...
}
Replace a standard Request object in a controller with a custom FormRequest to centralize validation logic. For example, replace:
public function create(Request $request)
{
$this->validate($request, [
'name' => 'required',
'email' => 'email',
]);
// ...
}
with:
public function create(StoreUserRequest $request)
{
$data = $request->validated(); // Already validated
// ...
}
Centralized Validation:
Move validation logic from controllers to dedicated FormRequest classes. Example:
class UpdateProfileRequest extends FormRequest
{
public function rules()
{
return [
'avatar' => 'nullable|image|mimes:jpeg,png|max:2048',
'bio' => 'nullable|string|max:500',
];
}
}
Authorization:
Override authorize() to gate access:
public function authorize()
{
return $this->user()->isAdmin(); // Assume $this->user() is available
}
Custom Validation Logic:
Use validateCustomRules() for complex rules:
public function rules()
{
return [
'password' => ['required', [$this, 'validatePasswordStrength']],
];
}
public function validatePasswordStrength($attribute, $value, $fail)
{
if (!preg_match('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/', $value)) {
$fail('Password must be at least 8 characters with uppercase, lowercase, and a number.');
}
}
Dependency Injection:
Inject services into FormRequest via constructor:
public function __construct(
private UserRepository $userRepository,
private LoggerInterface $logger
) {}
public function rules()
{
return [
'email' => ['required', [$this->userRepository, 'isEmailAvailable']],
];
}
Reusable Requests:
Extend base FormRequest for shared validation (e.g., BaseApiRequest for API-wide rules).
API Development:
Use FormRequest for API endpoints to handle validation before reaching controllers:
// src/Controller/UserController.php
public function store(CreateUserRequest $request)
{
$user = User::create($request->validated());
return new JsonResponse(['user' => $user], 201);
}
Form Handling:
Combine with Symfony’s Form component for hybrid validation:
public function update(UpdateProfileRequest $request, User $user)
{
$form = $this->createForm(ProfileType::class, $user);
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
$user->update($form->getData());
}
}
Testing:
Mock FormRequest in tests:
$request = $this->createMock(StoreUserRequest::class);
$request->method('isValid')->willReturn(true);
$request->method('validated')->willReturn(['name' => 'Test']);
$this->controller->store($request);
Symfony Event Dispatcher:
Listen to kernel.request to log validation failures:
$event->getRequest()->addListener(
FormRequest::EVENT_VALIDATION_FAILED,
[$this->logger, 'logValidationFailure']
);
Custom Error Responses:
Override handleValidationError() in FormRequest:
protected function handleValidationError($errors)
{
return new JsonResponse(['errors' => $errors], 422);
}
Localization: Use Symfony’s translation system for validation messages:
public function rules()
{
return [
'name' => 'required|string',
];
}
public function messages()
{
return [
'name.required' => 'This field is required.',
];
}
Dynamic Rules: Generate rules based on runtime conditions:
public function rules()
{
$rules = ['email' => 'required|email'];
if ($this->user()->isAdmin()) {
$rules['role'] = 'required|in:admin,user';
}
return $rules;
}
Kernel Version Mismatch: The bundle was last updated in 2020 and may not support newer Symfony versions (e.g., 6.x). Test thoroughly or fork the package if needed.
Request Object Override:
The bundle replaces Symfony’s default Request object with its own. Ensure middleware/services expecting Request are compatible:
// Avoid this if using the bundle:
$request = $this->get('request_stack')->getCurrentRequest();
// Use FormRequest instead.
Validation Timing:
Validation runs before the controller action. Avoid relying on $request->request->all() before validation—use $request->validated() or $request->get() for raw data.
Circular Dependencies:
Avoid injecting controllers or other FormRequest classes into each other to prevent circular references.
Flex Recipes:
If using Symfony Flex, the bundle may not auto-configure. Manually enable it in config/bundles.php:
return [
// ...
Adamsafr\FormRequestBundle\AdamsafrFormRequestBundle::class => ['all' => true],
];
Validation Errors:
Check $request->errors() for validation messages:
if (!$request->isValid()) {
return new Response($request->errors()->first(), 400);
}
Authorization Failures:
Override handleAuthorizationError() to customize responses:
protected function handleAuthorizationError()
{
return new JsonResponse(['error' => 'Unauthorized'], 403);
}
Logging: Enable debug mode to see validation traces:
$this->container->get('debug')->addErrorCollector(new ValidationErrorCollector());
Bundle Disabling:
Set 'all' => false in bundles.php to disable the bundle globally (not recommended for intended use).
Custom Event Names:
The bundle uses Adamsafr\FormRequestBundle\Request\FormRequest::EVENT_VALIDATION_FAILED. Override in subclasses if needed:
const EVENT_CUSTOM_VALIDATION = 'custom.validation.event';
Request Stack:
The bundle modifies Symfony’s request_stack. Ensure no other bundles interfere with request resolution.
Custom Validators: Create a validator class and attach it to rules:
$validator = new UniqueUsernameValidator();
$rules['username'] = [$validator, 'validate'];
Request Factories:
Extend FormRequestFactory to customize request creation:
$factory = new CustomFormRequestFactory($container);
$request = $factory->create(StoreUserRequest::class);
Middleware Integration: Use the bundle’s events to trigger middleware logic:
$dispatcher->addListener(
FormRequest::EVENT_VALIDATED,
[$middleware, 'onValidatedRequest']
);
API Platform: Combine with API Platform for automatic request validation:
How can I help you explore Laravel packages today?