Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як у Laravel повертати всі помилки API в одному форматі, наприклад Problem Details?

Стандартні JSON-помилки Laravel різняться за форматом: {"message": "..."} для одних, {"message", "errors"} для валідації. Для публічного API зручніше один формат - наприклад, Problem Details (RFC 9457, application/problem+json).

Централізований рендеринг у bootstrap/app.php:

use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->shouldRenderJsonWhen(fn (Request $r) => $r->is('api/*') || $r->expectsJson());

    $exceptions->render(function (ValidationException $e, Request $request) {
        if (! $request->is('api/*')) {
            return null;   // стандартна поведінка для веб-форм
        }

        return response()->json([
            'type' => 'https://api.example.com/problems/validation',
            'title' => 'Дані не пройшли перевірку',
            'status' => 422,
            'errors' => $e->errors(),
        ], 422, ['Content-Type' => 'application/problem+json']);
    });

    // ApiProblemException - власний базовий клас доменних помилок застосунку
    $exceptions->render(function (ApiProblemException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'type' => $e->problemType(),
                'title' => $e->getMessage(),
                'status' => $e->status(),
            ], $e->status(), ['Content-Type' => 'application/problem+json']);
        }
    });
})

Доменні винятки з власним рендерингом - клас знає свій статус і тип:

final class OrderAlreadyShipped extends Exception
{
    public function render(Request $request): JsonResponse
    {
        return response()->json([
            'type' => 'https://api.example.com/problems/order-already-shipped',
            'title' => 'Замовлення вже відправлено',
            'status' => 409,
            'order_id' => $this->orderId,
        ], 409, ['Content-Type' => 'application/problem+json']);
    }
}

Що врахувати:

  • винятки фреймворку теж мають іти в загальному форматі: AuthenticationException (401), AuthorizationException/AccessDeniedHttpException (403), NotFoundHttpException і ModelNotFoundException (404), ThrottleRequestsException (429), будь-який HttpExceptionInterface - через $e->getStatusCode();
  • 500 на продакшені - без деталей винятку: загальний title і ідентифікатор для підтримки (instance чи trace_id), за яким знайдеться запис у логах;
  • звітування не змінюється: render впливає лише на відповідь, винятки й далі логуються й ідуть у Sentry/Flare;
  • type - стабільний URL з описом помилки в документації; клієнти орієнтуються на нього, а не на текст title;
  • тести: для кожного типу помилки - перевірка статусу, Content-Type і структури (assertJsonStructure).

Зміна формату помилок - ламаюча зміна для наявних клієнтів, тож його варто обрати на старті API.

Докладніше в документації: Laravel: рендеринг винятків

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання