Handle errors
Until now, the errors we had were in HTML format.
To create a consistent API, this format is not ideal. It would be more interesting to return JSON even when it is an error.
Creating the normalizer
To do this, we will use a Normalizer
. As mentioned in the documentation, a normalizer allows to transform any data into an associative list. In our case, we will create a normalizer to transform exceptions into a list that will be managed by Symfony.
Let's start by creating a new file called src/Normalizer/ErrorNormalizer.php
. In this file we will add the following content that we will explain next.
// src/Normalizer/ErrorNormalizer.php
namespace App\Normalizer;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class ErrorNormalizer implements NormalizerInterface
{
public function normalize($exception, string $format = null, array $context = []): array
{
return [
'message' => $context['debug'] ? $exception->getMessage() : 'An error occured',
'status' => $exception->getStatusCode(),
'trace' => $context['debug'] ? $exception->getTrace() : [],
];
}
public function supportsNormalization($data, string $format = null, array $context = []): bool
{
return $data instanceof FlattenException;
}
}
The normalize
method allows to transform our object into a list. As for the supportsNormalization
method, it allows to define the elements that will be normalized with the previous method.
For security reasons, the error message and the stacktrace
will be displayed only when the application is in debug
mode, in other words, in dev
mode. This way, when our api is in production, the error details will not be accessible.
Changing the response format
Also, in order to "activate" our normalizer, we will change the default format of the responses. Instead of using an HTML format, we will use the JSON format.
To do this, it's very simple, just add a new format
parameter to the Route
attribute of our controller.
// src/Controller/TodoController.php
#[Route("/api", "api_", format: "json")]
class TodoController extends AbstractController
{
// ...
}
That's it. Now, if we generate an error while trying to read the information of a todo that does not exist, our error will be serialized in JSON format using the format that we have defined.
Our normalizer is not restricted to the JSON format. Indeed, if we want to change the format to XML, the error format would also be updated to follow the format we have defined.
In summary
In this lesson we have seen one of the methods to change the error format. Indeed, we could have used an EventSubscriber
, however this method allows to create only one error format (only JSON or only XML for example). The advantage of the normalizer is that it is adaptive. As we mentioned, we can very simply change the format of the errors.