Laravel API, как правильно обрабатывать ошибки

Кто-нибудь знает, как лучше всего обрабатывать ошибки в 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, я вижу другой способ обработки ошибок в Интернете, что лучше?

Спасибо.

Можете сослаться на аналогичный вопрос stackoverflow.com/questions/42958875/…

Leo Rams 27.06.2018 16:23
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Поиск нового уровня в Laravel с помощью MeiliSearch и Scout
Поиск нового уровня в Laravel с помощью MeiliSearch и Scout
Laravel Scout - это популярный пакет, который предоставляет простой и удобный способ добавить полнотекстовый поиск в ваше приложение Laravel. Он...
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
23
1
46 364
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Попробуйте, я использовал это в своем проекте (приложение / Исключения / 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);
}

Всегда добавляйте Accept: application/json в свой запрос api или json.

Спасибо за отличный ответ! Не все потребители добавляют заголовок Accept, поэтому вместо $request->wantsJson() я проверяю $request->expectsJson() || $request->isJson().

Stalinko 17.09.2019 12:34

Нет причин реализовывать то, что Laravel уже обрабатывает по умолчанию, проверьте мой ответ

Andrea Mauro 25.09.2019 10:47

Это то что я хочу

hassanrazadev 08.01.2021 16:45

@rkj Я делаю то же самое. Можете ли вы рассказать, как справиться с синтаксической ошибкой или другой ошибкой?

Yasin Patel 08.04.2021 13:31

Вы можете использовать $response['message'] = Symfony\Component\HttpFoundation\Response::$statusTexts[$sta‌​tusCode] вместо длинного переключателя.

shaedrich 12.04.2021 17:55

На мой взгляд, я бы не усложнил задачу.

Вернуть ответ с кодом ошибки 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.

Laravel уже по умолчанию может управлять ответами json.

Без настройки метода рендеринга в app \ Handler.php вы можете просто выбросить Symfony \ Component \ HttpKernel \ Exception \ HttpException, обработчик по умолчанию распознает, содержит ли заголовок запроса Принять: приложение / 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 ()

TomH 13.01.2020 08:55

Именно то, что я искал. Спасибо

Christopher Kikoti 01.05.2020 21:27

Это работает, только если вы используете только API. Однако у моего приложения также есть API и нормальные ответы. Поэтому мне нужно 2 разных способа их обработки - даже если я вызываю один и тот же код. Поэтому я не выбрасываю 2 разных типа исключений. Или ваш код тоже так делает, а я этого не понял?

alex toader 12.04.2021 12:33

Если заголовок вашего запроса содержит Accept: application/json, он ответит с ошибкой json, если вы выполняете обычный запрос, он ответит страницей с ошибкой html, показывая детали исключения, если вы включите отладку.

Andrea Mauro 13.04.2021 14:16

Я думаю, что было бы лучше изменить существующее поведение, реализованное в приложение / Исключения / Handler.php, чем переопределять его.

Вы можете изменить JSONResponse, возвращаемый parent::render($request, $exception);, и добавить / удалить данные.

Пример реализации:
приложение / Исключения / 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;
}

Используя некоторый код из лучшего ответа @RKJ, я обработал ошибки следующим образом:

Откройте класс "Освещение \ Основание \ Исключения \ Обработчик" и найдите метод с именем "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',
        ];
    }

Теперь перейдите к классу Приложение \ Исключения \ Обработчик и вставьте приведенный ниже код чуть выше метода оказывать:

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[$sta‌​tusCode] вместо длинного переключателя.

shaedrich 12.04.2021 17:55

В вашем 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);
}

Для меня лучший способ - использовать конкретное исключение для ответа API.

Если вы используете Laravel версии> 5.5, вы можете создать собственное исключение с методами report() и 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());
    }
}

Этого достаточно, чтобы сделать ваш код более простым для чтения, красивым и полезным.
С наилучшими пожеланиями!

Другие вопросы по теме