Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

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.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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:

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

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony. The concepts of REST and errors are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}
userVoice