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

99 api/problem+json(s)

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

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Time for me to reveal why I chose this error response format with type, title and errors. Imagine if every JSON API returned the same format when things went wrong: always with these keys. That'd be pretty awesome. As API client, we'd always know what to expect and what things mean. That's a beautiful fairy tale.

In the real world, every API does whatever they want. But there are people out there working on standards for error responses, with the hope that someday, API's have some consistency.

The API Problem Details Format

One of those is called api problem details. Google for that. Ah, a boring spec document. Go ahead and read this whole thing, I'll wait... Kidding! I'll show you the good parts.

But first - click the draft ietf link. These drafts go through version, and this one has been replaced with a whole new document. And yea, these are just drafts - not official specs. But who cares? If a spec makes sense to you, why not follow it instead of making up your own format.

Scroll down to the example response. Two important things here. First, if you follow this spec, you should return a custom Content-type response header to advertise this: application/problem+json. When a client sees this - they can research it to find out what the fields in the response mean in human terms.

Second, check out the fields: the main ones are type and title. type is a unique string for what went wrong. It's supposed to be a URL - our's is just a key. We'll revisit that later. Next, this says title is a human-readable version of type and there are a bunch of other optional fields. The Extension Members section says that you can also add whatever other fields you want. We're adding an extra errors key.

Adding the application/problem+json Header

So we're already following this format - or at least we're pretty close. So I want to advertise this to our clients so they can dig into what each key means. Copy the application/problem+json Content-Type header so we can use it.

First, check for this in the test: $this->assertEquals() with application/problem+json as the expected value and $response->getHeader('Content-Type') for the actual value:

... lines 1 - 5
class ProgrammerControllerTest extends ApiTestCase
... lines 8 - 123
public function testValidationErrors()
... lines 126 - 144
$this->assertEquals('application/problem+json', $response->getHeader('Content-Type'));

Make sure it fails - run just this test:

bin/phpunit -c app --filter testValidationErrors

Yep, it fails. Now go to ProgrammerController and find createValidationErrorResponse. Instead of returning JsonResponse, set it to a $response variable and then call $response->headers->set() with Content-Type and application/problem+json. Return this:

... lines 1 - 15
class ProgrammerController extends BaseController
... lines 18 - 165
private function createValidationErrorResponse(FormInterface $form)
... lines 168 - 175
$response = new JsonResponse($data, 400);
$response->headers->set('Content-Type', 'application/problem+json');
return $response;

See if this fixed the test:

bin/phpunit -c app --filter testValidationErrors


Attempted to call function JsonResponse from namespace "AppBundle\Controller\Api"

That looks like a "Ryan" mistake - I deleted the new keyword before JsonResponse. You probably saw me do that. Put it back and now try the tests:

Beautiful green! And now we're advertising our special error format.

Leave a comment!

Login or Register to join the conversation
Default user avatar
Default user avatar julien moulis | posted 5 years ago

I have an error in my testValidationErrors. When I add the assert ```$this->assertEquals('application/problem+json', $response->getHeader('Content-Type'));```
My test fails with ```Array (...) does not match expected type "string".```
I'm not sure to understand why?
What do you think?
Thx Julien

Default user avatar

Ok I reply myself, I guess it's a Guzzle 6 update stuff... It seems that the getHeader('Content-Type') is an array. So I added [0] to fetch the first... It works. Sorry


Hey julien moulis

Don't worry ;) I'm glad that you could fix your problem.
Sometimes is handy to dump some variables inside your tests, so you can actually see what's going on.

Have a nice day

Cat in space

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

This tutorial uses an older version of Symfony. The concepts of REST and errors 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