Validate层只做字段合规性校验,Service层处理业务规则;两者不可直接依赖,校验需分层隔离,DTO传递数据,错误信息按类型区分返回。

Service 层和 Validate 层不该有直接依赖关系,Validate 只负责字段级基础校验,Service 才处理业务规则。两者强行耦合(比如在 Service 里 new Validate、或 Validate 里调 Service 方法)会破坏分层边界,导致测试难、复用差、错误定位混乱。
Validate 层只做「字段合规性」,不做「业务合理性」
验证器类(如 UserRegisterValidate)的 $rule 里只能出现 require、email、confirmed、between 这类无状态规则;不能写 'email' => 'checkEmailExists' 或 'password' => 'checkOldPassword:uid' 这种需查库、依赖上下文的逻辑。
- 字段重复(如邮箱已注册)、密码强度策略、权限校验、关联数据存在性等,必须移入 Service 层的
register()或updateProfile()方法中 - 验证器若硬塞业务逻辑,会导致单元测试无法隔离——你测的是字段格式,还是数据库连接?
-
exists:user,email是个例外:它是 ThinkPHP 内置的轻量规则,仅做单表主键/唯一索引查询,不涉及复杂业务判断,可保留在 Validate 中
Service 层接收已清洗的数据,不信任任何外部输入
Controller 调用 Service 前,必须确保数据已通过 Validate 校验;Service 方法签名应明确接受 DTO(如 UserRegisterDTO),而非原始 array 或 Request 对象。
- DTO 类可封装类型转换逻辑(如把字符串
status转为int),避免 Service 里散落(int)$data['status'] - Service 方法内不再调用
$this->validate()或实例化验证器——它只专注组合、决策、事务、领域模型操作 - 若需二次校验(如“用户余额是否足够支付”),应在 Service 内部用领域对象方法(
$user->canPay($order))而非再拉一个验证器
错误信息怎么对齐前端?别让 Validate 和 Service 各说各话
Validate 失败返回字段级错误(['email' => '邮箱格式不正确']),Service 业务失败应返回语义化错误码与提示(Result::fail(403, '余额不足,请充值')),二者结构不同,不能混用 getError() 直接透传。
立即学习“PHP免费学习笔记(深入)”;
- Controller 层统一拦截:先
try { $this->validate(...); },再try { $service->doSomething(); },最后合并成标准响应格式 - 不要在 Service 里抛
ValidateException—— 它是验证层专属异常,Service 应抛自定义业务异常(如InsufficientBalanceException),由全局异常处理器转为 Result - 前端若需区分“表单填错”和“业务失败”,后端响应体里可用
error_type: 'validation' | 'business'字段标识,而不是靠 HTTP 状态码硬分
最易被忽略的一点:Validate 的 scene 和 Service 的方法粒度必须对齐。一个 UserService::createOrder() 对应一个 OrderSubmitValidate,而不是复用 UserRegisterValidate;否则字段白名单、错误提示、扩展规则全乱套——这不是配合,是强扭的瓜。



















