gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Tip
In this course we're using Symfony 2, but starting in episode 4, we use Symfony 3. If you'd like to see the finished code for this tutorial in Symfony 3, download the code from episode 4 and check out the start directory!
Errors! A lot of things can go wrong - like 500 errors when your database server is on fire, 404 errors, validation errors, authentication errors and errors in judgement, like wearing a khaki shirt with khaki shorts, unless you're on Safari.
In your API, handling all of these errors correctly ends up taking some effort. So that's why we're devoting an entire episode on getting a beautiful, robust error system into your Symfony API.
First, we'll talk about what most people think of when you mention errors: validation
errors! In episode 1, we created this cool ProgrammerControllerTest
class where
we can test all of our endpoints for creating a programmer, updating a programmer
deleting a programmer, etc etc. We don't have validation on any of these endpoints
yet.
So let's add some: when we POST to create, we really need to make sure that the
username isn't blank. That would be crazy! Copy testPOST()
. Down at the bottom,
paste that, and rename it to testValidationErrors()
. Get rid of the nickname
data field and most of the asserts:
... lines 1 - 5 | |
class ProgrammerControllerTest extends ApiTestCase | |
{ | |
... lines 8 - 123 | |
public function testValidationErrors() | |
{ | |
$data = array( | |
'avatarNumber' => 2, | |
'tagLine' => 'I\'m from a test!' | |
); | |
// 1) Create a programmer resource | |
$response = $this->client->post('/api/programmers', [ | |
'body' => json_encode($data) | |
]); | |
... lines 135 - 144 | |
} | |
} |
Ok, design time! Writing the test is our time to think about how each endpoint
should work. Since we're not sending a username
, what status code should the endpoint
return? Use 400:
... lines 1 - 123 | |
public function testValidationErrors() | |
{ | |
... lines 126 - 131 | |
$response = $this->client->post('/api/programmers', [ | |
'body' => json_encode($data) | |
]); | |
$this->assertEquals(400, $response->getStatusCode()); | |
... lines 137 - 144 | |
} | |
... lines 146 - 147 |
There are a few other status codes that you could use and we talk about them in our original REST series. But 400 is a solid choice.
Next, what should the JSON content of our response hold? Let me suggest a format. Trust me for now - I'll explain why soon. We need to tell the client what went wrong - a validation error - and what the validation errors are.
Use $this->asserter()->assertResponsePropertiesExist()
to assert that the response
will have 3 properties. The first is type
- a string error code - the second is
title
- a human description of what went wrong - and the third is errors
- an
array of all of the validation errors:
... lines 1 - 123 | |
public function testValidationErrors() | |
{ | |
... lines 126 - 131 | |
$response = $this->client->post('/api/programmers', [ | |
'body' => json_encode($data) | |
]); | |
... lines 135 - 136 | |
$this->asserter()->assertResponsePropertiesExist($response, array( | |
'type', | |
'title', | |
'errors', | |
)); | |
... lines 142 - 144 | |
} | |
... lines 146 - 147 |
Don't forget to pass the $response
as the first argument. Now, think about that
errors
array property. I'm thinking it'll be an associative array where the keys
are the fields that have errors, and the values are those errors. Use the
asserter again to say assertResponsePropertyExists()
to assert that errors.nickname
exists:
... lines 1 - 123 | |
public function testValidationErrors() | |
{ | |
... lines 126 - 141 | |
$this->asserter()->assertResponsePropertyExists($response, 'errors.nickname'); | |
... lines 143 - 144 | |
} | |
... lines 146 - 147 |
Basically, we want to assert that there is some error on the nickname field, because
it should be required. Actually, go one step further and assert the exact validation
message. Use assertResponsePropertyEquals
with $response
as the first argument,
errors.nickname[0]
as the second and, for the third, a nice message, how about
"Please enter a clever nickname":
... lines 1 - 123 | |
public function testValidationErrors() | |
{ | |
... lines 126 - 141 | |
$this->asserter()->assertResponsePropertyExists($response, 'errors.nickname'); | |
$this->asserter()->assertResponsePropertyEquals($response, 'errors.nickname[0]', 'Please enter a clever nickname'); | |
... line 144 | |
} | |
... lines 146 - 147 |
Why the [0]
part? It won't be too common, but one field could have multiple errors,
like a username that contains invalid characters and is too short. So each field
will have an array of errors, and we're checking that the first is set to our clever
message.
And since we are sending a valid avatarNumber
, let's make sure that there is
no error for it. Use assertResponsePropertyDoesNotExist()
and pass it errors.avatarNumber
:
... lines 1 - 123 | |
public function testValidationErrors() | |
{ | |
... lines 126 - 141 | |
$this->asserter()->assertResponsePropertyExists($response, 'errors.nickname'); | |
$this->asserter()->assertResponsePropertyEquals($response, 'errors.nickname[0]', 'Please enter a clever nickname'); | |
$this->asserter()->assertResponsePropertyDoesNotExist($response, 'errors.avatarNumber'); | |
} | |
... lines 146 - 147 |
Love it! We've just planned how we want our validation errors to work. That'll make coding this a lot easier.
// 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
}
}