DELETE'ing Albums [FOSRESTBundle]


At this point we can create new Albums, we can get one, or a collection of existing Albums, and we can update an Album either partially or fully.

Now, we're going to implement the process of deleting individual Albums through our Symfony 4 FOSRESTBundle API.

As with every other HTTP verb, the setup of DELETE is mostly taken care of for us, so long as we follow the naming convention: deleteAction.

Before we begin, we have our failing test:

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

Feature: Provide a consistent standard JSON API endpoint

  In order to build interchangeable front ends
  As a JSON API developer
  I need to allow Create, Read, Update, and Delete functionality

  Background:                                                   # features/album_common.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 |
    And the "Content-Type" request header is "application/json" # Imbo\BehatApiExtension\Context\ApiContext::setRequestHeader()

  @t
  Scenario: Can delete an Album                 # features/album_common.feature:43
    Given I request "/album/3" using HTTP GET   # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 200               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
    When I request "/album/3" using HTTP DELETE # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 204               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
      Expected response code 204, got 405. (Imbo\BehatApiExtension\Exception\AssertionFailedException)
    Then sleep                                  # FeatureContext::sleep()
    When I request "/album/3" using HTTP GET    # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 404               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()

--- Failed scenarios:

    features/album_common.feature:43

1 scenario (1 failed)
9 steps (5 passed, 1 failed, 3 skipped)
0m0.13s (9.83Mb)

Right now we're getting a 405 - the method is not implemented.

Implementing DELETE

We start, as usual, by implementing the method stub:

    public function deleteAction(string $id)
    {

    }

And checking the router:

bin/console debug:router
 -------------------------- -------- -------- ------ ----------------------------------- 
  Name                       Method   Scheme   Host   Path                               
 -------------------------- -------- -------- ------ ----------------------------------- 
  cget_album                 GET      ANY      ANY    /album                             
  get_album                  GET      ANY      ANY    /album/{id}                        
  post_album                 POST     ANY      ANY    /album                             
  put_album                  PUT      ANY      ANY    /album/{id}                        
  patch_album                PATCH    ANY      ANY    /album/{id}                        
  delete_album               DELETE   ANY      ANY    /album/{id}                        
 -------------------------- -------- -------- ------ ----------------------------------- 

The process for deleting is quite straightforward.

We start by getting the requested Album entity by ID.

As we have covered already, if the requested ID is not available, we will throw, which results in a 404.

Once we have the Album entity, we want to call Doctrine's remove method, followed by immediately flushing this change - which deletes the record from the database.

Finally we return a 204 / No Content response.

Here's the code that does exactly this:

    public function deleteAction(string $id)
    {
        $album = $this->findAlbumById($id);

        $this->entityManager->remove($album);
        $this->entityManager->flush();

        return $this->view(null, Response::HTTP_NO_CONTENT);
    }

Really quite straightforward. Honestly this is almost identical to what you'd do with a HTML delete process - maybe throw in a redirect for good measure, but still, very close indeed.

And that gets us to a passing test:

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

Feature: Provide a consistent standard JSON API endpoint

  In order to build interchangeable front ends
  As a JSON API developer
  I need to allow Create, Read, Update, and Delete functionality

  Background:                                                   # features/album_common.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 |
    And the "Content-Type" request header is "application/json" # Imbo\BehatApiExtension\Context\ApiContext::setRequestHeader()

  @t
  Scenario: Can delete an Album                 # features/album_common.feature:43
    Given I request "/album/3" using HTTP GET   # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 200               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
    When I request "/album/3" using HTTP DELETE # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 204               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()
    Then sleep                                  # FeatureContext::sleep()
    When I request "/album/3" using HTTP GET    # Imbo\BehatApiExtension\Context\ApiContext::requestPath()
    Then the response code is 404               # Imbo\BehatApiExtension\Context\ApiContext::assertResponseCodeIs()

1 scenario (1 passed)
9 steps (9 passed)
0m1.19s (9.80Mb)

That wraps up the happy path.

There's one final step I'd like to take: making the error output match our basic Symfony 4 JSON API. Let's look at that in the next video.

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