Going far, then Too Far with the ViewResponseListener [FOSRESTBundle]


This next step is optional, but I like how it cuts down my controller method code even further. We're going to enable and configure the ViewResponseListener. This will allow us to simply return a View instance from our controller methods:

// current implementation

    public function postAction(
        Request $request
    ) {
        // ... etc

        return $this->handleView(
            $this->view(
                [
                    'status' => 'ok',
                ],
            );
        );
    }

Can simplify to:

// current implementation

    public function postAction(
        Request $request
    ) {
        // ... etc

        return $this->view(
            [
                'status' => 'ok',
            ],
        );
    }

In order for this to work, we need composer require sensio/framework-extra-bundle.

Please note that in the Symfony 4 API with FOSRESTBundle the SensioFrameworkExtraBundle was listed as an optional dependency. It is mandatory if you want this feature.

Let's enable the view_response_listener:

# config/packages/fos_rest.yaml

fos_rest:
    routing_loader:
        include_format: true
    format_listener:
        rules:
            - { path: ^/, prefer_extension: true, fallback_format: json, priorities: [ xml, json ] }
    view:
        view_response_listener:
            enabled: true

And that's it. We can now just return either calls to $this->view(...), or new View(...) objects directly.

You can take this one step further. It is possible to simply return data:

# config/packages/fos_rest.yaml

fos_rest:
    routing_loader:
        include_format: true
    format_listener:
        rules:
            - { path: ^/, prefer_extension: true, fallback_format: json, priorities: [ xml, json ] }
    view:
        view_response_listener:
            enabled: true
+           force: true

Which allows you to e.g:

// current implementation

    public function postAction(
        Request $request
    ) {
        // ... etc

        return [
            'status' => 'ok',
        ];
    }

Or more interestingly:

// current implementation

    public function postAction(
        Request $request
    ) {
        // ... etc

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

I used to do this a lot.

The reason I now prefer the $this->view(...) approach is that the view method call takes optional second and third arguments:

    /**
     * Creates a view.
     *
     * Convenience method to allow for a fluent interface.
     *
     * @param mixed $data
     * @param int   $statusCode
     * @param array $headers
     *
     * @return View
     */
    protected function view($data = null, $statusCode = null, array $headers = [])
    {
        return View::create($data, $statusCode, $headers);
    }

Simply put, it makes it easier to set the status code for a given request.

If we returned a $form when our form submission failed, and a new JsonResponse if everything went to plan, then we're returning two different types. For me, this feels weird, and is not a practice that has served me well over the years. Returning a single type - a View - standardises this, and leads to code that's easier to reason about.

Now admittedly on this last issue is moot point, as nothing should be directly consuming our controller methods. We return JSON. Our front end consumers work with that JSON. They would never directly call the postAction. But generally, as far as programming practices go, this approach of returning a single type is something I prefer, if at all possible.

Episodes

# Title Duration
1 What will our JSON API actually do? 08:46
2 What needs to be in our Database for our Tests to work? 12:32
3 Cleaning up after each Test Run 02:40
4 Docker makes for Easy Databases 09:01
5 Healthcheck [Raw Symfony 4] 07:53
6 Send in JSON data using POST [Raw Symfony 4] 05:33
7 Keep your data nice and tidy using Symfony's Form [Raw Symfony 4] 10:48
8 Validating incoming JSON [Raw Symfony 4] 08:26
9 Nicer error messages [Raw Symfony 4] 06:23
10 GET'ting data from our Symfony 4 API [Raw Symfony 4] 08:11
11 GET'ting a collection of Albums [Raw Symfony 4] 01:50
12 Update existing Albums with PUT [Raw Symfony 4] 05:00
13 Upsetting Purists with PATCH [Raw Symfony 4] 02:39
14 Hitting DELETE [Raw Symfony 4] 02:11
15 How to open your API to the outside world with CORS [Raw Symfony 4] 07:48
16 Getting Setup with Symfony 4 and FOSRESTBundle [FOSRESTBundle] 09:11
17 Healthcheck [FOSRESTBundle] 06:14
18 Handling POST requests [FOSRESTBundle] 08:31
19 Saving POST data to the database [FOSRESTBundle] 09:44
20 Work with XML, or JSON, or Both [FOSRESTBundle] 04:31
21 Going far, then Too Far with the ViewResponseListener [FOSRESTBundle] 03:19
22 GET'ting data from your Symfony 4 API [FOSRESTBundle] 05:58
23 GET'ting a Collection of data from your Symfony 4 API [FOSRESTBundle] 01:27
24 Updating with PUT [FOSRESTBundle] 02:58
25 Partially Updating with PATCH [FOSRESTBundle] 02:15
26 DELETE'ing Albums [FOSRESTBundle] 01:27
27 Handling Errors [FOSRESTBundle] 08:58
28 Introducing the API Platform [API Platform] 08:19
29 The Entry Point [API Platform] 04:30
30 The Context [API Platform] 05:52
31 Healthcheck - Custom Endpoint [API Platform] 05:17
32 Starting with POST [API Platform] 07:08
33 Creating Entities with the Schema Generator [API Platform] 07:38
34 Defining A Custom POST Route [API Platform] 07:31
35 Finishing POST [API Platform] 06:29
36 GET'ting One Resource [API Platform] 02:50
37 GET'ting Multiple Resources [API Platform] 02:59
38 PUT to Update Existing Data [API Platform] 02:19
39 DELETE to Remove Data [API Platform] 01:15
40 No One Likes Errors [API Platform] 03:28