推荐无条件用自定义 FormRequest 类:规则写在 rules() 方法里,动态逻辑用闭包或 withValidator(),查库操作禁放 rules();复用性验证必须用 Rule 类;API 错误统一 JSON 格式;日期校验优先用 date_format 而非 date。

验证规则写在 Controller 还是 Request 类里?
Laravel API 的校验逻辑放哪,直接决定后续维护成本。Controller 里硬写 validate() 看似快,但规则一多、复用一来就崩——比如同一个 email 校验在注册和密码重置里各写一遍,改个正则就得找两处。
推荐无条件用自定义 FormRequest 类:php artisan make:request StoreUserRequest。它天然支持授权(authorize())、预处理(prepareForValidation())、错误响应格式统一,而且 IDE 能跳转、Git 差异清晰。
- 规则写在
rules()方法里,返回数组,键是字段名,值是规则字符串或数组 - 需要动态规则(比如“用户名不能和当前用户相同”)就用闭包:
'username' => ['required', 'string', function ($attribute, $value, $fail) { if ($value === auth()->user()?->username) $fail('用户名不能与当前用户相同'); }] - 不要试图在
rules()里调用模型查询——查库逻辑应放在withValidator()或自定义规则里,否则验证失败时也会执行无谓查询
自定义验证规则该用 Rule 对象还是闭包?
闭包适合一次性、逻辑极简的判断(比如“不能等于某个固定值”),但只要涉及模型查询、多字段联动、或未来可能复用,就必须写成 Rule 类:php artisan make:rule NotSameAsCurrentUser。
Rule 类的好处是可测试、可注入依赖、可复用。比如你写了 NotSameAsCurrentUser,下次改邮箱时也能直接用,不用再抄一遍 auth()->user()?->email。
- Rule 类必须实现
passes($attribute, $value)和message();若需访问整个请求数据,加__construct()接收参数,并在passes()里用$this->validator->getData() - 别在
message()里拼接动态内容(如用户名),Laravel 不会自动替换;正确做法是返回占位符(:attribute 不能与当前用户相同),并在passes()失败时手动调用$fail(__(...)) - 闭包规则无法被
phpunit单独测,Rule 类可以,这点上线后很关键
API 返回的错误格式怎么统一?
默认 ValidateException 返回的是 HTML 重定向响应,API 必须强制走 JSON。靠中间件或全局异常处理器都行,但最稳的方式是在 app/Exceptions/Handler.php 的 render() 里拦截:
if ($request->expectsJson() && $exception instanceof ValidationException) {
return response()->json([
'message' => '验证失败',
'errors' => $exception->errors()
], 422);
}
注意两点:
-
$exception->errors()返回的是字段为 key 的二维数组(['email' => ['邮箱格式不正确']]),前端好解析 - 不要用
$exception->getMessage(),它只返回“The given data was invalid”,没用 - 如果用了 Sanctum 或 Passport,确保
Auth::guard('sanctum')在验证前已初始化,否则auth()->user()在 Rule 里会是 null
日期字段校验为什么总报“not a valid date”?
API 接收的日期通常是字符串("2024-05-20" 或 "2024-05-20T10:30:00Z"),但 Laravel 默认的 date 规则只认 PHP strtotime() 能解析的格式,对 ISO8601 带时区的字符串容易失败。
实际该用 date_format:Y-m-d 或 date_format:Y-m-d\TH:i:sP,而不是裸写 date。更稳妥的是先用 required|string 过滤空值和类型,再用 date_format 或自定义规则做精确匹配。
-
date规则底层调用strtotime(),会把"2024-05-20T10:30:00Z"当作有效,但某些 PHP 版本(尤其 Windows)会出错 - 若需接受多种格式(如前端传
"2024/05/20"或"20-May-2024"),别堆砌多个date_format,改用 Rule 类里用Carbon::createFromFormat()逐个尝试 - 时间戳字段(
created_at)不要用date校验,用integer|between:1,4102444800(对应 1970–2100 年 Unix 时间戳)更准
验证器不是越严越好,而是字段语义和接口契约对得上。比如手机号字段写 regex:/^1[3-9]\d{9}$/ 比 digits:11 更贴近真实约束,但也要留余地——海外号码、带国家码的格式,得换策略。










