Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly do a REST API POST call using FOSRest and Symfony 3.0

For an API I'm currently building I'd like to be able to send a request with a JSON body with the following content

{"title": "foo"}

to create a new database record for an Entity called Project.

I made a controller which subclasses FOSRestController. To create a project, I made an action

/**
 * @Route("/")
 *
 * @ApiDoc(
 *     section="Project",
 *     resource=true,
 *     input={"class"="AppBundle\Form\API\ProjectType"},
 *     description="Creates a new project",
 *     statusCodes={
 *         201="Returned when successful",
 *      }
 * )
 *
 * @Method("POST")
 * @Rest\View(statusCode=201)
 */
public function createProjectAction(Request $request)
{
    $project = new Project();
    $form = $this->createForm(ProjectType::class, $project);
    $form->submit(($request->request->get($form->getName())));

    if ($form->isSubmitted() && $form->isValid()) {
        return $project;
    }

    return View::create($form, 400);
}

The ProjectType looks like this

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

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

However, when I try to post said JSON to the API, it responds that the title property cannot be blank, which is good because that's the validation rule set for it. However, it IS set. I suddenly realized I have to send the JSON prefixed by the actual object's name to make this work:

{"project":{"title": "bla"}}

Which feels a little strange to be fair, it should be enough to just post the properties.

So, based on this information I simply have 2 questions:

  1. Why do I need to "submit" this form with ($request->request->get($form->getName())), shouldn't $request be enough?
  2. What do I need to change for the FormType to validate the entity as is, instead of prefixing it with the entity's name?

Edit 1: adding or removing the data_class in the Default Options does not change the behaviour at all.

like image 280
bmeulmeester Avatar asked Jan 06 '16 22:01

bmeulmeester


1 Answers

This is because of how Symfony Controller "createForm" helper method works. Reasoning behind it is that multiple forms could have same target URL. By prefixing with form name, Symfony can know which form was submitted.

This can be seen by looking at "createForm" method implementation:

public function createForm($type, $data = null, array $options = array())
{
    return $this->container->get('form.factory')->create($type, $data, $options);
}

If you don't want this behavior, it's really easy to change it:

public function createProjectAction(Request $request)
{
    $project = new Project();
    $form = $this->get('form.factory')->createNamed(null, new ProjectType(), $project);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        return $project;
    }

    return View::create($form, 400);
}

So you're basically creating a "nameless" form. Since you're building an API, it's probably a good idea to pull this into a createNamelessForm($type, $data, $options) helper method in your base controller so that you don't have to get Form Factory from container explicitly all the time and make it easier on the eyes.

Comment on your edit

Wrapper key is not generated by "data_class" option, but by "getName()" method on your form type.

like image 56
Igor Pantović Avatar answered Oct 17 '22 20:10

Igor Pantović