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

Centralize that Response!

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

Check out the response - it's got a Content-Type of text/html. I thought we fixed that! Well, that's no surprise - when we switched from JsonResponse to Response, we lost that header. But more importantly, this mistake is too easy to make: we're calling serialize() and then creating the Response by hand in every controller. That means we'd need to set this header everywhere. That sucks. Let's centralize this across our entire project.

First, move serialize() out of ProgrammerController and into a class called BaseController. This is something I created and all controllers extend this. Paste this at the bottom and make it protected:

... lines 1 - 15
abstract class BaseController extends Controller
{
... lines 18 - 122
protected function serialize($data, $format = 'json')
{
return $this->container->get('jms_serializer')
->serialize($data, $format);
}
}

And while we're here - make another function: protected function createApiResponse(). Give it two arguments: $data and $statusCode that defaults to 200:

... lines 1 - 15
abstract class BaseController extends Controller
{
... lines 18 - 113
protected function createApiResponse($data, $statusCode = 200)
{
... lines 116 - 120
}
... lines 122 - 127
}

Instead of creating the Response ourselves, we can just call this and it'll take care of the details. Inside, first serialize the $data - whatever that is. And then return a new Response() with that $json, that $statusCode and - most importantly - that Content-Type header of application/json so we don't forget to set that:

... lines 1 - 113
protected function createApiResponse($data, $statusCode = 200)
{
$json = $this->serialize($data);
return new Response($json, $statusCode, array(
'Content-Type' => 'application/json'
));
}
... lines 122 - 129

I love it! Let's use this everywhere! Search for new Response. Call $response = $this->createApiResponse() and pass the $programmer. Copy that line and make sure it's status code is 201. Remove the other stuff, but keep the line that sets the Location header:

... lines 1 - 15
class ProgrammerController extends BaseController
{
... lines 18 - 21
public function newAction(Request $request)
{
... lines 24 - 33
$response = $this->createApiResponse($programmer, 201);
... lines 35 - 38
$response->headers->set('Location', $programmerUrl);
return $response;
}
... lines 43 - 138
}

Ok, much easier. Find the rest of the new Response spots and update them. It's all pretty much the same - listAction() has a different variable name, but that's it. For deleteAction(), well, it's returning a null Response, so we can leave that one alone.

... lines 1 - 47
public function showAction($nickname)
{
... lines 50 - 60
$response = $this->createApiResponse($programmer, 200);
return $response;
}
... lines 65 - 69
public function listAction()
{
... lines 72 - 75
$response = $this->createApiResponse(['programmers' => $programmers], 200);
return $response;
}
... lines 80 - 84
public function updateAction($nickname, Request $request)
{
... lines 87 - 104
$response = $this->createApiResponse($programmer, 200);
return $response;
}
... lines 109 - 140

Let's re-run the tests!

phpunit -c app

They still fail, but the responses have the right Content-Type header.

Time to fix these failures, and see how we can control the serializer.

Leave a comment!

2
Login or Register to join the conversation
Shaun T. Avatar
Shaun T. Avatar Shaun T. | posted 5 years ago

Would it be best practice to have the serializer method in a service class rather than in the base controller?

Reply

Hey Shaun,

It would have more sense if you want to unit-test this serializer service or if you need to reuse it in another spot, like in a service, etc. But for now, this serialize() method contains a single line of code, so there's no code duplication. It probably makes sense to create an abstract controller and move that method to it if you need to use that serialize() method in a few controllers.

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