Symfony 3 API REST - Try Catch Exceptions to JSON response format Symfony 3 API REST - Try Catch Exceptions to JSON response format symfony symfony

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.