Buy

The ApiProblem object knows everything about how the response should look, including the status code and the response body information. So, it'd be great to have an easy way to convert this into a Response.

But, I want to go further. Sometimes, having a Response isn't enough. Like in processForm(): since nothing uses its return value. So the only way to break the flow is by throwing an exception.

Here's the goal: create a special exception class, pass it the ApiProblem object, and then have some central layer convert that into our beautiful API problem JSON formatted response. So whenever something goes wrong, we'll just need to create the ApiProblem object and then throw this special exception. That'll be it, in any situation.

Create the ApiProblemException

In the Api directory, create a new class called ApiProblemException. Make this extend HttpException - because I like that ability to set the status code on this:

10 lines src/AppBundle/Api/ApiProblemException.php
... lines 1 - 2
namespace AppBundle\Api;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiProblemException extends HttpException
{
}

Next, we need to be able to attach an ApiProblem object to this exception class, so that we have access to it later when we handle all of this. Let's pass this via the constructor. Use cmd+n - or go to the "Generate" menu at the top - and override the __construct method. Now, add ApiProblem $apiProblem as the first argument. Also create an $apiProblem property and set this there:

18 lines src/AppBundle/Api/ApiProblemException.php
... lines 1 - 6
class ApiProblemException extends HttpException
{
private $apiProblem;
public function __construct(ApiProblem $apiProblem, $statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0)
{
$this->apiProblem = $apiProblem;
parent::__construct($statusCode, $message, $previous, $headers, $code);
}
}

This won't do anything special yet: this is still just an HttpException that happens to have an ApiProblem attached to it.

Back in ProgrammerController, we can start using this. Throw a new ApiProblemException. Pass it $apiProblem as the first argument and 400 next:

193 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 142
private function processForm(Request $request, FormInterface $form)
{
$data = json_decode($request->getContent(), true);
if ($data === null) {
$apiProblem = new ApiProblem(400, ApiProblem::TYPE_INVALID_REQUEST_BODY_FORMAT);
throw new ApiProblemException(
$apiProblem,
400
);
}
... lines 154 - 156
}
... lines 158 - 193

Run the test:

./bin/phpunit -c app --filter testInvalidJson

It still acts like before: with a 400 status code, and now an exception with no message.

Simplifying the ApiProblemException Constructor

Before we handle this, we can make one minor improvement. Remove the $statusCode and $message arguments because we can get those from the ApiProblem itself. Replace that with $statusCode = $apiProblem->getStatusCode(). And I just realized I messed up my first line - make sure you have $this->apiProblem = $apiProblem. Also add $message = $apiProblem->getTitle():

20 lines src/AppBundle/Api/ApiProblemException.php
... lines 1 - 6
class ApiProblemException extends HttpException
{
... lines 9 - 10
public function __construct(ApiProblem $apiProblem, \Exception $previous = null, array $headers = array(), $code = 0)
{
$this->apiProblem = $apiProblem;
$statusCode = $apiProblem->getStatusCode();
$message = $apiProblem->getTitle();
parent::__construct($statusCode, $message, $previous, $headers, $code);
}
}

Hey wait! ApiProblem doesn't have a getTitle() method yet. Ok, let's go add one. Use the Generate menu again, select "Getters" and choose title:

65 lines src/AppBundle/Api/ApiProblem.php
... lines 1 - 7
class ApiProblem
{
... lines 10 - 59
public function getTitle()
{
return $this->title;
}
}

In ProgrammerController, simplify this:

190 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 142
private function processForm(Request $request, FormInterface $form)
{
$data = json_decode($request->getContent(), true);
if ($data === null) {
$apiProblem = new ApiProblem(400, ApiProblem::TYPE_INVALID_REQUEST_BODY_FORMAT);
throw new ApiProblemException($apiProblem);
}
... lines 151 - 153
}
... lines 155 - 190

It'll figure out the status code and message for us.

./bin/phpunit -c app --filter testInvalidJson

The exception class is perfect - we just need to add that central layer that'll convert this into the beautiful API Problem JSON response. Instead of this HTML stuff.

Leave a comment!