Respect Validation 的 AnyOf 组合验证器:OR 语义、短路径求值与嵌套用法全解析 后端开发工具【免费下载链接】ValidationThe most awesome validation engine ever created for PHP项目地址https://gitcode.com/gh_mirrors/va/Validation点击查看免费下载导读AnyOf是 PHP 验证库 Respect\Validation 提供的一组复合Composite验证器本质是一个逻辑或OR运算符只要内部任意一个子验证器通过整个AnyOf即判定为通过。本篇指南以 docs/validators/AnyOf.md 为骨架结合源码 src/Validators/AnyOf.php 及其单元测试 tests/unit/Validators/AnyOfTest.php、功能测试 tests/feature/Validators/AnyOfTest.php系统讲解其构造签名、求值原理、模板消息、嵌套组合与短路优化。读完本篇你将掌握如何在表单校验、接口参数校验中写出多选一可容忍多种合法格式的校验规则并能解释其错误消息与短路径求值行为。一、快速上手AnyOf的基本用法AnyOf支持两种构造形式且从 3.0.0 版本起至少需要传入两个子验证器AnyOf(Validator $validator1, Validator $validator2) AnyOf(Validator $validator1, Validator $validator2, Validator ...$validators)官方文档给出的最小示例v::anyOf(v::intVal(), v::floatVal())-assert(15.5); // Validation passes successfully上述示例中intVal()无法验证通过15.5但floatVal()可以因此整体AnyOf()返回通过。这也正是 OR 语义的直接体现至少一个内部验证器通过AnyOf()即返回true。构造约束为什么至少要两个从源码 src/Validators/Core/LogicalComposite.php 可以看到AnyOf继承自LogicalComposite其构造函数强制声明了两个必选参数$validator1、$validator2后续可变参数再追加更多验证器public function __construct(Validator $validator1, Validator $validator2, Validator ...$validators) { $this-validators array_merge([$validator1, $validator2], $validators); }$validators属性被声明为non-empty-arrayValidator非空数组这与文档 Changelog 中3.0.0 版本Require at least two validators to be defined的变更记录完全对应——只有一个成员的或语义没有实际意义因此在 3.0.0 被禁止。子验证器可通过getValidators()取出/** return non-empty-arrayValidator */ public function getValidators(): array { return $this-validators; }二、求值原理evaluate()的 OR 归约逻辑AnyOf::evaluate()的实现位于 src/Validators/AnyOf.php#L37-L47public function evaluate(mixed $input): Result { $children array_map(static fn(Validator $validator) $validator-evaluate($input), $this-validators); $valid array_reduce( $children, static fn(bool $carry, Result $result) $carry || $result-hasPassed, false, ); return Result::of($valid, $input, $this)-withChildren(...$children); }执行流程可以拆解为三步并行求值所有子验证器array_map对传入的每一个子验证器依次调用evaluate($input)得到一组子Result。OR 归约array_reduce以false为初值对每个子结果的hasPassed做||运算。只要任意一个子结果为通过整体结果即为通过。聚合子结果通过withChildren(...$children)将全部子Result挂载到父级Result的children上。Result类的完整结构见 src/Result.php它持有hasPassed、input、validator、id、parameters、template、name、path、adjacent等字段并通过withChildren()以不可变readonly clone方式维护子结果树。这意味着AnyOf失败时所有子验证器的失败原因都会被保留在结果树中供消息格式化与getMessages()使用。三、短路求值evaluateShortCircuit()的性能优化AnyOf同时实现了ShortCircuitable接口src/Validators/Core/ShortCircuitable.php提供短路求值能力。其实现位于 src/Validators/AnyOf.php#L49-L61public function evaluateShortCircuit(mixed $input): Result { $children []; foreach ($this-validators as $validator) { $result $this-evaluateShortCircuitWith($validator, $input); $children[] $result; if ($result-hasPassed) { return Result::passed($input, $this)-withChildren(...$children); } } return Result::failed($input, $this)-withChildren(...$children); }与evaluate()不同短路求值按顺序逐个求值一旦遇到第一个通过的子验证器就立即返回成功后续子验证器不再执行。这与 PHP 原生||运算符的行为一致结果确定后即可停止。evaluateShortCircuitWith()定义在 trait src/Helpers/CanEvaluateShortCircuit.php 中会优先调用子验证器自身的evaluateShortCircuit()若其实现了ShortCircuitable接口否则退化为普通evaluate()private function evaluateShortCircuitWith(Validator $validator, mixed $input): Result { if ($validator instanceof ShortCircuitable) { return $validator-evaluateShortCircuit($input); } return $validator-evaluate($input); }单元测试 tests/unit/Validators/AnyOfTest.php#L71-L85 明确验证了这一点当第一个 Stub 通过、后两个 Stub 被标记为daze()若被调用即失败时evaluateShortCircuit返回通过且stub2、stub3的inputs计数均为 0证明后两者确实没有被执行public function shouldShortCircuitStopEvaluatingAfterFirstSuccess(): void { $stub1 new Stub(true); $stub2 Stub::daze(); $stub3 Stub::daze(); $validator new AnyOf($stub1, $stub2, $stub3); $result $validator-evaluateShortCircuit([]); self::assertTrue($result-hasPassed); self::assertCount(1, $stub1-inputs); self::assertCount(0, $stub2-inputs); self::assertCount(0, $stub3-inputs); }与ShortCircuit验证器的关系仓库中还提供了独立的 ShortCircuit 验证器src/Validators/ShortCircuit.php语义类似 PHP 的按顺序求值遇到第一个失败即停止返回第一个失败者的结果全部通过则返回最后一个验证器的结果。ShortCircuit同样使用CanEvaluateShortCircuittrait 分发短路求值因此当AnyOf被ShortCircuit包裹时短路语义可以穿透到AnyOf内部。功能测试 tests/feature/Validators/AnyOfTest.php#L84-L114 覆盖了这种嵌套场景// AnyOf 被 ShortCircuit 包裹短路在第一个 AnyOf 命中处停止 v::shortCircuit(v::stringType(), v::anyOf(v::intType(), v::negative()))-assert(-5); // 失败信息只含 stringType-5 must be a string选择建议当只关心至少一个规则通过且希望失败时收集所有子验证器的错误信息使用AnyOf配合assert()当希望一旦命中即停止、只保留第一个失败信息可在外层包上ShortCircuit或直接使用check()ValidatorBuilder内部即基于ShortCircuit实现快速失败。四、消息模板与占位符AnyOf通过Template属性声明了标准模板见 src/Validators/AnyOf.php#L29-L32#[Template( {{subject}} must pass at least one of the rules, {{subject}} must pass at least one of the rules, )]AnyOf::TEMPLATE_STANDARD模式模板内容default{{subject}} must pass at least one of the rulesinverted{{subject}} must pass at least one of the rules注意default与inverted两种模式使用相同的模板文案第二个参数即反模式下的文案。两种模式的区分体现在内部子验证器上——反模式如外层包裹v::not()下子验证器本身的错误消息会被取反。模板占位符占位符说明subject被验证的输入值若为验证器指定了自定义名称则显示该名称。例如功能测试 tests/feature/Validators/AnyOfTest.php#L12-L26 中对string断言v::anyOf(v::intType(), v::negative())失败时完整消息为- string must pass at least one of the rules - string must be an integer - string must be a negative numbergetMessages()的结果以验证器名为键[ __root__ string must pass at least one of the rules, intType string must be an integer, negative string must be a negative number, ]可见父级消息使用标准模板而每个子验证器贡献各自独立的失败消息便于定位具体是哪一条规则未通过。五、反模式与v::not()组合实现全部不满足当AnyOf被v::not()包裹时语义变为输入必须不满足其中任意一条规则即所有子规则都必须失败。功能测试 tests/feature/Validators/AnyOfTest.php#L28-L58 演示了这一点v::not(v::anyOf(v::intType(), v::negative()))-assert(-1); // 失败消息 // - -1 must pass at least one of the rules // - -1 must not be an integer // - -1 must not be a negative number由于-1既是整数又是负数满足了AnyOfnot取反后整体失败且子验证器的消息被反转为must not be ...。若子验证器中只有一个失败例如v::not(v::anyOf(v::intType(), v::negative(), v::stringType()))-assert(-1)反模式下的AnyOf也只展示实际失败的子验证器消息此处为intType与negative两条通过的stringType不会出现在消息中。六、与其他验证器的组合与对比AnyOf归类为Composite复合与Nesting嵌套型验证器常与以下验证器配合使用AllOf逻辑与要求所有内部验证器都通过它与AnyOf互为对偶。注意AllOf会求值全部子验证器并收集所有失败而ShortCircuit是它的快速失败版本。NoneOf要求全部内部验证器都失败与not(anyOf(...))语义接近但作为独立验证器存在。OneOf要求恰好一个内部验证器通过异或语义。ContainsAny针对字符串/数组包含任意一个指定元素的判定适合元素级而非验证器级的 OR 需求。ShortCircuit如上文所述提供式的快速失败求值。When条件式验证可根据前置条件决定应用哪组规则。嵌套组合示例复合验证器天然可以嵌套实现复杂的业务校验逻辑// 输入必须是AB 组合 或 C 单独通过 v::anyOf( v::allOf(v::stringType(), v::length(3, 10)), // A字符串且长度 3-10 v::intVal(), // B整数 v::boolVal(), // C布尔值 )-assert(abc); // 输入必须同时满足整数字段在 1-100且 (字符串 admin 或 guest) v::allOf( v::intVal()-between(1, 100), v::anyOf(v::equals(admin), v::equals(guest)), );七、版本变更与迁移注意文档 Changelog 记录了AnyOf的演进版本说明3.0.0要求至少定义两个验证器Required at least two validators to be defined2.0.0创建Created若你从 2.x 迁移到 3.x参考 migrating-from-v2-to-v3.md需重点检查所有v::anyOf()调用必须传入两个及以上验证器否则将因构造函数签名约束而报错。此外 3.x 中错误消息体系Template、{{subject}}占位符、getMessages()返回结构与 2.x 存在差异建议以当前仓库的 docs/validators 各文档为准。总结语义AnyOf是 OR 逻辑的复合验证器至少一个子验证器通过即整体通过。性能普通evaluate()求值全部子验证器并聚合所有结果evaluateShortCircuit()按序短路命中即停。消息标准模板为{{subject}} must pass at least one of the rulesdefault与inverted文案一致子验证器消息逐条保留。组合可嵌套AllOf、Not、ShortCircuit、When等适合多格式容忍多条件命中类校验场景。约束3.0.0 起必须至少传入两个子验证器。深入阅读源码可从 src/Validators/AnyOf.php 与 src/Validators/Core/LogicalComposite.php 开始测试覆盖见 tests/unit/Validators/AnyOfTest.php 与 tests/feature/Validators/AnyOfTest.php。赞分享后端开发工具【免费下载链接】ValidationThe most awesome validation engine ever created for PHP项目地址https://gitcode.com/gh_mirrors/va/Validation点击查看免费下载相关推荐没有VR头显也能看3D视频VR-Reversal让普通电脑免费实现3D转2D没有VR头显也能看3D视频VR Reversal让普通电脑免费实现3D转2D 手机里、硬盘上囤了不少3D侧边并排格式的VR视频可自己并没有VR头显打开后在后端开发工具Watchman 查询表达式 anyof 术语详解语法、短路求值与源码实现Watchman 查询表达式 anyof 术语详解语法、短路求值与源码实现 本文基于 Watchman 官方文档 anyof 表达式术语 https://li后端开发工具Respect\Validation 的 Equals 验证器宽松相等比较、消息模板与组合用法详解Respect\Validation 的 Equals 验证器宽松相等比较、消息模板与组合用法详解 本篇技术指南聚焦于 Respect\Validation后端开发工具上一篇TTS Server 文字转语音服务器使用指南下一篇终极指南TypeScript如何赋能wenyan-lang文言文编程语言的编译器开发创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考