acme_tracking:AC 这样的字符串规则,需要将验证逻辑注册到验证的 Factory 中。这样,使用方应用的表单请求就可以用与内置规则相同的写法来使用它。
本页基于 Laravel Framework v13.40.0 的官方实现。它补充了自定义验证规则中的基础示例,讨论在将其作为扩展包的公开 API 维护时的注册、翻译以及状态的生命周期。
与规则对象区分使用
官方文档介绍的ValidationRule 对象同样可以在扩展包中使用。如果”能够以字符串形式指定”本身并不是需求,可以考虑使用能在构造函数中接收类型化参数的对象。
即使方法名同样是
validate(),调用约定也不同。如果将 ValidationRule 的实现类直接作为类字符串传给 Factory::extend(),第 3 个参数的类型和返回值都不匹配。
区分注册、创建和验证
Validator 门面的 extend() 是对 Illuminate\Validation\Factory 的调用。它与验证输入的 Illuminate\Validation\Validator 实例是不同的对象。
上图展示的是字符串规则被执行时的路径。因未填写或 nullable 等原因而跳过的情况将在后文说明。
Factory 以 validator 为键注册为 singleton,但标准实现的 make() 每次返回的都是新创建的 Validator。之后再向 Factory 注册的内容,不会更新已创建的 Validator 的扩展数组。
注册扩展包专属的规则
作为示例,我们验证一种由大写英文字母前缀、连字符和 8 位数字组成的虚构追踪码。AC-00000001 只是格式示例,并非真实快递公司的单号规范。
假设扩展包的命名空间 Acme\Courier\ 通过 Composer 的 PSR-4 映射到 src/,并且作为普通的非延迟 Service Provider 加载。关于 Provider 的加载,请参阅扩展包自动发现。
将验证逻辑拆分到类中
在src/Validation/TrackingCode.php 中准备一个用于字符串规则的处理类。该类不实现 ValidationRule 接口。
acme_tracking:AC,参数数组为 ['AC']。规则内部也会检查值的类型,这样即使单独使用该规则也不会产生类型错误。
不合法的前缀或参数数量不是用户的输入错误,而是被视为规则定义的配置错误并抛出异常。当规则因空值而被跳过时,这一配置检查也不会执行,因此还需要使用合法的非空值进行测试。
在 Factory 解析后注册
在src/CourierServiceProvider.php 的 boot() 中注册规则和消息替换处理。
callAfterResolving() 在 Factory 尚未解析时会等待其创建,而对于已经解析的 Factory 则会立即注册。关于该钩子本身的详细信息,请参阅扩展包的服务解析后钩子。
类字符串 TrackingCode::class.'@validate' 会在验证时通过容器解析该类并调用方法。即使省略 @validate 只写类名,默认的方法名也是 validate。如果存在构造函数依赖,请进行绑定,确保容器能够解析。
本示例没有以 singleton 方式注册处理类,也没有设置保存输入的属性。验证时的数据通过参数处理,设计上不要把请求特有的状态捕获进 Factory 所持有的闭包中。
在使用方应用中指定
bail 会在该属性的前置规则失败时停止后续规则。必填性交给 required,格式交给 acme_tracking,以此划分职责。
公开的名称统一使用像 acme_tracking 这样独有的 snake case。对同一类扩展以相同名称注册时,后注册的会覆盖之前的。另外,如果存在内置的 validateRequired() 等方法,会直接调用该方法,因此无法用同名扩展替换内置验证。
保留用户的错误消息
普通字符串规则的消息大致按以下优先级选择。尺寸规则等特殊分支不在此讨论范围内。
Laravel 替换
:attribute 等占位符之后,才会调用自定义 replacer。replacer 的参数有 5 个:已选择的消息、属性名、规则名、参数数组和 Validator。前面的示例省略了未使用的第 5 个参数。
要在应用侧指定日语的默认文本,可以在现有 lang/ja/validation.php 的返回数组中添加 acme_tracking。下面的示例是要添加的条目示例,并不会删除其他内置消息。
__('...') 并保存为 fallback,Factory 中保留的将是当时区域设置下的字符串。在会切换区域设置的应用中,应使用生成错误时的翻译。
使用扩展包的命名空间翻译时
在boot() 中调用 loadTranslationsFrom(__DIR__.'/../lang', 'acme-courier') 后,就可以通过 acme-courier::validation.tracking_code 引用扩展包中的 lang/ja/validation.php 等文件。
但是,仅将翻译键传给 extend() 的第 3 个参数并不会自动翻译。 Factory 只会把它当作普通的 fallback 字符串保存。将前面钩子中的 extend() 和 replacer() 替换为下面的注册方式后,只有在选中 fallback 时才会获取扩展包的翻译。
validation.php 中准备 tracking_code 条目。日语文本可以与前面的 acme_tracking 条目相同。
这里由于在 replacer 中获取了新的翻译字符串,因此也显式地把 :attribute 传给了翻译。Laravel 的标准替换不会再次应用于这个返回值。如果选中的是用户的自定义消息,则保持该文本不变。关于分发翻译的覆盖,请参阅扩展包翻译的覆盖与更新。
理解 Implicit 与 nullable 的执行条件
extendImplicit() 注册的规则,即使属性缺失或为空字符串也会成为验证候选。它并不意味着”一定失败”或”忽略所有其他跳过条件”。
上表比较的是各规则的执行条件。因
bail、属性排除、stopOnFirstFailure() 等原因而提前停止处理的情况另当别论。在 HTTP 输入中,空字符串可能会被中间件转换为 null,因此请分别测试空字符串和 null。
本例中的格式规则,使用普通的 extend() 与 required 组合就足够了。将已公开的规则改为 Implicit 后,以前不会被验证的输入可能会失败,因此应将其视为兼容性变更。
使用 Dependent 规则解析数组中的引用目标
对于以另一个字段名作为参数的规则,可以使用extendDependent()。在验证 items.*.confirmed_tracking 时,参数中的 items.*.tracking 会被转换为同一行的 items.0.tracking 等。普通的 extend() 不会进行这种替换。
下面是用于确认这一机制的匹配规则。如果只是验证简单的相等,实际应用中可以使用内置的 same。请将其视为应用于扩展包特有的跨字段验证时的示例。
将其添加到从钩子中接收到的 Factory。
getData() 返回的是验证对象的数据,而不仅仅是已验证的数据。请为引用方和被引用方都定义所需的规则。
此外,注册为 Dependent 并不会使其成为 Implicit。针对缺失或空值的必填性需要另行指定。由于实现会对参数进行点号转义处理和 * 替换,因此应避免设计在字段名之外混入任意字符串的 API。
在应用中测试从注册到验证的全过程
在加载了扩展包和 Service Provider 的 Laravel 13 应用中,创建tests/Feature/TrackingRuleTest.php。除了类的单元测试外,还要通过实际的 Factory 验证注册和消息处理。
长期维护时需要确认的事项
- 将规则名、参数的含义与数量、必填性以及可接受的值类型作为公开 API 记录下来。
- 验证不合法的参数,以及缺失、空字符串、
null与nullable、sometimes、bail的各种组合。 - 确认用户的
messages()、属性显示名、区域设置切换以及扩展包翻译的覆盖。 - 对于 Dependent 规则,使用多行和嵌套数组确认不会引用其他行的值。
- 确认向未解析和已解析的 Factory 注册的行为,不要期望注册内容会追溯反映到已创建的 Validator。
- 避免与多个扩展包发生名称冲突,不要将同一名称混用于普通、Implicit 和 Dependent 注册。
- 将变更反映到长生命周期 worker 时要重启进程,不要在共享的处理类中残留输入或用户信息。
参考的一手资料
- Laravel 官方文档:自定义规则与 Implicit 规则
- Laravel 官方文档:扩展包的 Service Provider 与翻译
- Factory:make、extend、extendImplicit、extendDependent、replacer
- Validator:执行条件、扩展注册、类的解析
- ValidationRuleParser:字符串规则的解析
- FormatsMessages:消息的优先级与替换
- ValidationServiceProvider:validator 的 singleton 注册
- ServiceProvider:callAfterResolving
- 官方测试:从 Factory 传递扩展与 fallback
- 官方测试:字符串、类、Implicit、Dependent 规则