【问题标题】:Laravel API, how to properly handle errorsLaravel API,如何正确处理错误
【发布时间】:2018-12-06 11:46:15
【问题描述】:

任何人都知道在 Laravel 中处理错误的最佳方法是什么,有什么规则或需要遵循的吗?

目前我正在这样做:

public function store(Request $request)
{
  $plate = Plate::create($request->all());

  if ($plate) {
    return $this->response($this->plateTransformer->transform($plate));
  } else {
    // Error handling ?
    // Error 400 bad request
    $this->setStatusCode(400);
    return $this->responseWithError("Store failed.");
  }
}

setStatusCode 和 responseWithError 来自我的控制器的父亲:

public function setStatusCode($statusCode)
{
    $this->statusCode = $statusCode;

    return $this;
}

public function responseWithError ($message )
{
    return $this->response([
        'error' => [
            'message' => $message,
            'status_code' => $this->getStatusCode()
        ]
    ]);

}

但是这是处理 API 错误的好方法吗?我在网上看到了一些不同的方法来处理错误,最好的方法是什么?

谢谢。

【问题讨论】:

标签: laravel api error-handling


【解决方案1】:

试试这个,我的项目里用过(app/Exceptions/Handler.php)

public function render($request, Exception $exception)
{
    if ($request->wantsJson()) {   //add Accept: application/json in request
        return $this->handleApiException($request, $exception);
    } else {
        $retval = parent::render($request, $exception);
    }

    return $retval;
}

现在处理 Api 异常

private function handleApiException($request, Exception $exception)
{
    $exception = $this->prepareException($exception);

    if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
        $exception = $exception->getResponse();
    }

    if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
        $exception = $this->unauthenticated($request, $exception);
    }

    if ($exception instanceof \Illuminate\Validation\ValidationException) {
        $exception = $this->convertValidationExceptionToResponse($exception, $request);
    }

    return $this->customApiResponse($exception);
}

在自定义 Api 处理程序响应之后

private function customApiResponse($exception)
{
    if (method_exists($exception, 'getStatusCode')) {
        $statusCode = $exception->getStatusCode();
    } else {
        $statusCode = 500;
    }

    $response = [];

    switch ($statusCode) {
        case 401:
            $response['message'] = 'Unauthorized';
            break;
        case 403:
            $response['message'] = 'Forbidden';
            break;
        case 404:
            $response['message'] = 'Not Found';
            break;
        case 405:
            $response['message'] = 'Method Not Allowed';
            break;
        case 422:
            $response['message'] = $exception->original['message'];
            $response['errors'] = $exception->original['errors'];
            break;
        default:
            $response['message'] = ($statusCode == 500) ? 'Whoops, looks like something went wrong' : $exception->getMessage();
            break;
    }

    if (config('app.debug')) {
        $response['trace'] = $exception->getTrace();
        $response['code'] = $exception->getCode();
    }

    $response['status'] = $statusCode;

    return response()->json($response, $statusCode);
}

始终在您的 api 或 json 请求中添加 Accept: application/json

【讨论】:

  • 感谢您的出色回答!并非所有消费者都添加Accept 标头,这就是为什么我检查$request->wantsJson() 而不是$request->expectsJson() || $request->isJson()
  • 没有理由实现 Laravel 已经默认处理的内容,请查看我的答案
  • 这就是我想要的
  • @rkj 我也在做同样的事情。你能提供如何处理语法错误或其他错误a
  • 你可以用$response['message'] = Symfony\Component\HttpFoundation\Response::$statusTexts[$statusCode]代替你的长开关。
【解决方案2】:

在我看来,我会保持简单。

返回带有 HTTP 错误代码和自定义消息的响应。

return response()->json(['error' => 'You need to add a card first'], 500);

或者如果你想抛出一个捕获的错误,你可以这样做:

   try {
     // some code
    } catch (Exception $e) {
        return response()->json(['error' => $e->getMessage()], 500);
    }

您甚至可以使用它来发送成功的响应:

return response()->json(['activeSubscription' => $this->getActiveSubscription()], 200);

这样,无论哪个服务使用您的 API,它都可以预期收到相​​同请求的相同响应。

您还可以通过传入 HTTP 状态代码来了解它的灵活性。

【讨论】:

    【解决方案3】:

    默认情况下,Laravel 已经能够管理 json 响应。

    如果不自定义 app\Handler.php 中的渲染方法,您可以简单地抛出 Symfony\Component\HttpKernel\Exception\HttpException,默认处理程序将识别请求头是否包含 Accept: application/json 并会相应地打印一条 json 错误消息。

    如果启用调试模式,它也会以 json 格式输出堆栈跟踪。

    这是一个简单的例子:

    <?php
    
    ...
    
    use Symfony\Component\HttpKernel\Exception\HttpException;
    
    class ApiController
    {
        public function myAction(Request $request)
        {
            try {
                // My code...
            } catch (\Exception $e) {
                throw new HttpException(500, $e->getMessage());
            }
    
            return $myObject;
        }
    }
    

    这是关闭调试的 laravel 响应

    {
        "message": "My custom error"
    }
    

    这是开启调试的响应

    {
        "message": "My custom error",
        "exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException",
        "file": "D:\\www\\myproject\\app\\Http\\Controllers\\ApiController.php",
        "line": 24,
        "trace": [
            {
                "file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
                "line": 48,
                "function": "myAction",
                "class": "App\\Http\\Controllers\\ApiController",
                "type": "->"
            },
            {
                "file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
                "line": 212,
                "function": "dispatch",
                "class": "Illuminate\\Routing\\ControllerDispatcher",
                "type": "->"
            },
    
            ...
        ]
    }
    

    使用 HttpException 调用将返回您选择的 http 状态代码(在本例中为内部服务器错误 500)

    【讨论】:

    • 不确定为什么这不是公认的答案。上面的“我的自定义错误”替换了 $e->getMessage()
    • 正是我想要的。谢谢
    • 这仅适用于您仅使用 API 的情况。但是我的应用程序也有 API 和正常响应。所以我需要两种不同的方法来处理它们——即使我调用的是相同的代码。所以我不会抛出 2 种不同类型的异常。还是您的代码也这样做了,我不明白?
    • 如果您的请求标头包含Accept: application/json,它将以 json 错误响应,如果您正在执行正常请求,它将以 html 错误页面响应,如果您将调试设置为显示异常详细信息.
    【解决方案4】:

    我认为修改 app/Exceptions/Handler.php 中实现的现有行为比重写它更好。

    您可以修改parent::render($request, $exception); 返回的 JSONResponse 并添加/删除数据。

    示例实现:
    app/Exceptions/Handler.php

    use Illuminate\Support\Arr;
    
    // ... existing code
    
    public function render($request, Exception $exception)
    {
        if ($request->is('api/*')) {
            $jsonResponse = parent::render($request, $exception);
            return $this->processApiException($jsonResponse);
        }
    
        return parent::render($request, $exception);
    }
    
    protected function processApiException($originalResponse)
    {
        if($originalResponse instanceof JsonResponse){
            $data = $originalResponse->getData(true);
            $data['status'] = $originalResponse->getStatusCode();
            $data['errors'] = [Arr::get($data, 'exception', 'Something went wrong!')];
            $data['message'] = Arr::get($data, 'message', '');
            $originalResponse->setData($data);
        }
    
        return $originalResponse;
    }
    

    【讨论】:

      【解决方案5】:

      使用@RKJ 最佳答案中的一些代码,我以这种方式处理了错误:

      打开 "Illuminate\Foundation\Exceptions\Handler" 类并搜索名为 "convertExceptionToArray" 的方法。此方法将 HTTP 异常转换为要显示为响应的数组。在这个方法中,我只是调整了一小段不会影响松耦合的代码。

      所以用这个替换convertExceptionToArray方法

      protected function convertExceptionToArray(Exception $e, $response=false)
          {
      
              return config('app.debug') ? [
                  'message' => $e->getMessage(),
                  'exception' => get_class($e),
                  'file' => $e->getFile(),
                  'line' => $e->getLine(),
                  'trace' => collect($e->getTrace())->map(function ($trace) {
                      return Arr::except($trace, ['args']);
                  })->all(),
              ] : [
                  'message' => $this->isHttpException($e) ? ($response ? $response['message']: $e->getMessage()) : 'Server Error',
              ];
          }
      

      现在导航到 App\Exceptions\Handler 类并将以下代码粘贴到 render 方法上方:

      public function convertExceptionToArray(Exception $e, $response=false){
      
              if(!config('app.debug')){
                  $statusCode=$e->getStatusCode();
                  switch ($statusCode) {
                  case 401:
                      $response['message'] = 'Unauthorized';
                      break;
                  case 403:
                      $response['message'] = 'Forbidden';
                      break;
                  case 404:
                      $response['message'] = 'Resource Not Found';
                      break;
                  case 405:
                      $response['message'] = 'Method Not Allowed';
                      break;
                  case 422:
                      $response['message'] = 'Request unable to be processed';
                      break;
                  default:
                      $response['message'] = ($statusCode == 500) ? 'Whoops, looks like something went wrong' : $e->getMessage();
                      break;
                  }
              }
      
              return parent::convertExceptionToArray($e,$response);
          }
      

      基本上,我们重写了 convertExceptionToArray 方法,准备了响应消息,并通过将响应作为参数传递来调用父方法。

      注意:此解决方案不适用于身份验证/验证错误,但大多数情况下,Laravel 可以通过适当的人类可读响应消息很好地管理这两个错误。

      【讨论】:

      • 你可以用$response['message'] = Symfony\Component\HttpFoundation\Response::$statusTexts[$statusCode]代替你的长开关。
      【解决方案6】:

      在您的 handler.php 中,这应该适用于处理 404 异常。

      public function render($request, Throwable $exception ){
          if ($exception instanceof ModelNotFoundException) {
              return response()->json([
                  'error' => 'Data not found'
              ], 404);
          }
          return parent::render($request, $exception);
      }
      

      【讨论】:

        【解决方案7】:

        对我来说,最好的方法是使用 API 响应的特定异常。

        如果您使用 Laravel 版本 > 5.5,您可以使用 create your own exceptionreport()render() 方法。使用命令: php artisan make:exception AjaxResponseException

        它将在以下位置创建 AjaxResponseException.php: app/Exceptions/
        之后用你的逻辑填充它。例如:

        /**
         * Report the exception.
         *
         * @return void
         */
        public function report()
        {
            \Debugbar::log($this->message);
        }
        
        /**
         * Render the exception into an HTTP response.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return JsonResponse|Response
         */
        public function render($request)
        {
            return response()->json(['error' => $this->message], $this->code);
        }
        

        现在,您可以在您的 ...Controller 中使用它和 try/catch 功能。
        例如以你的方式:

        public function store(Request $request)
        {
            try{
                $plate = Plate::create($request->all());
        
                if ($plate) {
                    return $this->response($this->plateTransformer->transform($plate));
                }
        
                throw new AjaxResponseException("Plate wasn't created!", 404);
        
            }catch (AjaxResponseException $e) {
                throw new AjaxResponseException($e->getMessage(), $e->getCode());
            }
        }
        

        这足以让您的代码更易于阅读、美观且有用。
        最好的问候!

        【讨论】:

          【解决方案8】:

          好吧,现在所有答案都还可以,但他们也在使用旧方法。 在 Laravel 8 之后,您可以通过将异常类引入为 renderable 来简单地更改 register() 方法中的响应:

          <?php
          
          
          namespace Your\Namespace;
          
          
          use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
          
          
          class Handler extends ExceptionHandler
          {
              /**
               * Register the exception handling callbacks for the application.
               *
               * @return void
               */
              public function register()
              {
                  $this->renderable(function (NotFoundHttpException $e, $request) {
                      if ($request->is('api/*')) {
                          return response()->json([
                              'message' => 'Record not found.'
                          ], 404);
                      }
                  });
              }
          }
          

          【讨论】:

            猜你喜欢
            • 2018-07-02
            • 1970-01-01
            • 2012-12-28
            • 2010-10-11
            • 1970-01-01
            • 1970-01-01
            • 2020-05-13
            • 2011-12-04
            • 2017-12-05
            相关资源
            最近更新 更多