Symfony 3 API REST - Try Catch Exceptions to JSON response format
We use the following approach:
Generic class for API exceptions:
class ApiException extends \Exception{ public function getErrorDetails() { return [ 'code' => $this->getCode() ?: 999, 'message' => $this->getMessage()?:'API Exception', ]; }}
Create a validation exception extending ApiException
class ValidationException extends ApiException{ private $form; public function __construct(FormInterface $form) { $this->form = $form; } public function getErrorDetails() { return [ 'code' => 1, 'message' => 'Validation Error', 'validation_errors' => $this->getFormErrors($this->form), ]; } private function getFormErrors(FormInterface $form) { $errors = []; foreach ($form->getErrors() as $error) { $errors[] = $error->getMessage(); } foreach ($form->all() as $childForm) { if ($childForm instanceof FormInterface) { if ($childErrors = $this->getFormErrors($childForm)) { $errors[$childForm->getName()] = $childErrors; } } } return $errors; }}
Use the exception in your controller when the form has errors
if ($form->getErrors(true)->count()) { throw new ValidationException($form);}
Create and configure your ExceptionController
class ExceptionController extends FOSRestController{ public function showAction($exception) { $originException = $exception; if (!$exception instanceof ApiException && !$exception instanceof HttpException) { $exception = new HttpException($this->getStatusCode($exception), $this->getStatusText($exception)); } if ($exception instanceof HttpException) { $exception = new ApiException($this->getStatusText($exception), $this->getStatusCode($exception)); } $error = $exception->getErrorDetails(); if ($this->isDebugMode()) { $error['exception'] = FlattenException::create($originException); } $code = $this->getStatusCode($originException); return $this->view(['error' => $error], $code, ['X-Status-Code' => $code]); } protected function getStatusCode(\Exception $exception) { // If matched if ($statusCode = $this->get('fos_rest.exception.codes_map')->resolveException($exception)) { return $statusCode; } // Otherwise, default if ($exception instanceof HttpExceptionInterface) { return $exception->getStatusCode(); } return 500; } protected function getStatusText(\Exception $exception, $default = 'Internal Server Error') { $code = $this->getStatusCode($exception); return array_key_exists($code, Response::$statusTexts) ? Response::$statusTexts[$code] : $default; } public function isDebugMode() { return $this->getParameter('kernel.debug'); }}
config.yml
fos_rest: #... exception: enabled: true exception_controller: 'SomeBundle\Controller\ExceptionController::showAction'
see: http://symfony.com/doc/current/bundles/FOSRestBundle/4-exception-controller-support.html
With this approach can create custom exceptions with custom messages and codes for each type of error (helpful for API documentation) in the other hand hide other internal exceptions showing to the API consumer only "Internal Server Error" when the exception thrown does not extended from APIException.