PHP项目Laravel API异常响应格式

wen PHP项目 3

本文目录导读:

PHP项目Laravel API异常响应格式

  1. 基础方案:自定义异常处理器
  2. 使用中间件方案
  3. 高级方案:统一异常响应服务
  4. 在控制器中使用
  5. 使用服务提供者
  6. 全局助手函数
  7. 配置异常级别
  8. 异常响应示例
  9. 推荐方案

在Laravel中统一处理API异常响应格式,我推荐以下几种方案:

基础方案:自定义异常处理器

1 创建统一响应格式

// app/Exceptions/ApiResponseTrait.php
<?php
namespace App\Exceptions;
use Illuminate\Http\JsonResponse;
trait ApiResponseTrait
{
    /**
     * 成功响应
     */
    protected function success($data = null, string $message = '操作成功', int $code = 200): JsonResponse
    {
        return response()->json([
            'code' => $code,
            'message' => $message,
            'data' => $data,
            'timestamp' => now()->timestamp
        ]);
    }
    /**
     * 失败响应
     */
    protected function error(string $message = '操作失败', int $code = 400, $data = null): JsonResponse
    {
        return response()->json([
            'code' => $code,
            'message' => $message,
            'data' => $data,
            'timestamp' => now()->timestamp
        ]);
    }
    /**
     * 分页响应
     */
    protected function paginated($paginator, string $message = '操作成功'): JsonResponse
    {
        return response()->json([
            'code' => 200,
            'message' => $message,
            'data' => [
                'items' => $paginator->items(),
                'pagination' => [
                    'current_page' => $paginator->currentPage(),
                    'per_page' => $paginator->perPage(),
                    'total' => $paginator->total(),
                    'last_page' => $paginator->lastPage(),
                    'has_more_pages' => $paginator->hasMorePages()
                ]
            ]
        ]);
    }
}

2 自定义异常类

// app/Exceptions/ApiException.php
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class ApiException extends Exception
{
    protected $data;
    public function __construct(
        string $message = 'API错误',
        int $code = 400,
        $data = null,
        Throwable $previous = null
    ) {
        parent::__construct($message, $code, $previous);
        $this->data = $data;
    }
    public function getData()
    {
        return $this->data;
    }
}
// 自定义业务异常
class BusinessException extends ApiException
{
    public function __construct(string $message = '业务处理失败', $data = null)
    {
        parent::__construct($message, 422, $data);
    }
}
// 权限异常
class PermissionException extends ApiException
{
    public function __construct(string $message = '没有权限执行此操作', $data = null)
    {
        parent::__construct($message, 403, $data);
    }
}

3 修改异常处理器

// app/Exceptions/Handler.php
<?php
namespace App\Exceptions;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;
class Handler extends ExceptionHandler
{
    use ApiResponseTrait;
    protected $dontReport = [
        ApiException::class,
        ValidationException::class,
    ];
    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
    ];
    public function register(): void
    {
        $this->reportable(function (Throwable $e) {
            // 日志记录
            if (config('app.debug')) {
                \Log::error($e->getMessage(), [
                    'exception' => get_class($e),
                    'file' => $e->getFile(),
                    'line' => $e->getLine(),
                ]);
            }
        });
        $this->renderable(function (Throwable $e, $request) {
            // 只处理API请求
            if ($request->is('api/*') || $request->expectsJson()) {
                return $this->handleApiException($e, $request);
            }
        });
    }
    /**
     * 处理API异常
     */
    protected function handleApiException(Throwable $e, $request): JsonResponse
    {
        // 自定义业务异常
        if ($e instanceof ApiException) {
            return $this->error($e->getMessage(), $e->getCode(), $e->getData());
        }
        // 验证异常
        if ($e instanceof ValidationException) {
            return $this->error(
                '数据验证失败',
                422,
                ['errors' => $e->errors()]
            );
        }
        // 认证异常
        if ($e instanceof AuthenticationException) {
            return $this->error('未认证或认证已过期', 401);
        }
        // 授权异常
        if ($e instanceof AuthorizationException) {
            return $this->error('没有权限执行此操作', 403);
        }
        // 模型未找到
        if ($e instanceof ModelNotFoundException) {
            return $this->error('资源不存在', 404);
        }
        // 路由未找到
        if ($e instanceof NotFoundHttpException) {
            return $this->error('请求路径不存在', 404);
        }
        // 请求方法不允许
        if ($e instanceof MethodNotAllowedHttpException) {
            return $this->error('请求方法不允许', 405);
        }
        // 数据库查询异常
        if ($e instanceof QueryException) {
            return $this->error('数据库操作失败', 500);
        }
        // 其他异常
        if (config('app.debug')) {
            return $this->error($e->getMessage(), 500, [
                'debug' => [
                    'exception' => get_class($e),
                    'trace' => $e->getTrace(),
                ]
            ]);
        }
        // 生产环境默认响应
        return $this->error('服务器内部错误', 500);
    }
}

使用中间件方案

// app/Http/Middleware/ForceJsonResponse.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ForceJsonResponse
{
    public function handle(Request $request, Closure $next)
    {
        $request->headers->set('Accept', 'application/json');
        $response = $next($request);
        // 确保所有API响应都是JSON格式
        if (!$response instanceof \Illuminate\Http\JsonResponse) {
            $response = response()->json([
                'code' => $response->getStatusCode(),
                'message' => $response->getContent(),
                'data' => null
            ], $response->getStatusCode());
        }
        return $response;
    }
}

高级方案:统一异常响应服务

// app/Services/ApiResponseService.php
<?php
namespace App\Services;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\MessageBag;
class ApiResponseService
{
    /**
     * 成功响应
     */
    public function success($data = null, string $message = '操作成功'): JsonResponse
    {
        return $this->sendResponse($data, $message, 200, true);
    }
    /**
     * 创建成功
     */
    public function created($data = null, string $message = '创建成功'): JsonResponse
    {
        return $this->sendResponse($data, $message, 201, true);
    }
    /**
     * 无内容
     */
    public function noContent(string $message = '操作成功'): JsonResponse
    {
        return $this->sendResponse(null, $message, 204, true);
    }
    /**
     * 错误响应
     */
    public function error(string $message, int $code = 400, $errors = null): JsonResponse
    {
        return $this->sendResponse(null, $message, $code, false, $errors);
    }
    /**
     * 验证错误
     */
    public function validationError(MessageBag $errors): JsonResponse
    {
        return $this->error('数据验证失败', 422, [
            'errors' => $errors->toArray()
        ]);
    }
    /**
     * 发送响应
     */
    protected function sendResponse($data, string $message, int $code, bool $status, $errors = null): JsonResponse
    {
        $response = [
            'code' => $code,
            'status' => $status,
            'message' => $message,
            'data' => $data,
            'timestamp' => now()->toISOString(),
            'time' => round(microtime(true) - LARAVEL_START, 3),
        ];
        if ($errors) {
            $response['errors'] = $errors;
        }
        return response()->json($response, $code);
    }
    /**
     * 分页响应
     */
    public function paginator($items, int $total, int $page, int $perPage): JsonResponse
    {
        return $this->success([
            'items' => $items,
            'meta' => [
                'total' => $total,
                'page' => $page,
                'per_page' => $perPage,
                'last_page' => ceil($total / $perPage),
            ],
        ]);
    }
}

在控制器中使用

// app/Http/Controllers/Api/UserController.php
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\ApiResponseService;
use Illuminate\Http\Request;
class UserController extends Controller
{
    protected $apiResponse;
    public function __construct(ApiResponseService $apiResponse)
    {
        $this->apiResponse = $apiResponse;
    }
    public function index()
    {
        try {
            $users = User::paginate(15);
            return $this->apiResponse->paginator(
                $users->items(),
                $users->total(),
                $users->currentPage(),
                $users->perPage()
            );
        } catch (\Exception $e) {
            return $this->apiResponse->error('获取用户列表失败: '.$e->getMessage());
        }
    }
    public function store(Request $request)
    {
        try {
            $validated = $request->validate([
                'name' => 'required|string|max:255',
                'email' => 'required|email|unique:users',
            ]);
            $user = User::create($validated);
            return $this->apiResponse->created($user);
        } catch (ValidationException $e) {
            return $this->apiResponse->validationError($e->errors());
        } catch (\Exception $e) {
            return $this->apiResponse->error('创建用户失败');
        }
    }
}

使用服务提供者

// app/Providers/ApiResponseServiceProvider.php
<?php
namespace App\Providers;
use App\Services\ApiResponseService;
use Illuminate\Support\ServiceProvider;
class ApiResponseServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton('api.response', function ($app) {
            return new ApiResponseService();
        });
    }
    public function boot()
    {
        //
    }
}

全局助手函数

// app/helpers.php
<?php
if (!function_exists('api_success')) {
    function api_success($data = null, string $message = '操作成功')
    {
        return app('api.response')->success($data, $message);
    }
}
if (!function_exists('api_error')) {
    function api_error(string $message, int $code = 400, $errors = null)
    {
        return app('api.response')->error($message, $code, $errors);
    }
}

配置异常级别

// config/api.php
<?php
return [
    // 是否显示调试信息
    'debug' => env('API_DEBUG', false),
    // 响应格式配置
    'response' => [
        'include_timestamp' => true,
        'include_time_cost' => true,
        'snake_case' => false,
    ],
    // 错误码配置
    'error_codes' => [
        'success' => 200,
        'created' => 201,
        'bad_request' => 400,
        'unauthorized' => 401,
        'forbidden' => 403,
        'not_found' => 404,
        'validation_error' => 422,
        'server_error' => 500,
    ],
];

异常响应示例

// 成功响应
{
    "code": 200,
    "status": true,
    "message": "操作成功",
    "data": {
        "id": 1,
        "name": "Test User"
    },
    "timestamp": "2024-01-15T10:30:00+00:00",
    "time": 12.3
}
// 验证错误响应
{
    "code": 422,
    "status": false,
    "message": "数据验证失败",
    "data": null,
    "timestamp": "2024-01-15T10:30:00+00:00",
    "time": 5.6,
    "errors": {
        "email": ["The email field is required."],
        "name": ["The name field is required."]
    }
}
// 服务器错误响应
{
    "code": 500,
    "status": false,
    "message": "服务器内部错误",
    "data": null,
    "timestamp": "2024-01-15T10:30:00+00:00",
    "time": 8.9
}

推荐方案

建议采用方案1(自定义异常处理器)+ 方案4(服务类)的组合:

  1. 使用 ApiException 和业务异常类处理业务异常
  2. 使用 ApiResponseService 统一响应格式
  3. Handler 中统一处理所有异常
  4. 在控制器中使用 try-catch 或直接抛出异常

这样既保证了异常处理的统一性,又提供了灵活性,如果是团队项目,建议将响应格式做成可配置的,方便不同场景适配。

抱歉,评论功能暂时关闭!