签入版本
This commit is contained in:
@@ -1,174 +1,153 @@
|
||||
# 3.4 验证器开发
|
||||
# 3.4 FormRequest 验证器
|
||||
|
||||
:::tip 概述
|
||||
验证器用于校验请求参数的合法性,继承 ThinkPHP 的 Validate 类。通过在 Logic 层配置 `$validateClass` 属性,`add`/`update` 时自动触发校验,无需手动调用。
|
||||
参数格式验证由 **FormRequest** 完成(Laravel 标准机制):控制器方法类型注入请求类,未通过校验时框架自动返回统一 JSON 错误响应。所有请求类继承本项目的 `BaseRequest`,子类只需声明 `rules()` 与 `messages()`。
|
||||
:::
|
||||
|
||||
## 验证器文件位置
|
||||
## 请求类文件位置
|
||||
|
||||
```text
|
||||
app/validate/ExampleValidate.php
|
||||
app/Http/Requests/ExampleRequest.php
|
||||
```
|
||||
|
||||
## 完整代码(来自项目实际代码)
|
||||
|
||||
```php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\validate;
|
||||
|
||||
use think\Validate;
|
||||
namespace App\Http\Requests;
|
||||
|
||||
/**
|
||||
* 案例演示验证器
|
||||
* 案例表单请求
|
||||
*
|
||||
* 负责案例演示模块的入参校验,继承 ThinkPHP 的 Validate。
|
||||
* 负责案例演示新增/修改接口的参数校验。
|
||||
*
|
||||
* 结构说明:
|
||||
* - $rule:字段校验规则,由必填字段自动推导;
|
||||
* - $message:自定义错误提示,按"字段.规则"生成中文提示;
|
||||
* - $scene:场景化校验,add 与 update 使用相同的字段集。
|
||||
*
|
||||
* 说明:唯一性校验未写入本验证器,
|
||||
* 由 ExampleLogic 的 uniqueFields 在逻辑层完成。
|
||||
* 说明:
|
||||
* - 继承 BaseRequest,基础校验能力(失败响应统一处理)由基类提供。
|
||||
* - 本类只声明案例特有的字段规则与自定义错误消息。
|
||||
* - 新增与修改共用本请求类,修改时由基类或控制器负责校验 id。
|
||||
* - 前端传参使用 camelCase,数据库字段为 snake_case;
|
||||
* - type 类型:1=类型1,2=类型2,3=类型3,4=类型4,
|
||||
* 与 Example 模型的约定一致。
|
||||
* - status 状态:1=在用,2=停用。
|
||||
*/
|
||||
class ExampleValidate extends Validate
|
||||
class ExampleRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* 校验规则
|
||||
*
|
||||
* 由生成器按字段的必填属性自动推导,
|
||||
* 通常形如 '字段名' => 'require'。
|
||||
*
|
||||
* @var array
|
||||
* @return array<string, string> 字段规则
|
||||
*/
|
||||
protected $rule = [
|
||||
'name' => 'require',
|
||||
];
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255', // 案例名称:必填,最长 255
|
||||
'avatar' => 'nullable|string|max:255', // 头像/图片地址:可空,最长 255
|
||||
'type' => 'nullable|integer|in:1,2,3,4', // 类型:1=类型1,2=类型2,3=类型3,4=类型4
|
||||
'status' => 'nullable|integer|in:1,2', // 状态:1=在用,2=停用
|
||||
'sort' => 'nullable|integer', // 排序值:可空,整数
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义错误提示
|
||||
* 自定义错误消息
|
||||
*
|
||||
* 键为"字段名.规则名",值为中文提示语,
|
||||
* 覆盖框架默认提示,提升用户可读性。
|
||||
* 为需要友好提示的规则提供中文消息,覆盖框架默认英文提示。
|
||||
*
|
||||
* @var array
|
||||
* @return array<string, string> 字段规则 => 错误消息
|
||||
*/
|
||||
protected $message = [
|
||||
'name.require' => '请输入案例名称',
|
||||
];
|
||||
|
||||
/**
|
||||
* 校验场景
|
||||
*
|
||||
* add 与 update 使用相同的字段集,
|
||||
* 与项目其它验证器的统一模式保持一致。
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $scene = [
|
||||
'add' => ['name'], // 新增场景
|
||||
'update' => ['name'], // 修改场景
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
## 在 Logic 中配置自动验证(推荐)
|
||||
|
||||
在 Logic 子类中配置 `$validateClass` 属性,`add`/`update` 时自动调用对应验证器:
|
||||
|
||||
```php
|
||||
// app/logic/ExampleLogic.php
|
||||
class ExampleLogic extends BaseLogic
|
||||
{
|
||||
/**
|
||||
* 关联的模型类
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $modelClass = Example::class;
|
||||
|
||||
/**
|
||||
* 参数验证器类
|
||||
*
|
||||
* 配置后,add/update 自动校验
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $validateClass = \app\validate\ExampleValidate::class;
|
||||
|
||||
// ... 其他属性
|
||||
}
|
||||
```
|
||||
|
||||
**工作原理:**
|
||||
|
||||
1. `add()` 流程:`beforeAdd` → `validateData($data, 'add')` → 后续入库
|
||||
2. `update()` 流程:`beforeUpdate` → `validateData($data, 'update', $id)` → 后续入库
|
||||
3. 验证器按场景(`add`/`update`)校验,场景不存在时使用默认规则
|
||||
4. 校验失败抛出 `ValidateException`,由 `ExceptionHandle` 统一返回错误响应
|
||||
|
||||
**覆盖范围:** 无论是 Controller 通过 `_add`/`_edit` 调用,还是 `import` 等内部调用都会生效。
|
||||
|
||||
## 在 Controller 中手动调用(特殊场景)
|
||||
|
||||
对于不走 `BaseLogic.add()`/`update()` 的特殊接口(如登录),可在 Controller 中手动调用:
|
||||
|
||||
```php
|
||||
// app/controller/LoginController.php
|
||||
public function login(): Json
|
||||
{
|
||||
$params = $this->getParams();
|
||||
try {
|
||||
$this->validate($params, LoginValidate::class . '.login');
|
||||
} catch (\Exception $e) {
|
||||
return $this->fail($e->getMessage(), 422);
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => '案例名称不能为空',
|
||||
'name.max' => '案例名称不能超过 255 个字符',
|
||||
'avatar.max' => '图片地址不能超过 255 个字符',
|
||||
'type.in' => '案例类型不合法,允许:1-类型1 2-类型2 3-类型3 4-类型4',
|
||||
'status.in' => '状态值不合法,允许:1-在用 2-停用',
|
||||
'sort.integer' => '排序值格式不正确',
|
||||
];
|
||||
}
|
||||
// ... 登录逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 控制器中的使用
|
||||
|
||||
```php
|
||||
// 类型注入:请求到达方法前自动完成校验
|
||||
public function add(ExampleRequest $request): JsonResponse
|
||||
{
|
||||
// validated() → 已校验数据,键名转换由 Service 写入入口统一完成
|
||||
return $this->_add($this->exampleService, $request->validated());
|
||||
}
|
||||
```
|
||||
|
||||
:::tip validated() 是白名单
|
||||
`validated()` 只返回 `rules()` 中声明过的键,未声明的字段在进入控制器前即被丢弃。业务需要传递给 Service 的字段(如修改接口的 `id`、用户表单的 `city` / `roles`)都应在规则中声明,否则即使前端传了也到不了 Service。
|
||||
:::
|
||||
|
||||
## BaseRequest 基类提供的能力
|
||||
|
||||
`app/Http/Requests/BaseRequest.php`:
|
||||
|
||||
| 能力 | 说明 |
|
||||
|------|------|
|
||||
| `authorize()` 统一返回 true | 鉴权由路由中间件与 `#[Permission]` 注解负责,请求类不参与授权 |
|
||||
| `failedValidation()` 统一处理 | 校验失败返回固定 JSON:`{"code":1,"ok":false,"msg":"参数验证失败","data":{字段=>错误数组}}`(code=1 与 `Controller::fail()` 约定一致) |
|
||||
|
||||
:::warning 两条校验失败路径
|
||||
- **FormRequest 注入**(本章方式)→ 失败响应 `code=1`;
|
||||
- **代码内手动** `Validator::validate()` 抛出的 `ValidationException` → 由全局 Handler 渲染为 `code=422`。
|
||||
|
||||
前端两种都应兼容(以 `code !== 0` 判失败)。详见 [5.16 统一响应与异常抛出](/zh/core/exception.html)。
|
||||
:::
|
||||
|
||||
## 常用验证规则
|
||||
|
||||
| 规则 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `require` | 必填 | `'name' => 'require'` |
|
||||
| `required` | 必填 | `'name' => 'required'` |
|
||||
| `string` | 字符串 | `'name' => 'string'` |
|
||||
| `max:n` | 最大长度 | `'title' => 'max:200'` |
|
||||
| `min:n` | 最小长度 | `'password' => 'min:6'` |
|
||||
| `number` | 数字 | `'sort' => 'number'` |
|
||||
| `integer` | 整数 | `'age' => 'integer'` |
|
||||
| `in:a,b,c` | 枚举值 | `'status' => 'in:0,1'` |
|
||||
| `integer` | 整数 | `'sort' => 'integer'` |
|
||||
| `in:a,b,c` | 枚举值 | `'status' => 'in:1,2'` |
|
||||
| `nullable` | 可空(不传则跳过其余规则) | `'avatar' => 'nullable\|string'` |
|
||||
| `email` | 邮箱格式 | `'email' => 'email'` |
|
||||
| `mobile` | 手机号格式 | `'mobile' => 'mobile'` |
|
||||
| `unique:table` | 唯一性 | `'username' => 'unique:user'` |
|
||||
| `date` | 日期格式 | `'start_time' => 'date'` |
|
||||
| `regex:/.../` | 正则 | `'code' => 'regex:/^[A-Za-z]+$/'` |
|
||||
|
||||
## 场景(Scene)用法
|
||||
## 分层职责:格式 vs 业务
|
||||
|
||||
场景允许在不同接口使用不同的验证规则。项目统一使用 `$scene` 属性定义:
|
||||
| 层 | 职责 | 示例 |
|
||||
|----|------|------|
|
||||
| **FormRequest(本章)** | 参数**格式**:必填、类型、长度、枚举 | `name` 必填、`type` 只能 1-4 |
|
||||
| **Service 层** | 参数**业务规则**:唯一性、状态依赖、权限 | `name` 不能与已有记录重复 |
|
||||
|
||||
:::tip 唯一性为什么不放 FormRequest?
|
||||
`uniqueFields` 校验在 Service 层完成(`HasUniqueCheck` Trait),因为软删除场景下数据库唯一索引会误伤(`is_delete=1` 的旧记录仍占索引),且需要排除自身(update 场景)。见 [3.5 Service 层开发](/zh/guide/logic.html)。
|
||||
:::
|
||||
|
||||
## 场景拆分(新增 vs 修改字段差异大时)
|
||||
|
||||
FormRequest 可按方法区分规则:
|
||||
|
||||
```php
|
||||
class UserValidate extends Validate
|
||||
class UserRequest extends BaseRequest
|
||||
{
|
||||
protected $rule = [
|
||||
'username' => 'require|unique:user',
|
||||
'realname' => 'require',
|
||||
'mobile' => 'mobile',
|
||||
'email' => 'email',
|
||||
];
|
||||
public function rules(): array
|
||||
{
|
||||
// 通过 $this->isMethod('post') 或自定义场景标识区分
|
||||
$rules = [
|
||||
'realname' => 'required|string|max:50',
|
||||
'mobile' => 'nullable|max:20',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'username.require' => '用户名不能为空',
|
||||
'username.unique' => '用户名已存在',
|
||||
'realname.require' => '姓名不能为空',
|
||||
];
|
||||
if ($this->isMethod('post')) {
|
||||
// 新增时才要求用户名
|
||||
$rules['username'] = 'required|string|min:3|max:50';
|
||||
}
|
||||
|
||||
// 场景属性:指定每个场景校验哪些字段
|
||||
protected $scene = [
|
||||
'add' => ['username', 'realname', 'mobile', 'email'],
|
||||
'update' => ['realname', 'mobile', 'email'], // 修改时不校验 username
|
||||
];
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**自动场景匹配:** Logic 配置了 `$validateClass` 后,`add()` 自动使用 `add` 场景,`update()` 自动使用 `update` 场景,无需手动指定。
|
||||
Reference in New Issue
Block a user