azyouness/request-to-form-bundle
Installation:
composer require azyouness/request-to-form-bundle
Ensure the bundle is registered in config/bundles.php if Flex didn't auto-register it.
First Use Case:
Add the #[MapRequestToForm] attribute to a controller argument to automatically map and validate request data into a form-backed entity or DTO.
#[Route('/posts', methods: ['POST'])]
public function create(
#[MapRequestToForm]
Post $post,
): JsonResponse {
return $this->json($post);
}
data_class of registered forms.data_class), explicitly specify it:
#[MapRequestToForm(PostType::class)]
Key Files to Review:
src/Attribute/MapRequestToForm.php (attribute definition).src/RequestToFormMapper.php (service for manual mapping).src/Exception/FormValidationFailedException.php (custom exception for invalid forms).Use #[MapRequestToForm] to replace manual form handling:
#[Route('/posts/{id}', methods: ['PUT'])]
public function update(
#[MapRequestToForm] // Resolves existing entity + submits request
Post $post,
): JsonResponse {
// $post is pre-loaded (e.g., via Doctrine) and updated with request data.
}
#[MapEntity]) for hybrid resolution.Receive the FormInterface object when you need access to form metadata (e.g., errors, submitted data):
public function create(
#[MapRequestToForm(PostType::class)]
FormInterface $form,
): JsonResponse {
if (!$form->isValid()) {
return $this->json($form->getErrors(true), 422);
}
return $this->json($form->getData());
}
Use dataArgument to submit the request into an existing object passed as another argument:
public function update(
Post $post,
#[MapRequestToForm(formType: PostType::class, dataArgument: 'post')]
FormInterface $form,
): JsonResponse {
// $post is updated with request data via the form.
}
Explicitly define supported formats (e.g., json, form, query) for clarity or restriction:
#[MapRequestToForm(acceptFormat: ['json'])] // Only accept JSON payloads
RequestToFormMapperUse the mapper service for complex scenarios (e.g., pre-processing data):
public function create(RequestToFormMapper $mapper): JsonResponse {
$post = new Post();
$post->setAuthor($this->getUser());
$mapper->handleCurrentRequest($post); // Submit request into $post
return $this->json($post);
}
Pass form options dynamically via the attribute:
#[MapRequestToForm(
formOptions: ['validation_groups' => ['Default', 'publish']]
)]
Form Type Inference Failures:
data_class, explicitly specify formType:
#[MapRequestToForm(PostType::class)] // Avoids ambiguity
debug:container | grep FormType.Null Data Handling:
null, ensure the controller argument is nullable:
#[MapRequestToForm]
?Post $post, // Note the nullable type
Request Format Mismatches:
json, form, and query formats. If your API uses xml, explicitly whitelist formats:
#[MapRequestToForm(acceptFormat: ['json'])] // Rejects non-JSON requests
Missing Fields in PATCH/GET:
clearMissing: false for PATCH/GET preserves existing data. Override carefully:
#[MapRequestToForm(clearMissing: true)] // Clears missing fields for all methods
Circular Dependencies:
#[Assert\Valid] sparingly.Exception Handling:
FormValidationFailedException on invalid forms. Catch it to customize responses:
try {
$mapper->handleCurrentRequest($post);
} catch (FormValidationFailedException $e) {
return $this->json($e->getForm()->getErrors(), 422);
}
Inspect the Form:
#[MapRequestToForm(..., throwOnInvalid: false)] with the mapper to debug invalid forms:
$form = $mapper->handle(..., throwOnInvalid: false);
dump($form->getErrors());
Check Request Data:
use Symfony\Component\HttpFoundation\Request;
public function create(Request $request): void {
dump($request->getContent()); // For JSON/form data
}
Form Type Registration:
debug:form
to list all registered forms.Custom Validation Failed Responses:
422 status or exception type by binding a compiler pass:
// config/services.yaml
AzYouness\RequestToFormBundle\:
resource: '../vendor/azyouness/request-to-form-bundle/src/'
exclude: '../vendor/azyouness/request-to-form-bundle/src/{DependencyInjection,Exception,Attribute}'
Add New Request Formats:
RequestToFormMapper to support custom formats (e.g., xml) by implementing a RequestDataExtractorInterface.Pre/Post-Submit Hooks:
// src/EventListener/FormSubmitListener.php
public function onKernelRequest(RequestEvent $event): void {
$request = $event->getRequest();
if ($request->attributes->get('_route') === 'post_create') {
$request->attributes->set('_form_options', ['custom_option' => true]);
}
}
public function testMapperSubmitsRequest(): void {
$mapper = $this->createMock(RequestToFormMapper::class);
$mapper->expects($this->once())
->method('handleCurrentRequest')
->with($this->isInstanceOf(Post::class));
$this->controller->create($mapper);
}
HttpClient to send malformed requests and verify 422 responses:
$response = $this->client->request('POST', '/posts', [
'json' => ['invalid' => 'data'],
]);
$this->assertEquals(422, $response->getStatusCode());
How can I help you explore Laravel packages today?