- 后端
- 开发工具
【免费下载链接】Validation
The most awesome validation engine ever created for PHP
葡萄牙的税务实体(个人或公司)都有一个唯一的企业/纳税人识别号 NIF(Número de Identificação Fiscal),在各类业务系统中校验其合法性是常见需求。本文基于 Respect Validation 仓库中的PortugueseNif验证器,讲解它的调用方式、底层校验算法(前导数字区间 + 模 11 校验位)、失败消息模板机制,以及在链式与面向对象 API 中的完整用法。
PortugueseNif 验证器是什么
PortugueseNif()是 Respect Validation 中用于校验葡萄牙 NIF 号码的验证器,官方文档将其归类为Identifications(身份识别类)验证器,定义见 PortugueseNif 文档。
NIF 是一个9 位十进制数字,前 8 位为实体编码,最后 1 位为校验位(check digit)。该验证器只接受恰好 9 位、纯数字且通过校验位运算的字符串,因此能有效拦截格式错误、类型错误和校验位错误的输入。
最简单的用法如下(验证器文档中的官方示例):
v::portugueseNif()->assert('124885446'); // Validation passes successfully v::portugueseNif()->assert('220005245'); // → "220005245" must be a Portuguese NIF第一个 NIF 合法,断言通过;第二个 NIF 校验位错误,抛出验证异常,并生成"220005245" must be a Portuguese NIF的失败消息。
底层校验算法:从源码看规则细节
PortugueseNif的实现位于 src/Validators/PortugueseNif.php,校验分三个阶段进行。
1. 格式与类型检查
if (!is_string($input)) { return false; } if (!is_numeric($input)) { return false; } if (strlen($input) != 9) { return false; }- 输入必须是字符串:数字、布尔值、数组、对象、资源一律判为无效;
- 必须是数字型字符串(可通过
is_numeric判定); - 长度必须恰好为 9 位。
这一点也被单元测试 tests/unit/Validators/PortugueseNifTest.php 中的"Invalid formats / Weird types"用例证实:'29698107'(8 位)、'3726972165'(10 位)、'ABC885446'(含字母)、以及[]、true、1、0.5、null、stdClass、stream_context_create()等非字符串类型都会被拒绝。
2. 前两位数字的区间约束
源码对第一位数字$digits[0]做了switch分支,对不同前缀要求第二位$digits[1]落在特定区间:
| 第一位 | 允许的第二位 | 含义(按葡萄牙 NIF 前缀规则) |
|---|---|---|
| 4 | 5 | 约 1995 年后出生的个人 |
| 7 | 0、1、2、4、5、7、8、9 | 公司实体(除 3 之外的 70~79 段) |
| 9 | 0、1、8、9 | 临时/非常规实体等 90 段号码 |
| 其他 | 任意 | 不做前缀约束,直接进入校验位计算 |
可以看出:以45开头、70/71/72/74/75/77/78/79开头、以及90/91/98/99开头的号码会走前缀校验,其余前缀(如测试用例中的12...、22...、38...)则不在此层拦截,统一交给校验位计算决定合法性。
3. 模 11 校验位计算
去掉最后一位作为校验位$checkDigit后,对前 8 位按权重(9 - position)加权求和(position 从 0 开始,即第一位权重 9、最后一位权重 2),然后取模 11:
$sumTerms = array_map(static fn(int $digit, int $position) => $digit * (9 - $position), $digits, $digitKeys); $sum = array_sum($sumTerms); $modulus = $sum % 11; if ($modulus == 0 || $modulus == 1) { return $checkDigit == 0; } return $checkDigit == 11 - $modulus;- 当模 11 结果为 0 或 1 时,校验位必须为
0; - 否则校验位必须等于
11 - $modulus(取值范围 2~10,其中 10 不会出现,因为 NIF 只使用 0~9 的数字作为校验位)。
这一算法与葡萄牙 NIF 官方校验规则一致:文档示例'124885446'通过校验,而'220005245'因校验位不符被拒绝。
失败消息与模板机制
验证失败时,PortugueseNif通过Template属性(定义见 src/Message/Template.php)生成可读消息。该类上声明了两个模板(src/Validators/PortugueseNif.php):
#[Template( '{{subject}} must be a Portuguese NIF', '{{subject}} must not be a Portuguese NIF', )]对应 PortugueseNif 文档 中的模板表:
| 模式 | 模板内容 |
|---|---|
default | {{subject}} must be a Portuguese NIF |
inverted | {{subject}} must not be a Portuguese NIF |
模板占位符{{subject}}表示被校验的输入值,或在链式用法中指定的自定义验证器名称。默认与反转(inverted)两种模式分别对应正向断言与取反断言(如notPortugueseNif()),具体转换机制可参考 消息占位符转换说明 与 占位符管道说明。
在链式 API 中使用
PortugueseNif与项目 Mixin 体系深度集成,v::portugueseNif()方法声明于 src/Mixins/Builder.php,实际返回Chain链。除了基础用法,还提供一系列派生方法:
allPortugueseNif()(src/Mixins/AllBuilder.php):对可迭代输入的每个元素都执行 NIF 校验;nullOrPortugueseNif()(src/Mixins/NullOrBuilder.php):输入为null时放行,否则校验;notPortugueseNif()(src/Mixins/NotBuilder.php):取反校验;undefOrPortugueseNif()(src/Mixins/UndefOrBuilder.php):未定义值放行,否则校验;keyPortugueseNif(int|string $key)(src/Mixins/KeyBuilder.php):校验数组/对象中指定键的值;propertyPortugueseNif(string $propertyName)(src/Mixins/PropertyBuilder.php):校验对象属性。
典型场景——校验请求 payload 中的nif字段:
v::key('nif', v::portugueseNif())->assert([ 'nif' => '124885446', ]);面向对象方式与重复属性
PortugueseNif是一个final class,继承自 src/Validators/Core/Simple.php,只需实现isValid(mixed $input): bool,evaluate()会自动把结果包装成Result(见 src/Result.php)。同时它声明了 PHP 属性(Attribute):
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]这意味着它可以作为可重复的属性注解直接标注在类的属性上,用于对象属性校验场景:
use Respect\Validation\Validators\PortugueseNif; final class Customer { #[PortugueseNif] public string $nif = ''; }测试覆盖与可验证性
仓库为PortugueseNif提供了完整的单元测试(tests/unit/Validators/PortugueseNifTest.php),包含 19 个合法样本(如124885446、296981079、709060548、990402509等)和 22 个非法样本(校验位错误、长度错误、含字母、各类非字符串类型)。这些用例直接印证了上文所述的算法行为,你也可以在本地用 PHPUnit 复现:
vendor/bin/phpunit tests/unit/Validators/PortugueseNifTest.php与其他欧洲纳税人识别号验证器的区别
在 See Also 列表中(见 PortugueseNif 文档),PortugueseNif与同类的欧洲身份识别验证器并列:
| 验证器 | 适用国家/地区 | 文档 |
|---|---|---|
| Bsn | 荷兰(公民服务号) | Bsn.md |
| Cnh | 巴西驾驶证号 | Cnh.md |
| Cnpj | 巴西公司税号 | Cnpj.md |
| Cpf | 巴西个人税号 | Cpf.md |
| Hetu | 芬兰个人身份代码 | Hetu.md |
| Nif | 西班牙 NIF(字母结尾) | Nif.md |
特别注意:西班牙的 Nif 是字母结尾(如49294492H),而葡萄牙 NIF 是纯 9 位数字,两者算法完全不同,选择验证器时不要混淆。
版本演进
根据 PortugueseNif 文档 的 Changelog,该验证器自2.2.0版本引入。与它形成对照的是 Nif 在 3.0.0 经历了"Templates changed"的消息模板改造,这也提醒你在升级项目版本时,留意验证失败消息文本是否发生变化(相关迁移说明可参考 从 v2 迁移到 v3)。
小结
PortugueseNif是 Respect Validation 中一个实现清晰、测试完备的国别化验证器:先做"字符串 + 9 位数字"的格式门槛,再做前缀区间约束,最后用模 11 加权校验位收口,三层规则共同保证只有真实合法的葡萄牙 NIF 才能通过。无论是通过v::portugueseNif()链式调用,还是作为#[PortugueseNif]属性注解,都能以最小成本把葡萄牙纳税人识别号的合法性校验引入到你的业务系统中。
- 后端
- 开发工具
【免费下载链接】Validation
The most awesome validation engine ever created for PHP
相关推荐
Chance.js 生成巴西 CPF 纳税人识别号:chance.cpf() 用法与校验位算法全解析
Chance.js 生成巴西 CPF 纳税人识别号:chance.cpf 用法与校验位算法全解析 CPF(Cadastro de Pessoas Físicas
测试G-Helper 完整指南:十分钟全面接管华硕笔记本性能控制
G Helper 完整指南:十分钟全面接管华硕笔记本性能控制 上周二下午开会,放在桌上的华硕笔记本突然开始发出类似吹风机的声响。我凑近一看:Turbo 性能模式
后端开发工具MNN Chat 端侧多模态大模型实战指南:手机本地跑通对话、视觉与文生图
MNN Chat 端侧多模态大模型实战指南:手机本地跑通对话、视觉与文生图 想在手机上离线跑一个 7B 多模态模型——图像输入、语音识别、文生图全部本地完成,一
后端开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考