- 后端
- 开发工具
【免费下载链接】Validation
The most awesome validation engine ever created for PHP
Given(Validator $when, Validator $then)是 PHP 校验库 Respect\Validation 在 3.2.0 版本引入的条件型校验器:只有当条件校验器$when对输入通过时,才会继续执行$then校验;条件不成立时直接静默放行,不存在$else分支。本文将从官方文档出发,结合 Given 源码 与测试用例,讲解它的行为语义、与When的差异、在AllOf/ShortCircuit链中的最佳实践,以及消息模板的自定义方式,帮助你写出“跳过无关校验”的精准验证逻辑。
一、核心语义:条件成立才校验,否则静默通过
Given的签名在文档中定义为:
Given(Validator $when, Validator $then)其行为可以概括为一句话:当$when对输入通过时,返回$then的校验结果;否则直接返回通过(passes silently)。文档给出的三个示例可以清晰展示这一语义:
v::given(v::intVal(), v::positive())->assert(5); // Validation passes successfully v::given(v::intVal(), v::positive())->assert('non-integer'); // Validation passes successfully v::given(v::intVal(), v::positive())->assert(-1); // → -1 must be a positive number解读这三个分支:
- 输入
5:intVal()通过,接着校验positive(),通过; - 输入
'non-integer':intVal()不通过,Given不执行positive(),整体静默通过; - 输入
-1:intVal()通过,positive()失败,抛出异常,消息为-1 must be a positive number。
文档特别强调:如果$input不是整数,校验静默通过——不存在$else分支。这一点与同为条件型校验器的 When 有本质区别(详见下文第四节对比)。
二、源码级解读:evaluate() 到底做了什么
Given的实现非常精简,完整源码位于 src/Validators/Given.php:
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)] final readonly class Given implements Validator { public function __construct( private Validator $when, private Validator $then, ) { } public function evaluate(mixed $input): Result { if ($this->when->evaluate($input)->hasPassed) { return $this->then->evaluate($input); } return (new AlwaysValid())->evaluate($input); } }从源码结构可以提炼出几个关键实现事实:
- 条件判定基于
Result::hasPassed:Given调用$this->when->evaluate($input),仅以返回的Result对象上的hasPassed布尔值决定分支走向,自身并不关心$when失败的具体原因。 - “静默通过”由
AlwaysValid兜底实现:当$when不通过时,直接构造 AlwaysValid 并返回其求值结果。AlwaysValid::isValid()恒为true,模板为{{subject}} must be valid,因此对调用方而言,条件未命中时Given表现如同一个恒真的占位校验器。 - 构造函数只接受两个校验器:没有第三个
$else参数。这一点与When(可传入$else,默认 AlwaysInvalid)形成对照——Given的“未命中即放行”行为是硬编码在类型结构里的。 final readonly class+ PHP 8 属性(Attribute):与仓库中绝大多数校验器一致,Given被声明为只读终类,且带有#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)],意味着它既可以作为链式调用中的普通校验器,也可以直接作为类属性注解使用。- 实现
Validator接口:Given直接实现 src/Validator.php 中定义的Validator接口(仅需实现evaluate(mixed $input): Result),因此它可以被嵌套进任何复合校验器(如AllOf、ShortCircuit)内部。
同时,仓库的 Builder/Chain 混入类也提供了同名工厂方法,见 src/Mixins/Builder.php 与 src/Mixins/Chain.php:
public static function given(Validator $when, Validator $then): Chain; public function given(Validator $when, Validator $then): Chain;这意味着你既可以用静态入口v::given(...),也可以在任意链上继续->given(...)拼接后续校验器。
三、为什么“静默放行”在 AllOf / ShortCircuit 链中特别有用
文档明确指出:“当跳过无关校验是期望行为时,Given在AllOf或ShortCircuit链中非常理想”(原文:This makesGivenideal insideAllOforShortCircuitchains where "skip when irrelevant" is the desired behavior)。
理解这一点需要先看两种复合校验器的差异:
- AllOf:会求值所有子校验器并收集全部失败,其
evaluate()通过array_map逐个执行子校验器后聚合Result(见 src/Validators/AllOf.php)。 - ShortCircuit:类似 PHP 的
&&,遇到第一个失败立即返回,后续校验器不再执行(见 src/Validators/ShortCircuit.php)。
Given的价值在于:把它作为链中的一个节点时,它可以按输入动态地“启用/禁用”某条校验规则,而不破坏链的整体结构。典型的组合写法如下:
$validator = v::allOf( v::given(v::stringType(), v::length(3, 10)), // 只有字符串才检查长度 v::given(v::not(v::stringType()), v::alwaysValid()), // 非字符串直接跳过 ); $validator->assert('abc'); // 通过 $validator->assert('toolong123'); // 抛出:长度校验失败 $validator->assert(12345); // 静默通过(不是字符串,长度规则被跳过)在ShortCircuit中,Given的“条件不成立返回通过”还能避免误触发出错短路——例如只有前置条件满足时才需要检查的字段,条件不满足时让短路逻辑继续向后执行,而不是把“跳过”误判为“失败”。
四、Given 与 When 的对比:有无 else 分支是关键差异
Given常被拿来与 When 比较,两者都是条件型校验器,但语义不同:
| 维度 | Given(Validator $when, Validator $then) | When(Validator $when, Validator $then[, Validator $else]) |
|---|---|---|
| 条件通过 | 校验$then | 校验$then |
| 条件不通过 | 静默放行(AlwaysValid) | 校验$else;未提供$else时默认使用AlwaysInvalid |
| 行为模型 | 二分支,无失败路径 | 三分支,恒有结果 |
| 适用场景 | “不相关就跳过” | 严格的 if / else 分支 |
对照 When 源码 可以看到,When的构造器第三个参数默认值是new Templated(AlwaysInvalid::TEMPLATE_SIMPLE, new AlwaysInvalid()),即条件不通过时默认失败;而Given的构造器根本不存在第三个参数,条件不通过时由AlwaysValid兜底通过。因此:
- 需要“不满足条件就报错”时用
When; - 需要“不满足条件就跳过、永不报错”时用
Given。
文档的 See Also 同时列出两者,正是提示读者在二者之间按语义取舍。此外,ShortCircuit 文档 还提到一个工程技巧:与其在When中重复书写条件校验器,不如用Given(或ShortCircuit+Factory)避免重复——这是Given在真实项目中的另一大卖点。
五、消息模板:subject 占位符与自定义模板
Given自身并不产生独立模板,它的模板占位符只有一个:
| 占位符 | 说明 |
|---|---|
subject | 被校验的输入值;若指定了自定义校验器名称,则为该校验器名称 |
实际抛出的消息完全来自内部$then校验器的模板。仓库的功能测试 tests/feature/Validators/GivenTest.php 验证了消息的三种形态:
- 默认消息:
v::given(v::intVal(), v::positive())->assert(-1); // message: -1 must be a positive number // fullMessage: - -1 must be a positive number // messages: ['positive' => '-1 must be a positive number']- 单一字符串模板(替换整条消息):
v::given(v::intVal(), v::positive())->assert(-1, 'That did not go as planned'); // message: That did not go as planned- 按校验器名分组的数组模板:
v::given(v::intVal(), v::positive())->assert(-1, ['positive' => 'Not a positive number']); // message: Not a positive number可以看到,自定义模板既可以按$then内部校验器的名称(此处为positive)定向替换,也可以整体替换,fullMessage与messages数组会同步反映替换结果。
六、测试验证:Given 的四象限行为矩阵
单元测试 tests/unit/Validators/GivenTest.php 用Stub桩校验器把Given的四种组合全部覆盖:
// providerForValidInput:应当通过 'when fail, then daze' => [new Given(Stub::fail(1), Stub::daze()), rand()], 'when pass, then pass' => [new Given(Stub::pass(1), Stub::pass(1)), rand()], // providerForInvalidInput:应当失败 'when pass, then fail' => [new Given(Stub::pass(1), Stub::fail(1)), rand()],对应行为矩阵为:
$when结果 | $then结果 | Given整体结果 |
|---|---|---|
| 失败 | 任意(daze桩) | 通过(静默放行) |
| 通过 | 通过 | 通过 |
| 通过 | 失败 | 失败(返回$then的失败结果) |
这组用例从行为层验证了文档对Given的描述:唯一导致校验失败的情形是条件成立且$then校验失败;其余三种情况均通过。
七、分类与版本信息
- 分类(Categorization):Conditions(条件)、Nesting(嵌套)。这意味着
Given属于条件逻辑类校验器,且设计上鼓励被嵌套进更复杂的组合结构中。 - 版本历史(Changelog):
3.2.0创建。它是在 Respect\Validation v3.2.0 中新增的校验器,属于较新引入的 API。
八、相关校验器一览
Given与其他逻辑类校验器共同构成条件校验家族,官方文档的 See Also 指向:
- AllOf:全部子校验器通过才通过,会收集所有失败;
- AlwaysValid:恒真校验器,
Given静默放行路径的底层实现; - AnyOf:任一子校验器通过即通过;
- NoneOf:所有子校验器都不通过才通过;
- OneOf:恰好一个子校验器通过才通过;
- ShortCircuit:短路求值,遇首个失败即返回;
- When:带
$else分支的三元条件校验器,与Given形成互补。
结语
Given以最小的 API 面(两个参数、无 else、静默放行)解决了条件校验中最常见的一类需求——“与输入无关的规则就不要执行,也不要报错”。无论是嵌在AllOf中按输入类型动态启用规则,还是放进ShortCircuit避免无关失败提前短路,它都能让校验逻辑更贴近业务语义。使用时的要点只有一条:想清楚条件不成立时是“跳过”(用Given)还是“走 else 分支”(用When)——这正是文档与源码共同强调的核心分界线。
- 后端
- 开发工具
【免费下载链接】Validation
The most awesome validation engine ever created for PHP
相关推荐
class-validator 条件验证指南:@ValidateIf 实现指定条件下才执行校验的实用技巧
class validator 条件验证指南:@ValidateIf 实现指定条件下才执行校验的实用技巧 在 TypeScript 项目中, class val
后端开发工具Gradle 工作校验(Work Validation)机制全解:静态校验与运行时校验的源码级剖析
Gradle 工作校验(Work Validation)机制全解:静态校验与运行时校验的源码级剖析 本文以 Work Validation.md 为核心骨架,结
构建工具开发工具Ant Design Form 动态校验规则实战:让校验条件随状态实时变化
Ant Design Form 动态校验规则实战:让校验条件随状态实时变化 表单校验规则并非只能静态写死。在 Ant Design 中, Form.Item 的
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考