Buy

Modeling the Error: ApiProblem Class

Ok, we've got a format for errors - and we're going to use this whenever anything goes wrong - like a 404 error, authentication error, 500 error, whatever. And each time, this format needs to be perfectly consistent.

So instead of creating this $data array by hand when things go wrong, let's create a class that models all this stuff.

The ApiProblem Class

I actually started this for us. In PhpStorm, I'll switch my view back so I can see the resources/ directory at the root. Copy the ApiProblem.php file. In AppBundle, create a new Api directory and paste the file here:

47 lines src/AppBundle/Api/ApiProblem.php
... lines 1 - 2
namespace AppBundle\Api;
/**
* A wrapper for holding data to be used for a application/problem+json response
*/
class ApiProblem
{
private $statusCode;
private $type;
private $title;
private $extraData = array();
public function __construct($statusCode, $type, $title)
{
$this->statusCode = $statusCode;
$this->type = $type;
$this->title = $title;
}
... lines 24 - 45
}

The namespace is already AppBundle\Api - so that's perfect. This holds data for an application/problem+json response. It has properties for type, title and statusCode - these being the three main fields from the spec.

And it also has a spot for extra fields:

47 lines src/AppBundle/Api/ApiProblem.php
... lines 1 - 7
class ApiProblem
{
... lines 10 - 15
private $extraData = array();
... lines 17 - 36
public function set($name, $value)
{
$this->extraData[$name] = $value;
}
... lines 41 - 45
}

If you call set(), we can add any extra stuff, like the errors key for validation. And when we're all done, we'll call the toArray() method to get all this back as a flat, associative array:

47 lines src/AppBundle/Api/ApiProblem.php
... lines 1 - 7
class ApiProblem
{
... lines 10 - 24
public function toArray()
{
return array_merge(
$this->extraData,
array(
'status' => $this->statusCode,
'type' => $this->type,
'title' => $this->title,
)
);
}
... lines 36 - 45
}

Using ApiProblem

Let's use this back in ProgrammerController. Start with $apiProblem = new ApiProblem(). The status code is 400, the type is validation_error and the title is There was a validation error. Let's knock this onto multiple lines for readability:

184 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 16
class ProgrammerController extends BaseController
{
... lines 19 - 166
private function createValidationErrorResponse(FormInterface $form)
{
... lines 169 - 170
$apiProblem = new ApiProblem(
400,
'validation_error',
'There was a validation error'
);
... lines 176 - 181
}
}

Get rid of the $data variable. To add the extra errors field, call $apiProblem->set() and pass it the errors string and the $errors variable:

184 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 166
private function createValidationErrorResponse(FormInterface $form)
{
... lines 169 - 170
$apiProblem = new ApiProblem(
400,
'validation_error',
'There was a validation error'
);
$apiProblem->set('errors', $errors);
... lines 177 - 181
}
... lines 183 - 184

The last step is to update JsonResponse. Instead of $data, use $apiProblem->toArray(). And to avoid duplication, use $apiProblem->getStatusCode() instead of 400:

184 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 166
private function createValidationErrorResponse(FormInterface $form)
{
... lines 169 - 170
$apiProblem = new ApiProblem(
400,
'validation_error',
'There was a validation error'
);
$apiProblem->set('errors', $errors);
$response = new JsonResponse($apiProblem->toArray(), $apiProblem->getStatusCode());
... lines 179 - 180
return $response;
}
... lines 183 - 184

It's not perfect yet - but this is a lot more dependable. Nothing should have change - so try the tests:

./bin/phpunit -c app --filter testValidationErrors

And yep! We're still green.

But go back and make the test fail somehow - like change the assert for the header. I want to see the response for myself. Re-run things:

./bin/phpunit -c app --filter testValidationErrors

Scroll up to the dumped response. Yes - we've got the Content-Type header, the type and title keys, and a new status field that the spec recommends.

Fix that test. Ok, now we're ready for other stuff to go wrong.

Leave a comment!

  • 2016-10-08 Johan

    Loving it :)

  • 2015-09-21 weaverryan

    Ah, you're right! I've just fixed this - try downloading the code again. Thanks for letting me know!

  • 2015-09-18 A Maria

    Um, I don't see this class in the resources directory. :( Only ApiTestCase and ResponseAsserter