Problem.php 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. <?php
  2. namespace App\Exceptions;
  3. use Illuminate\Http\JsonResponse;
  4. use Illuminate\Http\Request;
  5. /**
  6. * RFC 9457 Problem Details 响应的唯一构造点。
  7. *
  8. * v3 端点的所有错误都从这里出去:bootstrap/app.php 的异常处理器、
  9. * 以及自带 render() 的 BusinessException。控制器不要直接调用它——
  10. * 控制器只负责抛异常。
  11. */
  12. class Problem
  13. {
  14. /**
  15. * 状态码 → 稳定的英文 title。
  16. *
  17. * 按 RFC 9457,title 应在同类问题的所有场合保持一致、不随语言变化,
  18. * 便于机器识别;面向人的文案放 detail,由调用方传入已 __() 的字符串。
  19. */
  20. private const TITLES = [
  21. 400 => 'Bad request.',
  22. 401 => 'Authentication required.',
  23. 403 => 'Forbidden.',
  24. 404 => 'Resource not found.',
  25. 405 => 'Method not allowed.',
  26. 409 => 'Conflict.',
  27. 419 => 'Page expired.',
  28. 422 => 'Your request is not valid.',
  29. 429 => 'Too many requests.',
  30. 500 => 'Server error.',
  31. 503 => 'Service unavailable.',
  32. ];
  33. public static function titleFor(int $status): string
  34. {
  35. return self::TITLES[$status] ?? (
  36. $status >= 500 ? self::TITLES[500] : self::TITLES[400]
  37. );
  38. }
  39. /**
  40. * @param string $slug 问题类型标识,进 type 字段
  41. * @param array<string, mixed> $extra 扩展成员,如 ['errors' => [...]]
  42. */
  43. public static function response(
  44. Request $request,
  45. int $status,
  46. string $slug,
  47. ?string $title = null,
  48. ?string $detail = null,
  49. array $extra = []
  50. ): JsonResponse {
  51. $body = array_filter([
  52. // 刻意用 URN 而不是 url():绝对地址由 APP_URL 拼出,
  53. // 反向代理下容易拼错,而且目前没有文档页可供解引用。
  54. 'type' => "urn:problem:{$slug}",
  55. 'title' => $title ?? self::titleFor($status),
  56. 'status' => $status,
  57. 'detail' => $detail,
  58. 'instance' => $request->getRequestUri(),
  59. ], fn ($v) => $v !== null && $v !== '') + $extra;
  60. return response()->json(
  61. $body,
  62. $status,
  63. ['Content-Type' => 'application/problem+json'],
  64. JSON_UNESCAPED_UNICODE
  65. );
  66. }
  67. }