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 To Form Bundle Laravel Package

azyouness/request-to-form-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Installation:

    composer require azyouness/request-to-form-bundle
    

    Ensure the bundle is registered in config/bundles.php if Flex didn't auto-register it.

  2. 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);
    }
    
    • The bundle infers the form type from the data_class of registered forms.
    • If the form type isn't inferred (e.g., multiple forms use the same data_class), explicitly specify it:
      #[MapRequestToForm(PostType::class)]
      
  3. 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).

Implementation Patterns

1. Automatic Form Submission in Controllers

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.
}
  • Pattern: Combine with other attributes (e.g., #[MapEntity]) for hybrid resolution.

2. Handling Form Objects Directly

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());
}

3. Dynamic Form Data

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.
}

4. Request Format Flexibility

Explicitly define supported formats (e.g., json, form, query) for clarity or restriction:

#[MapRequestToForm(acceptFormat: ['json'])] // Only accept JSON payloads

5. Manual Mapping with RequestToFormMapper

Use 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);
}

6. Validation Groups and Options

Pass form options dynamically via the attribute:

#[MapRequestToForm(
    formOptions: ['validation_groups' => ['Default', 'publish']]
)]

Gotchas and Tips

Pitfalls

  1. Form Type Inference Failures:

    • If multiple forms use the same data_class, explicitly specify formType:
      #[MapRequestToForm(PostType::class)] // Avoids ambiguity
      
    • Debug Tip: Check registered form types with debug:container | grep FormType.
  2. Null Data Handling:

    • If the form can return null, ensure the controller argument is nullable:
      #[MapRequestToForm]
      ?Post $post, // Note the nullable type
      
  3. Request Format Mismatches:

    • The bundle defaults to json, form, and query formats. If your API uses xml, explicitly whitelist formats:
      #[MapRequestToForm(acceptFormat: ['json'])] // Rejects non-JSON requests
      
  4. Missing Fields in PATCH/GET:

    • By default, clearMissing: false for PATCH/GET preserves existing data. Override carefully:
      #[MapRequestToForm(clearMissing: true)] // Clears missing fields for all methods
      
  5. Circular Dependencies:

    • Avoid circular references in form data (e.g., bidirectional associations). Use #[Assert\Valid] sparingly.
  6. Exception Handling:

    • The bundle throws FormValidationFailedException on invalid forms. Catch it to customize responses:
      try {
          $mapper->handleCurrentRequest($post);
      } catch (FormValidationFailedException $e) {
          return $this->json($e->getForm()->getErrors(), 422);
      }
      

Debugging Tips

  1. Inspect the Form:

    • Use #[MapRequestToForm(..., throwOnInvalid: false)] with the mapper to debug invalid forms:
      $form = $mapper->handle(..., throwOnInvalid: false);
      dump($form->getErrors());
      
  2. Check Request Data:

    • Log the raw request payload to verify data before form submission:
      use Symfony\Component\HttpFoundation\Request;
      public function create(Request $request): void {
          dump($request->getContent()); // For JSON/form data
      }
      
  3. Form Type Registration:

    • Ensure your form type is registered as a service or tagged correctly. Use:
      debug:form
      
      to list all registered forms.

Extension Points

  1. Custom Validation Failed Responses:

    • Override the default 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}'
      
  2. Add New Request Formats:

    • Extend the RequestToFormMapper to support custom formats (e.g., xml) by implementing a RequestDataExtractorInterface.
  3. Pre/Post-Submit Hooks:

    • Use event subscribers to modify form data or options before submission:
      // 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]);
          }
      }
      

Performance Considerations

  • Avoid Over-Fetching: Ensure your form types only bind necessary fields to reduce validation overhead.
  • Caching: For read-heavy APIs, cache form metadata (e.g., validation groups) if using the same form across requests.

Testing

  • Unit Test the Mapper:
    public function testMapperSubmitsRequest(): void {
        $mapper = $this->createMock(RequestToFormMapper::class);
        $mapper->expects($this->once())
               ->method('handleCurrentRequest')
               ->with($this->isInstanceOf(Post::class));
        $this->controller->create($mapper);
    }
    
  • Integration Test Validation: Use HttpClient to send malformed requests and verify 422 responses:
    $response = $this->client->request('POST', '/posts', [
        'json' => ['invalid' => 'data'],
    ]);
    $this->assertEquals(422, $response->getStatusCode());
    
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums