Keeping Problem types Consistent
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 SubscribeLook back at the title
field in the spec:
A short, human-readable summary of the problem type. It SHOULD NOT
change from occurrence to occurrence of the problem, except if you're
translating it.
In human terms, this means that every time we have a validation_error
type, the title should be exactly There was a validation error
. So when we're validating in other places in the future, both of the type
and the title
need to be exactly the same. Otherwise, our API clients are going to wonder what kind of inconsistent jerk programmed this API.
Because validation_error
is now a "special string" in the app, I think this is great spot for a constant. In ApiProblem
, add a constant called TYPE_VALIDATION_ERROR
and set it to the string:
// ... lines 1 - 7 | |
class ApiProblem | |
{ | |
const TYPE_VALIDATION_ERROR = 'validation_error'; | |
// ... lines 11 - 47 | |
} |
Ok, use that back in the controller: ApiProblem::TYPE_VALIDATION_ERROR
:
// ... lines 1 - 16 | |
class ProgrammerController extends BaseController | |
{ | |
// ... lines 19 - 166 | |
private function createValidationErrorResponse(FormInterface $form) | |
{ | |
// ... lines 169 - 170 | |
$apiProblem = new ApiProblem( | |
400, | |
ApiProblem::TYPE_VALIDATION_ERROR, | |
'There was a validation error' | |
); | |
// ... lines 176 - 181 | |
} | |
} |
Ok, that's better. Next, I also don't want to mess up the title. Heck, I don't even want to have to write the title anywhere - can't it be guessed based on the type?
I think so - let's just create a map from the type, to its associated title. In ApiProblem
, add a private static $titles
property. Let's make it an associative array map: from TYPE_VALIDATION_ERROR
to its message: There was a validation error
:
// ... lines 1 - 7 | |
class ApiProblem | |
{ | |
const TYPE_VALIDATION_ERROR = 'validation_error'; | |
private static $titles = array( | |
self::TYPE_VALIDATION_ERROR => 'There was a validation error' | |
); | |
// ... lines 15 - 56 | |
} |
In __construct()
, let's kill the $title
argument completely. Instead - just use self::$titles
and look it up with $type
:
// ... lines 1 - 7 | |
class ApiProblem | |
{ | |
// ... lines 10 - 11 | |
private static $titles = array( | |
self::TYPE_VALIDATION_ERROR => 'There was a validation error' | |
); | |
// ... lines 15 - 23 | |
public function __construct($statusCode, $type) | |
{ | |
// ... lines 26 - 32 | |
$this->title = self::$titles[$type]; | |
} | |
// ... lines 35 - 56 | |
} |
And we should code defensively, in case we mess something up later. Check if (!isset(self::$titles[$type]))
and throw a huge Exception
message to our future selves. How about, "Hey - buddy, use your head!". Or, more helpfully: "No title for type" and pass in the value.
// ... lines 1 - 23 | |
public function __construct($statusCode, $type) | |
{ | |
// ... lines 26 - 28 | |
if (!isset(self::$titles[$type])) { | |
throw new \InvalidArgumentException('No title for type '.$type); | |
} | |
$this->title = self::$titles[$type]; | |
} | |
// ... lines 35 - 58 |
Now we can pop off the last argument in the controller:
// ... lines 1 - 5 | |
class ProgrammerControllerTest extends ApiTestCase | |
{ | |
// ... lines 8 - 147 | |
public function testInvalidJson() | |
{ | |
// ... lines 150 - 162 | |
} | |
// ... line 164 | |
} |
Ok future me, good lucking screwing up our API problem responses. This is easy to use, and hard to break. Time to re-run the test:
./bin/phpunit -c app --filter testValidationErrors
Great! Let's keep hardening our API.