GET'ting a Collection of data from your Symfony 4 API [FOSRESTBundle]


In the previous video we implemented the getAction. As such, we can now GET a single resource - one Album entity by ID. Now, let's implement the ability to GET multiple Album resources in one API call.

The route we will need here is another GET request, but this time to /album, rather than /album/{id}.

Once again, FOSRESTBundle gives us a convention to follow here. We need to implement the cgetAction - where the c stands for Collection.

As ever, let's start off with the simple method stub:

    public function cgetAction()
    {
    }

And then check the router:

bin/console debug:router                               
 ------------- -------- -------- ------ ------------- 
  Name          Method   Scheme   Host   Path         
 ------------- -------- -------- ------ ------------- 
  healthcheck   ANY      ANY      ANY    /ping        
  cget_album    GET      ANY      ANY    /album       
  get_album     GET      ANY      ANY    /album/{id}  
  post_album    POST     ANY      ANY    /album       
 ------------- -------- -------- ------ ------------- 

Cool, things are starting to come together.

As we injected the AlbumRepository when implementing the getAction controller method, we already have the dependency we need.

We also have the failing test:

vendor/bin/behat features/album.feature --tags=t

Feature: Provide insight into how Symfony 4 behaves on the unhappy path

  In order to eliminate bad Album data
  As a JSON API developer
  I need to ensure Album data meets expected criteria

  Background:                                          # features/album.feature:7
    Given there are Albums with the following details: # FeatureContext::thereAreAlbumsWithTheFollowingDetails()
      | title                              | track_count | release_date              |
      | some fake album name               | 12          | 2020-01-08T00:00:00+00:00 |
      | another great album                | 9           | 2019-01-07T23:22:21+00:00 |
      | now that's what I call Album vol 2 | 23          | 2018-02-06T11:10:09+00:00 |

  @t
  Scenario: Can get a collection of Albums  # features/album.feature:15
    Given I request "/album" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 200           # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
      Expected response code 200, got 500. (Imbo\BehatApiExtension\Exception\AssertionFailedException)
    And the response body contains JSON:    # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyContainsJson()
      """
      [
        {
          "id": 1,
          "title": "some fake album name",
          "track_count": 12,
          "release_date": "2020-01-08T00:00:00+00:00"
        },
        {
          "id": 2,
          "title": "another great album",
          "track_count": 9,
          "release_date": "2019-01-07T23:22:21+00:00"
        },
        {
          "id": 3,
          "title": "now that's what I call Album vol 2",
          "track_count": 23,
          "release_date": "2018-02-06T11:10:09+00:00"
        }
      ]
      """

--- Failed scenarios:

    features/album.feature:15

1 scenario (1 failed)
4 steps (2 passed, 1 failed, 1 skipped)
0m0.12s (9.84Mb)

Ok so we get a 500 error.

Why?

Because our cgetAction is created, so the route exists. It's not a 404. But we have no method body, and therefore don't return a Response:

[2018-03-09 15:56:56] request.INFO: Matched route "cget_album". {"route":"cget_album","route_parameters":{"_controller":"App\\Controller\\AlbumController:cgetAction","_format":null,"_route":"cget_album"},"request_uri":"http://api.oursite.com:8000/album","method":"GET"} []
[2018-03-09 15:56:56] request.CRITICAL: Uncaught PHP Exception LogicException: "The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?" at /tmp/symfony-4-fos-rest-api/vendor/symfony/http-kernel/HttpKernel.php line 165 {"exception":"[object] (LogicException(code: 0): The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller? at /tmp/symfony-4-fos-rest-api/vendor/symfony/http-kernel/HttpKernel.php:165)"} []

Again, we already know that the AlbumRepository gives us access to all the standard Doctrine helpers:

  • findOneBy()
  • findBy()
  • findAll()

And so on.

findAll is going to find all Album entities we currently have in our database.

As your data size grows, a paginator is essential. The KnpPaginatorBundle is a good shout. Right now we have only a handful of Albums in our collection, so returning the whole lot is "good enough". Of course, update this implementation as appropriate to your own use case.

    public function cgetAction()
    {
        return $this->view(
            $this->albumRepository->findAll()
        );
    }

Very straightforward. FOSRESTBundle is doing a lot for us here behind the scenes, but we've already done all the config needed to make this a breeze:

vendor/bin/behat features/album.feature --tags=t

Feature: Provide insight into how Symfony 4 behaves on the unhappy path

  In order to eliminate bad Album data
  As a JSON API developer
  I need to ensure Album data meets expected criteria

  Background:                                          # features/album.feature:7
    Given there are Albums with the following details: # FeatureContext::thereAreAlbumsWithTheFollowingDetails()
      | title                              | track_count | release_date              |
      | some fake album name               | 12          | 2020-01-08T00:00:00+00:00 |
      | another great album                | 9           | 2019-01-07T23:22:21+00:00 |
      | now that's what I call Album vol 2 | 23          | 2018-02-06T11:10:09+00:00 |

  @symfony_4_edge_case @t
  Scenario: Can get a collection of Albums  # features/album.feature:15
    Given I request "/album" using HTTP GET # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 200           # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
    And the response body contains JSON:    # Imbo\BehatApiExtension\Context\ApiContext::assertResponseBodyContainsJson()
      """
      [
        {
          "id": 1,
          "title": "some fake album name",
          "track_count": 12,
          "release_date": "2020-01-08T00:00:00+00:00"
        },
        {
          "id": 2,
          "title": "another great album",
          "track_count": 9,
          "release_date": "2019-01-07T23:22:21+00:00"
        },
        {
          "id": 3,
          "title": "now that's what I call Album vol 2",
          "track_count": 23,
          "release_date": "2018-02-06T11:10:09+00:00"
        }
      ]
      """

1 scenario (1 passed)
4 steps (4 passed)
0m0.14s (9.72Mb)

Job done.

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