Buy

Keeping Problem types Consistent

Look 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:

49 lines src/AppBundle/Api/ApiProblem.php
... 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:

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,
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:

58 lines src/AppBundle/Api/ApiProblem.php
... 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:

58 lines src/AppBundle/Api/ApiProblem.php
... 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.

58 lines src/AppBundle/Api/ApiProblem.php
... 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:

166 lines src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php
... 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.

Leave a comment!