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

DELETE is for Saying Goodbye

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

So you have to part ways with your programmer, and we all know goodbyes are hard. So let's delete them instead. We're going to create an rm -rf endpoint to send a programmer to /dev/null.

Start with the test! public function testDELETEProgrammer, because we'll send a DELETE request to terminate that programmer resource:

... lines 1 - 5
class ProgrammerControllerTest extends ApiTestCase
{
... lines 8 - 93
public function testDELETEProgrammer()
{
... lines 96 - 102
}
}

Copy the guts of the GET test - fetching and deleting a programmer are almost the same, except for the HTTP method, change it to delete():

... lines 1 - 93
public function testDELETEProgrammer()
{
$this->createProgrammer(array(
'nickname' => 'UnitTester',
'avatarNumber' => 3,
));
$response = $this->client->delete('/api/programmers/UnitTester');
... line 102
}
... lines 104 - 105

Now, what's the status code? What should we return? We can't return the JSON programmer, because we just finished truncating it's proverbial tables. I mean, it's deleted - so it doesn't make sense to return the resource. Instead, we'll return nothing and use a status code - 204 - that means "everything went super great, but I have no content to send back." Remove the asserts on the bottom... since there's nothing to look at:

... lines 1 - 93
public function testDELETEProgrammer()
{
$this->createProgrammer(array(
'nickname' => 'UnitTester',
'avatarNumber' => 3,
));
$response = $this->client->delete('/api/programmers/UnitTester');
$this->assertEquals(204, $response->getStatusCode());
}
... lines 104 - 105

The Controller

Let's get straight to the controller: public function deleteAction(). Copy the route stuff from updateAction(). It's all the same again, except the method is different. Take out the route name - we don't need this unless we link here. And change the method to DELETE:

... lines 1 - 117
/**
* @Route("/api/programmers/{nickname}")
* @Method("DELETE")
*/
public function deleteAction($nickname)
{
... lines 124 - 135
}
... lines 137 - 154

Grab the query code from updateAction() too, and make sure you have your $nickname argument:

... lines 1 - 121
public function deleteAction($nickname)
{
$programmer = $this->getDoctrine()
->getRepository('AppBundle:Programmer')
->findOneByNickname($nickname);
if (!$programmer) {
throw $this->createNotFoundException(sprintf(
'No programmer found with nickname "%s"',
$nickname
));
}
// todo ...
}
... lines 137 - 154

So this will 404 if we don't find the programmer. Surprise! In the REST world, this is controversial! Since the job of this endpoint is to make sure the programmer resource is deleted, some people say that if the resource is already gone, then that's success! In other words, you should return the same 204 even if the programmer wasn't found. When you learn more about idempotency, this argument makes some sense. So let's do it! But really, either way is fine.

Change the if statement to be if ($programmer), then we'll delete it. Grab the EntityManager and call the normal remove() on it, then flush():

... lines 1 - 121
public function deleteAction($nickname)
{
... lines 124 - 127
if ($programmer) {
// debated point: should we 404 on an unknown nickname?
// or should we just return a nice 204 in all cases?
// we're doing the latter
$em = $this->getDoctrine()->getManager();
$em->remove($programmer);
$em->flush();
}
... lines 136 - 137
}
... lines 139 - 156

And whether the Programmer was found or not, we'll always return the same new Response(null, 204):

... lines 1 - 121
public function deleteAction($nickname)
{
$programmer = $this->getDoctrine()
->getRepository('AppBundle:Programmer')
->findOneByNickname($nickname);
if ($programmer) {
... lines 129 - 134
}
return new Response(null, 204);
}
... lines 139 - 156

Try the test and send a programmer to the trash! Filter for testDELETE.

phpunit -c app --filter testPUTProgrammer

Another endpoint bites the dust!

Leave a comment!

2
Login or Register to join the conversation

I think we also should to send GET request and try to get this programmer by `$nickname` in tests and ensure that it doesn't exist anymore. What if we do something wrong in `deleteAction` (for example wrong DB query in `findOneByNickname` method) and programmer don't really delete? It almost always returns us a `204`, but programmer could not be deleted, so we need to check it too, right?

1 Reply

Hey!

I'm mixed on this. Well, obviously, doing what you're saying is probably best - and I like the idea that you're actually using the API to verify the delete. So why *wouldn't* I test this? Because I don't want to over-test. The only likely way that this could return a 204 but NOT delete is if we actually just forgot the save logic (any error would cause a 500). And as long as we manually play around with our API once or twice, we'll notice that it doesn't actually save. Unlike other bugs (the ones that cause errors), there's basically no risk that we'd add a regression where we accidentally deleted the save code later.

I'll admit, this over-testing in functional tests is more possible when you're testing a web interface (e.g. don't submit a form 50 times to test all 50 different validation combinations), but that was the idea here.

Anyways - if you want to test it, I like your way :)

Cheers!

1 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