Symfony 3 API REST - 尝试捕获JSON响应格式的异常

csu*_*csu 2 json exception symfony fosrestbundle symfony-3.2

我使用Symfony创建了api rest服务器,这些包括FosRestBundle,jms/serializer-bundle,lexik/jwt-authentication-bundle.

我该如何发送一个干净的json响应格式,如下所示:

Missing field "NotNullConstraintViolationException"
    {'status':'error','message':"Column 'name' cannot be null"}
or
    {'status':'error','message':"Column 'email' cannot be null"}
Or Duplicate entry "UniqueConstraintViolationException" :
    {'status':'error','message':"The email user1@gmail.com exists in database."}
Run Code Online (Sandbox Code Playgroud)

而不是系统消息:

UniqueConstraintViolationException in AbstractMySQLDriver.php line 66: An exception occurred while executing 'INSERT INTO user (email, name, role, password, is_active) VALUES (?, ?, ?, ?, ?)' with params ["user1@gmail.com", "etienne", "ROLE_USER", "$2y$13$tYW8AKQeDYYWvhmsQyfeme5VJqPsll\/7kck6EfI5v.wYmkaq1xynS", 1]: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'user1@gmail.com' for key 'UNIQ_8D93D649E7927C74'
Run Code Online (Sandbox Code Playgroud)

返回一个干净的json响应,其名称为必填字段或错过字段.

我的控制器:

    <?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\User;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\HttpFoundation\JsonResponse;

use FOS\RestBundle\Controller\Annotations as Rest; // alias pour toutes les annotations

class DefaultController extends Controller
{


 /**
 * @Rest\View()
 * @Rest\Post("/register")
 */
public function registerAction(Request $request)
{
    //catch all errors and convert them to exceptions
    ErrorHandler::register();

    $em = $this->get('doctrine')->getManager();
    $encoder = $this->container->get('security.password_encoder');

    $username = $request->request->get('email'); 
    $password = $request->request->get('password');
    $name = $request->request->get('name');

    $user = new User($username);

    $user->setPassword($encoder->encodePassword($user, $password));
    $user->setName($name);
    $user->setRole('ROLE_USER');
    try {
        $em->persist($user);
        $em->flush($user);
    }
    catch (NotNullConstraintViolationException $e) {
        // Found the name of missed field
            return new JsonResponse();
    } 
    catch (UniqueConstraintViolationException $e) {
        // Found the name of duplicate field
            return new JsonResponse();
    } 
    catch ( \Exception $e ) {

        //for debugging you can do like this
        $handler = new ExceptionHandler();
        $handler->handle( $e );
        return new JsonResponse(
            array(
                'status' => 'errorException', 
                'message' => $e->getMessage()
            )
        );
    }

    return new Response(sprintf('User %s successfully created', $user->getUsername()));
}
}
Run Code Online (Sandbox Code Playgroud)

谢谢

raf*_*rsr 8

我们使用以下方法:

API异常的通用类:

class ApiException extends \Exception
{  
    public function getErrorDetails()
    {
        return [
            'code' => $this->getCode() ?: 999,
            'message' => $this->getMessage()?:'API Exception',
        ];
    }
}
Run Code Online (Sandbox Code Playgroud)

创建扩展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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当表单有错误时,在控制器中使用异常

if ($form->getErrors(true)->count()) {
    throw new ValidationException($form);
}
Run Code Online (Sandbox Code Playgroud)

创建和配置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');
    }
}
Run Code Online (Sandbox Code Playgroud)

config.yml

fos_rest:
    #...
    exception:
        enabled: true
        exception_controller: 'SomeBundle\Controller\ExceptionController::showAction'
Run Code Online (Sandbox Code Playgroud)

请参阅:http://symfony.com/doc/current/bundles/FOSRestBundle/4-exception-controller-support.html

使用此方法可以创建自定义异常,其中包含针对每种类型的错误的自定义消息和代码(对API文档有用),另一方面隐藏其他内部异常,仅向API使用者显示"内部服务器错误",当抛出的异常未从APIException.