Стандартні 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.