news 2026/10/6 2:06:31

Respect Validation 条件校验器 Given 深入解析:当条件成立时才校验,否则静默放行

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Respect Validation 条件校验器 Given 深入解析:当条件成立时才校验,否则静默放行
  • 后端
  • 开发工具

【免费下载链接】Validation

The most awesome validation engine ever created for PHP

项目地址:https://gitcode.com/gh_mirrors/va/Validation
点击查看免费下载

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); } }

从源码结构可以提炼出几个关键实现事实:

  1. 条件判定基于Result::hasPassed:Given调用$this->when->evaluate($input),仅以返回的Result对象上的hasPassed布尔值决定分支走向,自身并不关心$when失败的具体原因。
  2. “静默通过”由AlwaysValid兜底实现:当$when不通过时,直接构造 AlwaysValid 并返回其求值结果。AlwaysValid::isValid()恒为true,模板为{{subject}} must be valid,因此对调用方而言,条件未命中时Given表现如同一个恒真的占位校验器。
  3. 构造函数只接受两个校验器:没有第三个$else参数。这一点与When(可传入$else,默认 AlwaysInvalid)形成对照——Given的“未命中即放行”行为是硬编码在类型结构里的。
  4. final readonly class+ PHP 8 属性(Attribute):与仓库中绝大多数校验器一致,Given被声明为只读终类,且带有#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)],意味着它既可以作为链式调用中的普通校验器,也可以直接作为类属性注解使用。
  5. 实现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 验证了消息的三种形态:

  1. 默认消息:
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']
  1. 单一字符串模板(替换整条消息):
v::given(v::intVal(), v::positive())->assert(-1, 'That did not go as planned'); // message: That did not go as planned
  1. 按校验器名分组的数组模板:
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

项目地址:https://gitcode.com/gh_mirrors/va/Validation
点击查看免费下载

相关推荐

上一篇:vue2-ace-editor:Vue 2.x 的终极代码编辑器解决方案
下一篇:WSA Pacman:Windows上最直观的安卓应用管理神器终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!