应采用策略模式解耦PHP表单验证逻辑,因多字段组合校验(如唯一性、跨字段匹配、状态检查)无法由filter_var或正则单独完成,硬编码导致函数臃肿、复用低、维护难;需定义ValidatorInterface接口,各策略类单一职责、依赖注入、通过$context传参,配置驱动执行与错误收集,并注意空值处理、Unicode正则、密码强度拆分及上下文共享等细节。

直接用策略模式处理PHP表单验证,不是为了“设计模式而模式”,而是当验证规则开始出现分支多、复用低、改一个字段要动七八处 if 时,就该拆了。
为什么 filter_var + preg_match 还是越写越乱?
内置函数和正则能解决单点验证,但一到多字段组合场景就露馅:比如「手机号必须唯一」要查库、「密码确认必须匹配」要跨字段比对、「邮箱注册后禁用重发」要查状态——这些逻辑没法塞进 FILTER_VALIDATE_EMAIL 或一个 preg_match() 里。硬塞进去的结果就是验证函数越来越胖,validateUserForm() 里混着数据库查询、缓存检查、HTTP调用,改个提示文案都要测全链路。
常见错误现象:
- 同一套验证逻辑在注册、编辑、重置密码三处重复 copy-paste
- 加个「用户名不能含敏感词」需求,得改 4 个地方的 if 判断
- 单元测试只能整块跑,没法单独测「手机号格式校验」是否还有效
怎样把每个验证规则变成可插拔的类?
核心就一条:每个策略类只做一件事,且实现统一接口。不要让策略类自己去拿 $_POST,传进去什么,它就验什么。
立即学习“PHP免费学习笔记(深入)”;
实操建议:
- 定义接口
ValidatorInterface,强制所有策略实现isValid($value, $context = [])方法 - 每个策略类不依赖全局变量,比如
EmailValidator只管格式,UniqueEmailValidator才负责查数据库 - 上下文(
$context)用来传额外数据:如当前用户ID(避免自检重复)、数据库连接实例、请求参数全集 - 示例:
class EmailValidator implements ValidatorInterface { public function isValid($value, $context = []): bool { return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; } } class UniqueEmailValidator implements ValidatorInterface { private $pdo; public function __construct(PDO $pdo) { $this->pdo = $pdo; } public function isValid($value, $context = []): bool { $stmt = $this->pdo->prepare('SELECT 1 FROM users WHERE email = ? AND id != ?'); $stmt->execute([$value, $context['exclude_id'] ?? 0]); return $stmt->fetch() === false; } }
怎么组织多个策略并收集错误?
别在控制器里 new 一堆 validator,用配置数组驱动。字段名 → 策略类名 → 错误消息,三者绑定,增删改都只动配置。
使用场景:
- 表单提交后,遍历字段配置,逐个执行
$validator->isValid($value, $context) - 任一策略返回
false,就把预设的错误消息推入错误栈,不中断后续字段验证 - 错误消息支持占位符,比如
"邮箱 {{ value }} 已被注册",替换时再填真实值,避免拼接出 XSS 风险 - 注意:数据库类策略(如唯一性检查)要共用同一个 PDO 实例,否则事务或连接池会出问题
哪些地方最容易踩坑?
策略模式本身简单,但落地时几个细节常被忽略:
-
filter_var()对空字符串返回false,但有些业务要求「允许为空」,策略类里得显式判断empty($value)再决定是否跳过验证 - 正则策略若没加
u修饰符,中文姓名验证/^[\x{4e00}-\x{9fa5}]+$/会失效 - 密码强度策略如果只用一个
preg_match(),很难覆盖「至少含大小写字母+数字+特殊字符」的组合逻辑,建议拆成多个轻量策略(HasUppercaseValidator、HasNumberValidator)并行执行 - 别把策略类塞进
__autoload或手动 require,用 Composer 的自动加载,否则测试时 mock 不了
真正麻烦的从来不是写十个策略类,而是让它们共享上下文又互不感知——PDO 实例、用户会话、请求参数,这些怎么传才不污染策略职责,才是需要反复调的点。



















