Modeling the Error: ApiProblem Class
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOk, 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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.
While I try to run the server this error shows: "Cannot autowire service "App\Controller\Api\ApiProblem": argument "$statusCode" of method "__construct()" has no type-hint, you should configure its value explicitly. "