10/02/2017 - SYMFONY
In this example we are going to create custom exceptions and an event listener to return standard response to end user. There will be just two examples but you can extend it by adding more exceptions. No need to change any other code at all.
The reason why I use messages
as an array but not message
as single string is because some validations might generate more than one reason. You'll see what I mean in tests below.
{
"error": {
"code": {An inreger error code goes here which matches header status code},
"messages": [{Zero or more error messages}]
}
}
The way you can use custom exceptions are listed below.
namespace Inanzzz\ApplicationBundle\Controller;
use Inanzzz\ApplicationBundle\Exception\BadRequestException;
use Inanzzz\ApplicationBundle\Exception\NotFoundException;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route("", service="inanzzz_application.controller.default")
*/
class DefaultController
{
/**
* @Method({"GET"})
* @Route("/not-found-exception")
*
* @return Response
*
* @throws NotFoundException
*/
public function notFoundExceptionAction()
{
throw new NotFoundException();
throw new NotFoundException('Order you are looking for cannot be found.');
}
/**
* @Method({"GET"})
* @Route("/bad-request-exception")
*
* @return Response
*
* @throws BadRequestException
*/
public function badRequestExceptionAction()
{
throw new BadRequestException();
throw new BadRequestException([]);
throw new BadRequestException(['Name field is required.']);
throw new BadRequestException(['Name field is required.', 'Postcode field is required.']);
}
}
services:
inanzzz_application.controller.default:
class: Inanzzz\ApplicationBundle\Controller\DefaultController
namespace Inanzzz\ApplicationBundle\Exception;
interface ApiExceptionInterface
{
}
Accepts only string type error message or nothing at all. It throws 404 Not Found
exception.
namespace Inanzzz\ApplicationBundle\Exception;
use Exception;
use Symfony\Component\HttpFoundation\Response;
class NotFoundException extends Exception implements ApiExceptionInterface
{
public function __construct($message = null)
{
parent::__construct($message, Response::HTTP_NOT_FOUND);
}
}
Accepts only array type error message or nothing at all. It throws 400 Bad Request
exception.
namespace Inanzzz\ApplicationBundle\Exception;
use Exception;
use Symfony\Component\HttpFoundation\Response;
class BadRequestException extends Exception implements ApiExceptionInterface
{
public function __construct(
array $message = [],
$code = Response::HTTP_BAD_REQUEST,
Exception $previous = null
) {
parent::__construct(json_encode($message), $code, $previous);
}
}
namespace Inanzzz\ApplicationBundle\EventListener;
use Inanzzz\ApplicationBundle\Exception\ApiExceptionInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
class ApiExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
if (!$event->getException() instanceof ApiExceptionInterface) {
return;
}
$response = new JsonResponse($this->buildResponseData($event->getException()));
$response->setStatusCode($event->getException()->getCode());
$event->setResponse($response);
}
private function buildResponseData(ApiExceptionInterface $exception)
{
$messages = json_decode($exception->getMessage());
if (!is_array($messages)) {
$messages = $exception->getMessage() ? [$exception->getMessage()] : [];
}
return [
'error' => [
'code' => $exception->getCode(),
'messages' => $messages
]];
}
}
services:
inanzzz_application.listener.api_exception:
class: Inanzzz\ApplicationBundle\EventListener\ApiExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
Calling GET /not-found-exception
endpoint.
# 1
{
"error": {
"code": 404,
"messages": []
}
}
# 2
{
"error": {
"code": 404,
"messages": [
"Order you are looking for cannot be found."
]
}
}
Calling GET /bad-request-exception
endpoint.
# 1
{
"error": {
"code": 400,
"messages": []
}
}
# 2
{
"error": {
"code": 400,
"messages": []
}
}
# 3
{
"error": {
"code": 400,
"messages": [
"Name field is required."
]
}
}
# 4
{
"error": {
"code": 400,
"messages": [
"Name field is required.",
"Postcode field is required."
]
}
}