Search code examples
phpjsonrestsymfonyfosrestbundle

How to handle invalid forms for a REST POST?


I am developing a website that offers a REST service. All the GET actions are OK and rendered using a .json.twig template, but I am having a hard time understanding how to output form errors if the query made to create a new record is not valid. If I try to do a simple

return $form;

I get the following exception from SF:

"exception":[{"message":"Unable to find template \"SomeBundle:Customers:postCustomer.json.twig\"}]

The template does not exist, that's true, but I have no idea how to create one in JSON format to tell the requestor that his query is incomplete / malformed.

If I try anything else dealing with views but without specifying a template, the result is the same. Is there a way to do that automatically so that if the form is modified the change are reflected as well in the error ? Or a way to tell FOSRestBundle / JMSSerializerBundle to deal with the serialization themselves ? Before switching to Twig responses the error was nicely handled, and I'd like to have that back, along with the Twig templates for normal operations.

For information, my current controller's action is:

/**
 * @ApiDoc(
 *      resource=false,
 *      input="SomeBundle\Form\CustomerType",
 *      description="Create a new customer",
 *      section="Customers",
 *      statusCode={
 *          201="Action successful",
 *          403="Authorization required but incorrect / missing information or insufficient rights",
 *          500="Returned if action failed for unknown reasons"
 *      }
 *  )
 *
 * --View(template="SomeBundle:Customers:add.json.twig", templateVar="form", statusCode=400)
 * @View(templateVar="form", statusCode=400)
 * @param Request $request
 * @return \FOS\RestBundle\View\View
 */
    public function postCustomerAction(Request $request) {
        $data = json_decode($request->getContent(), true);
        $manager = $this->getManager();
        $customer = new Customer();
        $form = $this->getForm($customer);
        //$form->submit($data);
        //$manager->create($customer);

//        $form->handleRequest($request);
//        if ($form->isSubmitted() && $form->isValid()) {
//            $manager->create($customer);
//
//            return $this->redirectView($this->generateUrl('api_get_customer_internal', ['uuid' => $customer->getInternalUuid()], true),
//                                       201);
//        }

        return $form;
        //return $this->handleView($this->view($form, 400));
        //return \FOS\RestBundle\View\View::create($form, 400);
    }

And the FOSRestBundle configuration:

fos_rest:
  param_fetcher_listener: true
  body_listener: true
  format_listener:
    enabled: true
  view:
    view_response_listener: 'force'
    formats:
      json: true
    templating_formats:
        json: true
    force_redirects:
      html: true
    failed_validation: HTTP_BAD_REQUEST
    default_engine: twig
  routing_loader:
    include_format: false
    default_format: json
  serializer:
    serialize_null: true

sensio_framework_extra:
  view:
    annotations: true

Solution

  • Thanks to jorge07 at https://github.com/FriendsOfSymfony/FOSRestBundle/issues/1620 I was able to find a way to circumvent that in a rather proper way (at least IMHO), here's the updated Controller action (no change in the fosrestbundle settings required):

    /**
     * @Route("/customers")
     * @ApiDoc(
     *      resource=false,
     *      input="NetDev\CoreBundle\Form\CustomerType",
     *      description="Create a new customer",
     *      section="Customers",
     *      statusCode={
     *          201="Action successful",
     *          403="Authorization required but incorrect / missing information or insufficient rights",
     *          500="Returned if action failed for unknown reasons"
     *      }
     *  )
     *
     * @View(template="NetDevRestBundle:Common:form_error.json.twig", templateVar="errors", statusCode=400)
     *
     * @RequestParam(name="customerName", nullable=false)
     * @RequestParam(name="customerIndex", nullable=false)
     *
     * @return \FOS\RestBundle\View\View
     */
    public function postCustomerAction(ParamFetcher $fetcher)
    {
        $customer = new Customer();
        $form = $this->getForm($customer);
        $form->submit($fetcher->all(), true);
    
        if ($form->isValid()) {
            $manager = $this->getManager();
            $manager->create($customer);
    
            return $this->redirectView($this->generateUrl('api_get_customer_internal', ['uuid' => $customer->getInternalUuid()], true), 201);
        }
    
        $err = $form->getErrors();
        $errorsList = [];
        foreach ($err as $it) {
            $errorsList[(string)$it->getOrigin()->getPropertyPath()] = $it->getMessage();
        }
    
        return $this->view([$errorsList])
            ->setTemplateVar('errors')
            ;
    }