PHP与前端共用一套校验规则,我的实践方案
先说说我踩过的坑
前后端校验脱节的问题,每个做全栈的朋友应该都深有体会。后端写了一套 `checkEmail()`、`validateAge()`,前端为了用户体验又用 jQuery Validate 或者 Element UI 的规则重新写一遍。两套逻辑肉眼可见地相同,却散落在不同仓库里,某天产品经理说“邮箱格式再加个限制”,你改了后端却忘了前端,线上立刻出现诡异报错。
更折磨人的是,前端校验说“通过”了,后端提交上去却提示“参数错误”。用户瞬间觉得你们系统是个半成品。这种割裂感持续了大半年,直到我实在受不了了,开始琢磨怎么让 PHP 和 JavaScript 共用一套校验逻辑。
为什么不用 JSON Schema 或现成库
先说结论:JSON Schema 很强,但太重了。我们项目的接口有几十种场景,每种场景有嵌套对象、数组元素、条件必填,JSON Schema 描述起来复杂度完全不亚于写代码,而且 PHP 和 JS 两端需要引入的库体积都不小。
我也考虑过像 `laravel-validator` 转 JS 的桥接包,但它们的维护活跃度参差不齐,遇到自定义规则还要自己造轮子,最终放弃了。
我的核心思路很简单:**把校验规则抽象成纯数据,用一套可序列化的 DSL 描述,PHP 和前端各自实现一个轻量解释器**。
规则怎么描述
我把常见的校验逻辑收敛成几个原子操作,用数组/对象表示,JSON 天然可传输。比如:
$rules = [
'username' => [
['required', 'message' => '用户名必填'],
['min', 'value' => 2, 'message' => '至少2个字符'],
['max', 'value' => 20, 'message' => '最多20个字符'],
],
'email' => [
['required', 'message' => '邮箱必填'],
['email', 'message' => '邮箱格式不正确'],
],
'age' => [
['integer', 'message' => '年龄必须是数字'],
['min', 'value' => 18, 'message' => '未满18岁'],
],
// 条件规则:当 password 存在时,confirm_password 必须匹配
'confirm_password' => [
['requiredIf', 'field' => 'password', 'message' => '请确认密码'],
['same', 'field' => 'password', 'message' => '两次密码不一致'],
],
];
前端拿到这份 JSON 后,通过一个不到 200 行的解释器逐条执行即可,后端则写一个对应的 PHP 解析类。两边看到的规则描述完全一致,不存在“翻译”过程,自然就不会出现偏差。
PHP 端的实现要点
服务端我用一个 `Validator` 类来做解析,核心方法大概是:
public function validate(array $data, array $rules): array
{
$errors = [];
foreach ($rules as $field => $ruleList) {
foreach ($ruleList as $rule) {
[$name] = $rule;
$params = array_merge(['field' => $field], $rule);
$result = $this->callRule($name, $data, $params);
if (!$result) {
$errors[$field][] = $rule['message'] ?? "$field 校验失败";
break;
}
}
}
return $errors;
}
每个规则比如 `required`、`min`、`email` 都是一个独立的小方法,便于扩展。比如我后来加了一个 `phone_cn` 规则,前后端各写十几行正则调用就同步好了。
前端解释器怎么设计
前端我用一个轻量函数 `validateData(data, rules)`,同样遍历规则并逐个触发对应验证函数,因为规则本身就是 JSON,在 Vue/React 里可以直接塞给表单组件:
const errors = validateData(formData.value, userRules);
if (Object.keys(errors).length) {
// 展示错误提示,而不需要手动为每个字段写规则
}
这种方式也天然适配了动态表单——后端接口返回什么规则,前端就渲染什么校验,做配置化表单特别舒服。
分享两个值得注意的细节
第一点,正则表达式要统一写法。 PHP 和 JS 的正则语法绝大部分互通,但注意 `/` 转义、字符类的差异,这类问题我建议把正则单独抽出来作为配置项,而不是嵌在校验函数里。
第二点,错误信息的“位置”要一致。 前端在 blur 时就提示,后端在整体提交时返回,需要双方约定按字段名收集错误、按顺序渲染,否则用户看到的错误顺序会不一样,又会觉得“这俩系统不对”。
总结一下我的实践感受
从引入这套 DSL 方案到现在已经快半年,最大的收获是“改规则只改一处”的安心感。新增一个字段或修改约束时,PHP 里写好规则,前端 JSON 同步更新(其实可以通过接口下发),测试成本大大降低。虽然初期搭解释器花了点时间,但长期来看绝对值得。
如果你的项目也是前后端分离、接口数量多、校验频繁变动,不妨试试这种思路。纯配置化、双端解释执行、按需扩展自定义规则,一套规则走天下。
年卡会员