API - POST to Create New Data
In this video you will see how we can quickly add new BlogPost
s 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 (BlogPost
s) 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.