7

I'm having a hard time figuring out how to handle a JSON request with Symfony forms (using v3.0.1).

Here is my controller:

/**
 * @Route("/tablet")
 * @Method("POST")
 */
public function tabletAction(Request $request)
{
    $tablet = new Tablet();
    $form = $this->createForm(ApiTabletType::class, $tablet);
    $form->handleRequest($request);

    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($tablet);
        $em->flush();
    }

    return new Response('');
}

And my form:

class ApiTabletType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('macAddress')
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\Tablet'
        ]);
    }
}

When I send a POST request with the Content-Type header properly set to application/json, my form is invalid... all fields are null.

Here is the exception message I get if I comment the if ($form->isValid()) line :

An exception occurred while executing 'INSERT INTO tablet (mac_address, site_id) VALUES (?, ?)' with params [null, null]:

I've tried sending different JSON with the same result each time:

  • {"id":"9","macAddress":"5E:FF:56:A2:AF:15"}
  • {"api_tablet":{"id":"9","macAddress":"5E:FF:56:A2:AF:15"}}

"api_tablet" being what getBlockPrefix returns (Symfony 3 equivalent to form types getName method in Symfony 2).

Can anyone tell me what I've been doing wrong?


UPDATE:

I tried overriding getBlockPrefix in my form type. The form fields have no prefix anymore, but still no luck :/

public function getBlockPrefix()
{
    return '';
}
7
  • Render the form and take a look at the element names. You will quickly see that macAddress element is named something like 'form[macAddress]' I myself do not use forms for rest stuff. I think the naming issue is more trouble than it's worth. But other folks think otherwise. Might want to take a look at the FOSRestBundle. Commented Jan 5, 2016 at 18:52
  • I tried rendering the form, the form is named "api_tablet" and the element "api_tablet_macAddress". I tried using "api_tablet_macAddress" instead of "macAddress" (with or without the parent node, as mentionned in my question) but no success either. (I'd like to avoid using FOSRestBundle, since the API I need is really small. I'd rather not add a bundle for such a basic task.) Commented Jan 5, 2016 at 19:02
  • You are looking at the id. The element name is: name="api_tablet[macAddress]". Setting blockName to '' does make the api_table prefix go away though your form name is now blank as well which is not really desirable. By the way, javascript sometimes has trouble with [] so use the browser F12 key to verify what is actually being posted. Commented Jan 5, 2016 at 19:37
  • You're totally right, my bad. I removed the getBlockPrefix override and tried using brackets in my JSON, but no luck either. Commented Jan 5, 2016 at 19:46
  • 2
    The thing is that a JSON request (a request were the request body is a JSON string) is not populated the same way as a "regular" HTTP request (you can dump the Request object to see the difference). Thus, you need to first "decode" the request body. You can take a look at the BodyListener from FOSRestBundle for some inspiration (implementing a custom request handler would probably work too, but imho that's not easier). Commented Jan 5, 2016 at 20:47

3 Answers 3

19
$data = json_decode($request->getContent(), true);
$form->submit($data);

if ($form->isValid()) {
    // and so on…
}
Sign up to request clarification or add additional context in comments.

Comments

2

I guess you can drop forms and populate->validate entities

$jsonData = $request->getContent();
$serializer->deserialize($jsonData, Entity::class, 'json', [
    'object_to_populate' => $entity,
]);
$violations = $validator->validate($entity);

if (!$violations->count()) {

    return Response('Valid entity');
}

return new Response('Invalid entity');

https://symfony.com/doc/current/components/serializer.html#deserializing-in-an-existing-object

https://symfony.com/components/Validator

2 Comments

I'm using validator classes in a project as well. Way simpler than using an in-between form.
This will not work if for example form has extra non mapped fields such as agreeTerms.
0

I would recommend you use the FOSRestBundle.

Here's an example config I use at the moment:

fos_rest:
    view:
        view_response_listener: force
        force_redirects:
          html: true
        formats:
            jsonp: true
            json: true
            xml: false
            rss: false
        templating_formats:
            html: true
        jsonp_handler: ~
    body_listener: true
    param_fetcher_listener: force
    allowed_methods_listener: true
    access_denied_listener:
        json: true
    format_listener:
        enabled: true
        rules:
            - { path: ^/, priorities: [ 'html', 'json' ], fallback_format: html, prefer_extension: true }
    routing_loader:
            default_format: ~
            include_format: true
    exception:
        enabled: true
        codes:
            'Symfony\Component\Routing\Exception\ResourceNotFoundException': 404
            'Doctrine\ORM\OptimisticLockException': HTTP_CONFLICT
        messages:
            'Symfony\Component\Routing\Exception\ResourceNotFoundException': true

Make sure you have getBlockPrefix defined with something (I haven't tried empty strings so it might work):

public function getBlockPrefix()
{
   return 'api_tablet'
}

Disable CSRF protection for good measure:

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'csrf_protection' => false,
        'data_class' => 'AppBundle\Entity\Tablet'
    ]);
}

You can then POST the following data to the form:

{"api_tablet":{"macAddress":"5E:FF:56:A2:AF:15"}}

Notes / caveats:

  • You need to make sure your Form is configured with all the fields of the Entity. You can configure what you need in a validator.

  • You have to post in camelCase. FOSRestBundle supports Array Normalization, which I haven't tried, but apparently that will let you post in underscores.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.