【问题标题】:Symfony 3 API REST - Try Catch Exceptions to JSON response formatSymfony 3 API REST - 尝试捕获 JSON 响应格式的异常
【发布时间】:2017-10-06 16:21:40
【问题描述】:

我使用 Symfony 和这些捆绑包 FosRestBundle、jms/serializer-bundle、lexik/jwt-authentication-bundle 创建 api rest 服务器。

我怎样才能像这样发送一个干净的 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."}

代替系统消息:

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'

返回一个干净的 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()));
}
}

谢谢

【问题讨论】:

  • 你确定这段代码被正确执行了吗?我刚刚在现有的 symfony 项目中尝试过,catch() 按预期工作
  • 代码可以工作,但我想要 json 格式的自定义消息错误。
  • 啊,对不起,误读了这个问题。所以你想返回遗漏字段的名称或重复条目? rafrsr 的答案是否(部分)解决了这个问题?
  • 只是部分,因为在我的控制器中我不使用表单。

标签: json symfony exception fosrestbundle symfony-3.2


【解决方案1】:

我们使用以下方法:

API 异常的通用类:

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

创建一个扩展 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;
    }
}

当表单出现错误时在控制器中使用异常

if ($form->getErrors(true)->count()) {
    throw new ValidationException($form);
}

创建和配置您的 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'

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

使用这种方法可以为每种类型的错误创建带有自定义消息和代码的自定义异常(有助于 API 文档),另一方面隐藏其他内部异常,仅在抛出异常时向 API 使用者显示“内部服务器错误”不是从 APIException 扩展而来的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-04
    • 2021-12-13
    • 2011-12-05
    • 2014-05-11
    • 1970-01-01
    相关资源
    最近更新 更多