Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ? valid, but the code we use can't be used in a real application.

PUT: Editing Resources

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 $12.00

PUT: Editing Resources

We can create a programmer resource, view a representation of a programmer, or view the collection representation for all programmers.

PUT: The Basic Definition

We’re killing it! Now let’s make it possible to edit a programmer resource.

Depending on who you ask, there are about 10 HTTP methods, and the 4 main ones are

  • GET
  • POST
  • PUT
  • DELETE

We know GET is for retrieving a representation and DELETE is pretty clear.

But things get trickier with POST and PUT. I’m about to say something that’s incorrect. Ready?

POST is used for creating resources and PUT is used for updating.

Seriously, this is not true, and it’s dangerous to say: there might be hardcore REST fans waiting around any corner that’s eager to tell us how wrong that is.

But in practice, this statement is pretty close: PUT is for edit, POST is for create. So let’s use the PUT method for our edit endpoint. Afterwards, we’ll geek out on the real difference between POST and PUT.

Writing the Test

You guys know the drill: we start by writing the test. So let’s add yet another scenario:

// features/api/programmer.feature
// ...

Scenario: PUT to update a programmer
  Given the following programmers exist:
    | nickname    | avatarNumber | tagLine |
    | CowboyCoder | 5            | foo     |
  And I have the payload:
    """
    {
      "nickname": "CowboyCoder",
      "avatarNumber" : 2,
      "tagLine": "foo"
    }
    """
  When I request "PUT /api/programmers/CowboyCoder"
  Then the response status code should be 200
  And the "avatarNumber" property should equal "2"

This looks a lot like our POST scenario, and that’s good: consistency! But we do need to add a line to put a programmer into the database and tweak a few other details. The status code is different: 201 is used when an asset is created but the normal 200 is used when it’s an update.

Just to keep us tied into the theory of things, I’ll describe this using REST-nerd language. Ready? Ok.

This tests that when we send a “representation” of a programmer resource via PUT, the server will use it to update that resource and return a representation.

We haven’t actually coded this yet, so when we run the test, it fails:

$ php vendor/bin/behat

The test reports that the status code isn’t 200, it’s 405. 405 means “method not allowed”, and our framework is doing this for us. It’s a way of saying “Hey, /api/programmers/CowboyCoder is a valid URI, but the resource doesn’t support the PUT method.”

If your API doesn’t support an HTTP method for a resource, you should return a 405 response. If you use a decent framework, you should get this functionality for free.

Coding up the PUT Endpoint

Let’s code it! Create another route, but use the put method to make it respond only to PUT requests:

// src/KnpU/CodeBattle/Controller/Api/ProgrammerController.php
// ...

protected function addRoutes(ControllerCollection $controllers)
{
    // ...

    $controllers->put('/api/programmers/{nickname}', array($this, 'updateAction'));
}

Next, copy the newAction and rename it to updateAction, because these will do almost the same thing. The biggest difference is that instead of creating a new Programmer object, we’ll query the database for an existing object and update it. Heck, we can steal that code from showAction:

// src/KnpU/CodeBattle/Controller/Api/ProgrammerController.php
// ...

public function updateAction($nickname, Request $request)
{
    $programmer = $this->getProgrammerRepository()->findOneByNickname($nickname);

    if (!$programmer) {
        $this->throw404();
    }

    $data = json_decode($request->getContent(), true);

    $programmer->nickname = $data['nickname'];
    $programmer->avatarNumber = $data['avatarNumber'];
    $programmer->tagLine = $data['tagLine'];
    $programmer->userId = $this->findUserByUsername('weaverryan')->id;

    $this->save($programmer);

    // ...
}

Change the status code from 201 to 200, since we’re no longer creating a resource. And you should also remove the Location header:

// src/KnpU/CodeBattle/Controller/Api/ProgrammerController.php
// ...

public function updateAction($nickname, Request $request)
{
    // ...

    $this->save($programmer);

    $data = $this->serializeProgrammer($programmer);

    $response = new JsonResponse($data, 200);

    return $response;
}

We only need this header with the 201 status code when a resource is created. And it makes sense: when we create a new resource, we don’t know what its new URI is. But when we’re editing an existing resource, we clearly already have that URI, because we’re using it to make the edit.

Run the Test and Celebrate

Time to run the test!

$ php vendor/bin/behat

Woot! It passes! And we can even run it over and over again.

Leave a comment!

7
Login or Register to join the conversation
MichalWilczynski Avatar
MichalWilczynski Avatar MichalWilczynski | posted 4 years ago

According to RFC 7231 when PUT fails, must send a 204 (No Content) response to indicate not successful completion of the
request, but in the video I see that we throwing 404. is it ok? Thanks for reply ;)

Reply

Hey Michal,

You probably understand it wrong, because all 2xx statuses mean successful operations, that's why when something went wrong - you either return 4xx or 5xx response, depends on the problem. 204 status means that your request was successfully handled but no content was returned to user back, i.e. it means that some operation on the server side was executed successfully.

I hope it helps.

Cheers!

1 Reply

Does `createAction` will be better to use instead of `newAction` in this case, doesn't it? To be consistent with `updateAction`.
What do you think, Ryan?

Reply

Yea, createAction is probably a little better, because we ultimately return a 201 "Created" status code. newAction is just a habit I have! :)

Reply

I could rename all entries of `newAction` to `createAction` in this repo with PR. Or it will be too excessive and better live it as is?

Reply

I think it's better as it is (to match the video). Allowing the code/script to update, while making it clear when the video doesn't match the updated code is something that's tough. For now, unless it's a bug, I usually leave it alone.

But anyways - you rock for even asking :)

Reply

Yes, you right! I forgot about usage this name in video :)

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses a deprecated micro-framework called Silex. The fundamentals of REST are still ? valid, but the code we use can't be used in a real application.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "silex/silex": "~1.0", // v1.3.2
        "symfony/twig-bridge": "~2.1", // v2.7.3
        "symfony/security": "~2.4", // v2.7.3
        "doctrine/dbal": "^2.5.4", // v2.5.4
        "monolog/monolog": "~1.7.0", // 1.7.0
        "symfony/validator": "~2.4", // v2.7.3
        "symfony/expression-language": "~2.4" // v2.7.3
    },
    "require-dev": {
        "behat/mink": "~1.5", // v1.5.0
        "behat/mink-goutte-driver": "~1.0.9", // v1.0.9
        "behat/mink-selenium2-driver": "~1.1.1", // v1.1.1
        "behat/behat": "~2.5", // v2.5.5
        "behat/mink-extension": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~5.7.0", // 5.7.27
        "guzzle/guzzle": "~3.7" // v3.9.3
    }
}
userVoice