API - POST to Create New Data


In this video you will see how we can quickly add new BlogPosts via the postAction. This action will expect us to use the POST HTTP verb to send in data, just like we would do with a regular form submission on a regular old website.

The key, as ever with these things, is not to over think it. Imagine you had a webpage with a Symfony form on it. We'd type in our data into the fields provided and then click submit. The receiving action would do some things (such as asking Doctrine to save / persist the record to the database), and then maybe redirect us somewhere on success.

Well, the API postAction is going to largely do just this. Except, without rendering anything out for us - that's not the API's concern.

At this stage it's a little catch-22 though: if we don't get any form fields rendered then how do we know what form fields we can submit?

Well, fortunately, we created the API so we know which fields are available. However, other developers would require that we document our API properly.

It may be worth a very quick recap of the form though:

<?php

// /src/AppBundle/Form/Type/BlogPostType.php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class BlogPostType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class)
            ->add('body', TextType::class)
        ;
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\BlogPost',
        ]);
    }
    public function getName()
    {
        return 'blog_post';
    }
}

The key part is the fields we have added to the form - namely the title and body fields.

This tells us that we need to send (or POST) in some JSON with these two form fields:

{
    "title": "some interesting title here",
    "body": "your blog post body content here"
}

And that's about it.

I'd advise using the Postman client for doing this stage, and be sure to set your 'body' content to 'raw' and 'JSON (application/json)', as shown in the video.

However, if you are still unsure about this particular form then take a look at this video where we originally set up the form, or this series which explains how to use Symfony forms in more detail.

Reviewing The postAction

Let's review the contents of the postAction.

Because we are implementing ClassResourceInterface, by simply naming our action as postAction, this is enough to signify that this action will only allow a POST request.

    public function postAction(Request $request)
    {
        $form = $this->createForm(BlogPostType::class, null, [
            'csrf_protection' => false,
        ]);

        $form->submit($request->request->all());

        if (!$form->isValid()) {
            return $form;
        }
        /**
         * @var $blogPost BlogPost
         */
        $blogPost = $form->getData();

        $em = $this->getDoctrine()->getManager();
        $em->persist($blogPost);
        $em->flush();

        $routeOptions = [
            'id' => $blogPost->getId(),
            '_format' => $request->get('_format'),
        ];

        return $this->routeRedirectView('get_post', $routeOptions, Response::HTTP_CREATED);
    }

That's the action in full, but let's break it down line by line.

    public function postAction(Request $request)
    {
        $form = $this->createForm(BlogPostType::class, null, [
            'csrf_protection' => false,        
        ]);

We start off by creating the form from the BlogPostType. We need to pass in an array of options to the createForm method (the third parameter), but to be able to do that we need to pass in 'something' for the second parameter. Roll on named parameters...

We can pass in null as the second param as our form type has a data_class option set, which will tell the form that it needs to new up a BlogPost entity.

We are turning off csrf_protection because we wouldn't have a CSRF token as we haven't any way of receiving one. Normally, our form would have rendered once already, and by way of the {{ form_end(form) }} twig tag, we would have had the hidden _token value set for us.

In this case though, you would want to ensure that a user has logged in already, or have some other authentication scheme in place to mitigate turning off CSRF protection.

        $form->submit($request->request->all());

In a HTML / Twig-style controller action, we would instead call $form->handleRequest($request), but here we submit the form manually. Therefore we can bypass the handleRequest method as we know the form is going to have been submitted.

        if (!$form->isValid()) {
            return $form;
        }

We don't have any entity validation configured, but if we did and our submitted form data was invalid, then by simply returning the $form object, a listener inside FOSRESTBundle will 'hear' about it and transform our invalid form into a JSON representation which we can easily pass back to the user. It's not the nicest looking thing, but it does the job.

        /**
         * @var $blogPost BlogPost
         */
        $blogPost = $form->getData();

        $em = $this->getDoctrine()->getManager();
        $em->persist($blogPost);
        $em->flush();

If any of this is new to you then I'd strongly recommend you watch this course where this is explained in much more detail.

Essentially - take the form data, which we know is a valid entity at this stage - and save it to the database.

        $routeOptions = [
            'id' => $blogPost->getId(),
            '_format' => $request->get('_format'),
        ];

        return $this->routeRedirectView('get_post', $routeOptions, Response::HTTP_CREATED);

Finally, once we have saved the new BlogPost to the database, we can return a 201 code to the user (which is what the Response::HTTP_CREATED constant refers too), and also in the response headers, send a link to this newly created resource.

PHP makes writing this out inline look pretty messy in my opinion... hey, did I already mention named parameters in this write up? Yes, it's my number one gripe with PHP. But still, we could just as correctly write this out as:

        return $this->routeRedirectView('get_post', [
            'id' => $blogPost->getId(),
            '_format' => $request->get('_format'),
        ], Response::HTTP_CREATED);

But it looks messy. Infact, it looks just like some of PHP's array_* functions, which are actually pretty darn useful, just ugly as heck to write. If you're unsure what I mean here, check out this video write up, specifically the array_reduce method about half way down the page.

And so that is it for POST. We can now create new BlogPost's via our REST API, which is very cool and wasn't that difficult to implement at all.

In the next video we will look at PUT, which allows us to us to replace existing resources (BlogPosts) by sending in updated data. We will also see just how easy it is to implement PATCH once we have our PUT action in place.

Code For This Video

Get the code for this video.

Episodes