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. "