Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Finish POST with a Form

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

To create a programmer, our client needs to send up some data. And while you can send that data as JSON, XML, form-encoded or any insane format you dream up, you'll probably want your clients to send JSON... unless you work for the banking or insurance industry. They love XML.

For the JSON, we can design it to have any keys. But since our Programmer entity has the properties nickname, avatarNumber and tagLine, let's use those.

These don't have to be the same, but it makes life easier if you can manage it.

Back in testing.php, create a $nickname - but make it a little bit random: this has a unique index in the database and I don't want everything to blow up if I run the file twice. Make a $data array and put everything in it. The avatarNumber is which built-in avatar you want - it's a number from 1 to 6. And add a tagLine:

25 lines testing.php
... lines 1 - 11
$nickname = 'ObjectOrienter'.rand(0, 999);
$data = array(
'nickname' => $nickname,
'avatarNumber' => 5,
'tagLine' => 'a test dev!'
);
... lines 18 - 25

To send this data, add an options array to post. It has a key called body, and it's literally the raw string you want to send. So we need to json_encode($data):

25 lines testing.php
... lines 1 - 11
$nickname = 'ObjectOrienter'.rand(0, 999);
$data = array(
'nickname' => $nickname,
'avatarNumber' => 5,
'tagLine' => 'a test dev!'
);
$response = $client->post('/api/programmers', [
'body' => json_encode($data)
]);
... lines 22 - 25

Reading the Request Body

This looks good - so let's move to our controller. To read the data the client is sending, we'll need the Request object. So add that as an argument:

... lines 1 - 7
use Symfony\Component\HttpFoundation\Request;
... lines 9 - 12
/**
* @Route("/api/programmers")
* @Method("POST")
*/
public function newAction(Request $request)
{
... lines 19 - 21
}
... lines 23 - 24

To get the JSON string, say $body = $request->getContent(). And to prove things are working, just return the POST'd body right back in the response:

... lines 1 - 16
public function newAction(Request $request)
{
$data = $request->getContent();
return new Response($data);
}
... lines 23 - 24

The client is sending a JSON string and our response is just sending that right back. Try it!

php testing.php

Hey, that's prefect! We get a 200 status code response and its content is the JSON we sent it. Time to pack it up and call it a day. Just kidding.

Create the Programmer

Now that we've got the JSON, creating a Programmer is ridiculously simple. First, json_decode the $body into an array:

... lines 1 - 17
public function newAction(Request $request)
{
$data = json_decode($request->getContent(), true);
... lines 21 - 30
}
... lines 32 - 33

For now, we'll trust the JSON string has a valid structure. And the second argument to json_decode makes sure we get an array, not a stdClass object.

Now for the most obvious code you'll see $programmer = new Programmer(), and pass it $data['nickname'] and $data['avatarNumber'] - I gave this entity class a __construct() function with a few optional arguments. Now, $programmer->setTagLine($data['tagLine']):

... lines 1 - 17
public function newAction(Request $request)
{
$data = json_decode($request->getContent(), true);
$programmer = new Programmer($data['nickname'], $data['avatarNumber']);
$programmer->setTagLine($data['tagLine']);
... lines 24 - 30
}
... lines 32 - 33

The only tricky part is that the Programmer has a relationship to the User that created it, and this is a required relationship. On the web, I'm logged in, so that controller sets my User object on this when I create a Programmer. But our API doesn't have any authentication yet - it's all anonymous.

We'll add authentication later. Right now, we need a workaround. Update the controller to extend BaseController - that's something I created right in AppBundle/Controller that just has some handy shortcut methods. This will let me say $programmer->setUser($this->findUserByUsername('weaverryan')):

... lines 1 - 4
use AppBundle\Controller\BaseController;
... lines 6 - 11
class ProgrammerController extends BaseController
{
... lines 14 - 17
public function newAction(Request $request)
{
$data = json_decode($request->getContent(), true);
$programmer = new Programmer($data['nickname'], $data['avatarNumber']);
$programmer->setTagLine($data['tagLine']);
$programmer->setUser($this->findUserByUsername('weaverryan'));
... lines 25 - 30
}
}

So we're cheating big time... for now. At least while developing, that user exists because it's in our fixtures. I'm not proud of this, but I promise it'll get fixed later.

Finish things off by persisting and flushing the Programmer:

... lines 1 - 17
public function newAction(Request $request)
{
... lines 20 - 23
$programmer->setUser($this->findUserByUsername('weaverryan'));
$em = $this->getDoctrine()->getManager();
$em->persist($programmer);
$em->flush();
... lines 29 - 30
}
... lines 32 - 33

Enjoy this easy stuff... while it lasts. For the Response, what should we return? Ah, let's worry about that later - return a reassuring message, like It worked. Believe me, I'm an API!:

... lines 1 - 17
public function newAction(Request $request)
{
... lines 20 - 27
$em->flush();
return new Response('It worked. Believe me - I\'m an API');
}
... lines 32 - 33

The whole flow is there, so go back and hit the script again:

php testing.php

And... well, I think it looks like that probably worked. Now, let's add a form.

Leave a comment!

13
Login or Register to join the conversation
Default user avatar

Hello,

When I write in ProgrammerController.php
public function newAction(Request $request)
{
$body = $request->getContent();
return new Response($body);
}
ide highlights getContent();
and when I type testing.php in the cmd I get:
> php testing.php
HTTP/1.1 500 Internal Server Error
Host: localhost:8000
Connection: close
Cache-Control: no-cache
Date: Mon, 14 Aug 2017 03:29:33 GMT
X-Debug-Token: 1943b6
X-Debug-Token-Link: /_profiler/1943b6
Content-Type: text/html; charset=UTF-8

What could be the reason the error?
How can I to fix this problem?

Reply

Hey Nina

I believe you chosed the wrong type of Request, make sure you are using "Symfony\Component\HttpFoundation\Request"
if that's not the case, let me know :)

Cheers!

Reply

Hi,

Thanks for you tutorail it are always amazing.
I have a little question, I try to use Postman to make my call for "api/programmes" with in body form-data (nickname in key and Bob for value etc for the others data) I have nothing in $request->getContent()

Edited:
I found how to send data I need to use raw but why can I use form-data ?

If I use Guzzle I have no pb.
Do you know why?

Thanks again.

Reply

Hey Greg,

That's because Postman sends form-data/raw with a different headers. For the raw type "Content-Type" header equals to "text/plain" so the Symfony's Request object do not parse it itself, you need to call $request->getContent() to get request content. But for the form-data type "Content-Type" header of request is "multipart/form-data", so Request object parse content and you put data on the "$request" property for POST data and on the "$query" property for GET request. So if you send a POST request with "form-data" type via Postman - then you can get already parsed data in app with $request->request->get('your-sent-key-here').

P.S. use "dump($request->request->all())" to dump all sent POST data in your app.

Cheers!

Reply
Default user avatar

Hello guys and gals from KnpU!

My question is, since the Programmer resource has a relation attached to the user who created it. I suppose that a resource /programmers from a user 1 is different from a resource /programmers from a user 2, although they have the same URL. Would it be better to explicit this relation in the URI? e.g: app/users/{slug}/programmers.
What are the pros and cons when using this approach? I heard that it its good for using http caching.

Regards.

Reply

Hey Felipe!

Ah, GREAT question. Basically, yes! If you follow the rules of REST, then really, if you want to return a collection of all of the programmers from a specific user, you should have something like /users/{slug}/programmers, not /programmers. Caching is one reason. But more broadly, in in REST, each URL is an address to a specific resource... and I shouldn't be able to suddenly get back a different resource just by sending a different authentication token. As a by product of that rule, you can use HTTP caching more easily :). It's also a bit more explicit!

In the real world, it does seem like most API's follow this practice, but it's not an absolute rule. For example, if you use Facebook's API, I believe you use the URL /me to fetch your user profile, and /me/photos for my photos. On the bright side, "cheating" like this makes it even easier for your clients :).

Cheers!

Reply
Default user avatar

Cool! Thanks Ryan.

Reply
Default user avatar

For those of us who choose to purchase just this tutorial, I think providing the full picture (e.g. the Programmer model) would make this easier to replicate.

Reply

Hey Caro!

I totally want you to be able to replicate this :). On this page, you should see a "Download" link in the upper right corner, with a "Course Code" option in it. This will download a .tar file. Inside, there is a "start" directory (how the code looks at the beginning of this tutorial) and a "finish" directory (how the code looks at the end of the tutorial).

Does this help? Or is there still something missing?

Cheers!

Reply
Default user avatar

yes it did, thank you so much!

Reply
Default user avatar

Hi there,

Everything looks normal except when I compare my output to the expected output shown at 2:00 in the video I don't see the JSON output. The headers look exactly the same as the expected output:
----------------------------------------------------
HTTP/1.1 200 OK
Host: localhost:8000
Connection: close
X-Powered-By: PHP/5.6.27
Cache-Control: no-cache
Date: Wed, 11 Jan 2017 09:19:18 GMT
Content-Type: text/html; charset=UTF-8
X-Debug-Token: 7a9b8d
X-Debug-Token-Link: /_profiler/7a9b8d

-------------------------------------------------
The source for testing.php and ProgrammerController.php is an exact copy also. There is even an exact number of lines after the header, just no JSON line. Is there anything I should check, maybe one of the dependencies, or something I may have missed?

Reply
Dan Avatar

Actually no need to answer this one I worked it out, it was just a case of mistaken identity - got lost in my own folder structure.

Reply
Victor Avatar Victor | SFCASTS | Dan | posted 5 years ago | edited

Hey Dan ,

I'm glad you figured it out by yourself so quickly!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of REST are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}
userVoice