Search code examples
phpjsonsymfonyfosrestbundle

FOSRestBundle EntityType not working in JSON


I am using Symfony 3.4 and FOSRestBundle for my APIs.
All the services are working fine except this one where I am posting an entity with a form and an EntityType field.

Controller:

public function createAssistanceCallAction(Request $request)
{
    $assistanceCall = new AssistanceCall();
    $form = $this->createForm(AssistanceCallType::class, $assistanceCall);

    $form->handleRequest($request);
    dump($form->isSubmitted(), $form->isValid());die;
}

Entity property:

/**
 * @var MobileAppUser
 *
 * @ORM\ManyToOne(targetEntity="MobileAppUser")
 * @ORM\JoinColumn(name="mobile_app_user_id", referencedColumnName="id", nullable=false)
 * @Assert\NotBlank
 */
protected $mobileAppUser;

Form:

/**
 * {@inheritdoc}
 */
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('mobile_app_user', EntityType::class, array(
            'class' => 'AppBundle:MobileAppUser',
        ))
        ->add('save', SubmitType::class)
    ;
}

It's working fine with a normal POST:

curl -X POST http://mysite.local/api/users/1/create-assistance-call -F 'assistance_call[mobile_app_user]=26'

dump($form->isSubmitted(), $form->isValid());die; // true and true

It's not working with JSON format:

curl -X POST \
  http://mysite.local/api/users/1/create-assistance-call \
  -d '{
    "assistance_call": {
        "mobile_app_user": {
            "id": 1
        }
    }
}'

dump($form->isSubmitted(), $form->isValid());die; // false and false

What am I doing wrong in the JSON example?


Solution

  • Since you are sending your json object as a request body and not as normal POST fields - you should json_decode request content first and then use $form->submit(...) to load your form with the data. The old good handleRequest() won't do here. Take a look at the following example:

    public function createAssistanceCallAction(Request $request)
    {
        $assistanceCall = new AssistanceCall();
        $form = $this->createForm(AssistanceCallType::class, $assistanceCall);
    
        //Notice the "true" argument passed to the json_decode
        $data = json_decode($request->getContent(), true);
        $form->submit($data);
    
        dump($form->isSubmitted(), $form->isValid());die;
    }
    

    You can also use FOSRestBundle BodyListener to decode json for you. To do that - add the following configuration entry:

    fos_rest:
        body_listener:
          decoders:
              json: fos_rest.decoder.jsontoform
    

    This still doesn't make $form->handleRequest() a good choice since it will only allow one method per form, so if you configure your form to do POST - the PUT requests will always fail without any explicit error messages.

    So then you would amend the code above like this:

    public function createAssistanceCallAction(Request $request)
    {
    
        $assistanceCall = new AssistanceCall();
        $form = $this->createForm(AssistanceCallType::class, $assistanceCall);
        $form->submit($request->request->all());
    
        dump($form->isSubmitted(), $form->isValid());die;
    }
    

    Pay attention to the Content-Type header you send as if it is set to 'multipart/form-data' while having json payload in its body - decoder will fail.