Symfony Validator 组件 CHANGELOG 全解读:从 2.1 到 8.2 的校验能力演进与实践指南
Symfony Validator 组件 CHANGELOG 全解读:从 2.1 到 8.2 的校验能力演进与实践指南
本篇文章以 src/Symfony/Component/Validator/CHANGELOG.md 为主线,系统梳理 Symfony Validator 组件从 2.1 到 8.2 版本的演进脉络:新增了哪些约束、验证组机制如何发展、自定义约束的编写方式经历了怎样的重构,以及 8.0 带来了哪些破坏性变更。读完本文,你将能够依据版本特性选择正确的约束与配置方式,理解 GroupSequence、Valid 级联、命名参数构造器等核心机制,并能结合仓库源码验证每一项能力的真实实现。
一、Validator 组件是什么
Symfony Validator 组件用于依据 JSR-303 Bean Validation 规范对值进行校验,是 Symfony 全栈框架中表单验证、API 入参校验、领域模型约束的核心引擎。它提供三件核心能力:约束(Constraint)——描述"值必须满足什么规则";校验器(Validator)——执行规则并产出 ConstraintViolation 违规对象列表;元数据加载器(Mapping/Loader)——从属性(Attribute)、YAML、XML 配置中读取约束声明。
组件根目录包含 Constraint.php(约束基类)、ValidatorBuilder.php(构建器)、Validation.php(静态入口),以及存放全部内置约束实现的 Constraints/ 目录(截至 8.2 有 100 余个约束类)。
二、8.2:新增媒体校验与验证组级联增强
2.1 音频文件校验:Audio 约束
8.2 新增 Audio 约束,用于校验音频文件。从源码 Audio.php 可见,它继承自 File,因此天然拥有文件存在性、大小、MIME 类型、文件名长度等全部 File 能力,同时新增音频专属维度:
minDuration/maxDuration:最小/最大时长(秒),错误码TOO_SHORT_ERROR/TOO_LONG_ERROR;minBitrate/maxBitrate:最小/最大比特率(bps),错误码BITRATE_TOO_LOW_ERROR/BITRATE_TOO_HIGH_ERROR;allowedSampleRates:允许的采样率(Hz);minChannels/maxChannels:声道数量上下限;allowedCodecs/allowedContainers:允许的编码格式与容器格式;- 默认
mimeTypes = 'audio/*',消息默认值为'This file is not a valid audio file.'。
use Symfony\Component\Validator\Constraints as Assert;
class PodcastUpload
{
#[Assert\Audio(
maxSize: '20M',
minDuration: 30,
maxDuration: 3600,
allowedContainers: ['mp3', 'm4a'],
)]
public $file;
}
注意:该类定义了 16 个专属错误常量(如 CORRUPTED_AUDIO_ERROR、NO_AUDIO_STREAM_ERROR、MULTIPLE_AUDIO_STREAMS_ERROR 等),说明其校验逻辑依赖底层媒体解析能力,属于"尽力检测型"校验。
2.2 cron 表达式校验:Cron 约束
Cron 约束用于校验 cron 表达式,源码 Cron.php 显示它支持三种模式,通过 mode 选项选择,默认 MODE_ALIASES:
| 模式 | 常量 | 说明 |
|---|---|---|
standard |
Cron::MODE_STANDARD |
标准五段式 cron 表达式 |
aliases |
Cron::MODE_ALIASES |
额外支持 @daily、@weekly 等别名(默认) |
hashed |
Cron::MODE_HASHED |
支持哈希 cron 表达式 |
#[Assert\Cron(mode: 'standard')]
public string $schedule;
使用前提:该约束依赖 dragonmantank/cron-expression 包。源码在构造器中显式检查 class_exists(CronExpression::class),未安装时抛出 LogicException 并提示执行 composer require dragonmantank/cron-expression。如果 mode 不在三种模式之列,则抛出 ConstraintDefinitionException。
2.3 ValidationBundle 与配置迁移
8.2 新增 ValidationBundle(见 ValidationBundle.php),提供 validation 配置及此前由 FrameworkBundle 在 framework.validation 下提供的服务。同时抽出两个此前由 FrameworkBundle 提供的类:
- CacheWarmer/ValidatorCacheWarmer.php:校验元数据缓存预热;
- DependencyInjection/AddValidatorSecurityExpressionLanguageProviderPass.php:向校验器使用的表达式语言注册安全相关 Provider。
这标志着校验组件的 DI 集成开始从 FrameworkBundle 中解耦,独立成可复用的 Bundle。
2.4 验证组序列的新选项:cascadeCurrentGroup
GroupSequence 与 GroupSequenceProvider 新增 cascadeCurrentGroup 选项。源码 GroupSequence.php 的注释解释了设计动机:当类声明了组序列(Default 组被序列覆盖)时,被 Valid 约束引用的对象默认只在 Default 组中级联,因此注册在序列组(如 Strict)上的约束永远不会被执行。
设置 cascadeCurrentGroup = true 后,校验器会把"当前正在执行的序列组"与 Default 组一起级联到引用对象,使这些约束得以生效:
use Symfony\Component\Validator\Constraints as Assert;
#[Assert\GroupSequence(['Basic', 'Strict'], cascadeCurrentGroup: true)]
class Order
{
// ...
}
注意该标志只作用于"类上声明的序列";当序列由调用方显式传给 validate() 时,被级联的本来就是当前序列组,无需此选项。
同时,8.2 移除了不再使用的 GroupSequence::$cascadedGroup 属性,并允许在 GroupSequence 定义中直接书写被注解类的 ::class 常量。
2.5 Valid 约束的 restrictGroups 选项
Valid 约束新增 restrictGroups 选项(默认 true)。源码 Valid.php 中参数注释说明:为 true 时,Valid 上配置的 groups 同时限定嵌套对象使用哪些组进行校验;为 false 时,groups 只决定"何时触发级联",嵌套对象实际按当前正在校验的组执行:
#[Assert\Valid(groups: ['api'], restrictGroups: false)]
public Address $address;
配合 8.2 的另一项改动——允许在 Sequentially 中嵌套无组的 Valid 约束,实现"仅当前面约束全部通过后才执行级联校验"的效果:
#[Assert\Sequentially([
new Assert\NotBlank(),
new Assert\Valid(),
])]
public $payload;
2.6 Callback 约束的 message 选项与布尔回调
Callback 约束新增 message 选项(源码 Callback.php):当设置了 message 时,回调必须返回布尔值,返回 false 即自动抛出以 message 为内容的违规(错误码 NOT_SATISFIED_ERROR);不设置 message 时,返回值被忽略,回调应自行通过执行上下文构建违规:
#[Assert\Callback(
static function (\DateTimeImmutable $value) {
return $value > new \DateTimeImmutable('today');
},
message: 'The delivery date must be in the future.',
)]
public \DateTimeImmutable $deliveryDate;
2.7 其他 8.2 增强
ValidationFailedException增加#[WithHttpStatus(422)],未捕获时渲染为 "Unprocessable Content"(422)响应而非 500;Range及比较类约束支持BcMath\Number值;ConstraintViolationBuilderInterface::setParameter()允许传入int、float、\Stringable、\DateTimeInterface;- 当
File约束配置了extensions时,不再用"从扩展名自动推导的 MIME 类型"去收窄mimeTypes选项; Unique约束的fields选项支持读取对象属性(不再局限于数组键);Mapping\Loader\LoaderChain::__construct()新增可选参数$mappedClasses,用于声明每个 loader 映射哪些类;ValidatorBuilder::addMappedClasses() 与之配套,用于声明每个映射文件映射的类,从而让"文件级元数据"拥有可检索的类清单。
三、8.1:测试性、属性元数据与校验器接口重构
3.1 属性元数据存在性检查
ValidatorBuilder::enablePropertyMetadataExistenceCheck()(ValidatorBuilder.php)启用后,validateProperty() 与 validatePropertyValue() 在目标属性没有元数据时会直接抛异常,而不是静默返回空结果——便于在类型化、强约束的代码中尽早暴露"忘了加约束"的配置错误。
3.2 findByCodes() 与时钟感知校验
ConstraintViolationListInterface新增findByCodes(),可按一个或多个错误码在违规列表中检索;- 比较类与
Range校验器支持时钟注入(clock-aware),使"基于当前时间的比较校验"(如日期必须晚于今天)可以在测试中固定时间,保证可重复断言。
3.3 Xml 约束与校验器测试重构
- 新增
Xml约束,用于校验 XML 内容(与 7.2 的Yaml、4.3 的Json形成"格式校验"家族); - 核心接口重构:弃用
ConstraintValidatorInterface::initialize()与validate(),改用validateInContext();抽象类 ConstraintValidator.php 负责上下文管理;测试基类ConstraintValidatorTestCase提供新的validate()方法封装校验器调用方式,使编写约束校验器单元测试更简洁。
四、8.0:破坏性变更全景(升级必读)
4.1 移除 XML/YAML 隐式选项配置
8.0 彻底移除在 XML/YAML 中"隐式把 <value> 当作选项"的旧写法,必须显式声明 option name:
XML 迁移:<value> 直接书写参数 → 包裹在 <option name="callback"> 内:
<!-- Before -->
<class name="App\Entity\Order">
<constraint name="Callback">
<value>App\Validator\OrderValidator</value>
<value>validate</value>
</constraint>
</class>
<!-- After -->
<class name="App\Entity\Order">
<constraint name="Callback">
<option name="callback">
<value>App\Validator\OrderValidator</value>
<value>validate</value>
</option>
</constraint>
</class>
YAML 迁移:- Callback: validateMeStatic 的简写形式改为显式 callback: 键:
# Before
App\Entity\Order:
constraints:
- Callback: validateMeStatic
- Callback: [App\Validator\OrderValidator, validate]
# After
App\Entity\Order:
constraints:
- Callback:
callback: validateMeStatic
- Callback:
callback: [App\Validator\OrderValidator, validate]
(上述两种弃用自 7.4 开始,8.0 正式移除。)
4.2 约束构造器全面迁移到命名参数
这是 8.0 最重要、影响面最广的破坏性变更:基础 Constraint 类不再支持"传入选项数组再在内部求值",所有具体约束必须在构造函数中初始化属性,并将必填选项改为必填构造参数。旧的 getRequiredOptions()、getDefaultOption()、normalizeOptions() 方法被移除。
自定义简单约束迁移:
// Before (8.0 之前可用的旧写法)
class CustomConstraint extends Constraint
{
public $option1;
public $option2;
public function __construct(?array $options = null)
{
parent::__construct($options);
}
}
// After(8.0 推荐写法)
class CustomConstraint extends Constraint
{
public function __construct(
public $option1,
public $option2 = null,
?array $groups = null,
mixed $payload = null,
) {
parent::__construct(null, $groups, $payload);
}
}
必填选项迁移:不再用 getRequiredOptions() 声明必填项,而是直接写成无默认值的构造参数:
class CustomConstraint extends Constraint
{
public function __construct(
public $option1, // 必填
public $option2 = null,
?array $groups = null,
mixed $payload = null,
) {
parent::__construct(null, $groups, $payload);
}
}
复合约束(Composite)迁移:不再通过 getCompositeOption() 声明嵌套选项来源,而是在子类构造器中直接初始化属性:
class CustomCompositeConstraint extends Composite
{
public function __construct(
public array $constraints,
?array $groups = null,
mixed $payload = null,
) {
parent::__construct(null, $groups, $payload);
}
}
同时,All、AtLeastOneOf、CardScheme、Collection、CssColor、Expression、Regex、Sequentially、Type、When 等约束的 getRequiredOptions() / getDefaultOption() 被移除(7.4 弃用,8.0 移除)。
4.3 其他 8.0 变更
GroupSequence不再接受关联数组:
// Before
GroupSequence(['value' => ['group 1', 'group 2']]);
// After
GroupSequence(['group 1', 'group 2']);
Url约束的$requireTld默认值改为true:URL 必须包含顶级域名(此变更自 7.1 引入该选项时即预告);ClassMetadataInterface新增getGroupProvider();GenericMetadata实现由__sleep/__wakeup()迁移到__(un)serialize();- 移除
Bic::INVALID_BANK_CODE_ERROR常量(该校验器已不再使用); - 移除了基类中"求值选项"的能力,具体约束须在构造函数中初始化属性。
五、7.x:命名参数体系与格式类约束的密集扩张
5.1 7.4:为 8.0 破坏性变更铺路
7.4 集中宣布了一批弃用(弃用内容即上节 8.0 移除的各类写法),并新增两样能力:
#[ExtendsValidationFor]:为某个类声明"额外的"校验约束,实现跨类扩展约束而无需修改目标类;ValidatorBuilder::addAttributeMappings()与AttributeMetadataPass:在编译期用属性声明约束元数据;Video约束:校验视频文件(与 8.2 的Audio同族,均继承File并增加媒体维度参数);Length约束违规增加min/max参数,便于客户端展示约束边界。
7.4 给出了自定义约束迁移到命名参数的过渡形态——配合 #<a href="https://link.gitcode.com/i/4c02017a90218355f1308cf934110fe7" target="_blank">HasNamedArguments] 属性(定义于 [Attribute/HasNamedArguments.php,作用于方法,用于向加载器提示"哪些约束选项是必需的"):
use Symfony\Component\Validator\Attribute\HasNamedArguments;
class CustomConstraint extends Constraint
{
#[HasNamedArguments]
public function __construct(
public $option1,
public $option2 = null,
?array $groups = null,
mixed $payload = null,
) {
parent::__construct(null, $groups, $payload);
}
}
5.2 7.3:命名参数成为主流用法
- 弃用"不支持命名参数的自定义约束"——新约束必须标注
#[HasNamedArguments]; - 弃用向约束构造器传选项数组,改为每个选项一个命名参数:
// Before
new NotNull([
'groups' => ['foo', 'bar'],
'message' => 'a custom constraint violation message',
]);
// After
new NotNull(
groups: ['foo', 'bar'],
message: 'a custom constraint violation message',
);
Image约束支持 SVG 的宽高比(ratio)检查;When约束支持otherwise选项与闭包(closures);Composite约束支持多个字段包含嵌套约束;Unique约束新增stopOnFirstError选项(默认true,设为false时校验所有元素并报告全部违规)。
5.3 7.2~7.1:格式与标识符约束家族
7.2 新增 Yaml 约束、WordCount 约束、Week 约束;Ulid 约束新增 format 选项;Unique 新增 errorPath;WhenValidator 增加上下文变量;DateTime 违规消息新增 format 参数;IbanValidator 接受含不换行空格(non-breaking space)的 IBAN;PasswordStrengthValidator::estimateStrength() 变为 public。
7.1 新增 MacAddress 约束与 Charset 约束;Ip 约束新增 *_NO_PUBLIC、*_ONLY_PRIVATE、*_ONLY_RESERVED 版本,且 Cidr 约束可复用全部 Ip 版本;Type 约束新增 list 与 associative_array 类型;Url 新增 requireTld 选项(未传值时 7.1 开始弃用、8.0 默认 true);PasswordStrengthValidator 违规中携带计算出的强度值。
5.4 7.0:错误码常量与注解时代的终结
- 静态属性
$errorNames全部替换为ERROR_NAMES常量(同规则适用于Ip约束的$versions→VERSIONS); - 移除
Email::VALIDATION_MODE_LOOSE,使用VALIDATION_MODE_HTML5; - 移除
ExpressionLanguageSyntax约束,使用ExpressionSyntax; - 移除 Doctrine 注解支持,全面转向原生属性(Attribute):
AnnotationLoader、ValidatorBuilder::enableAnnotationMapping()、setDoctrineAnnotationReader()等全部移除,改用AttributeLoader、enableAttributeMapping(); ConstraintViolationInterface新增getConstraint()、getCause()、__toString();ConstraintViolationBuilderInterface新增disableTranslation()。
六、6.x:条件校验与密码安全
6.1 6.4:When 约束的完善与 GroupProvider
Expression约束的is_valid函数行为等同于ValidatorInterface::validate;Uuid约束的versions选项允许传入单个整数;When约束的constraints选项允许传入单个约束;- 宣布注解弃用路线(与 7.0 移除对应);
Type约束新增number、finite-number、finite-float类型;Time约束新增withSeconds选项;- 新增
GroupProviderInterface,允许在底层类之外实现校验组提供器。
6.2 6.3:密码与字符串安全
NoSuspiciousCharacters约束:检测字符串是否为"仿冒攻击"(spoofing attempt);PasswordStrength约束:检查密码强度;Length约束新增countUnit选项:按码点(默认Length::COUNT_CODEPOINTS)、字节(COUNT_BYTES)或字素簇(COUNT_GRAPHEMES)计数;File约束新增filenameMaxLength;Cascade约束新增exclude选项;Regex违规新增pattern参数;ConstraintViolationInterface新增getConstraint()。
6.3 6.2~6.1:条件校验与表达式增强
6.2:新增 When 约束(满足表达式条件时才执行嵌套约束)及其校验器;Email 新增 VALIDATION_MODE_HTML5_ALLOW_NO_TLD 模式;File 新增 extensions 选项(同时校验 MIME 类型、扩展名及二者一致性);Expression 新增 negate 选项反转违规逻辑;NotCompromisedPasswordValidator 增加隐私增强的填充(padding)机制。
6.1:Unique 新增 fields 选项(只对指定集合键做唯一性校验);弃用 Constraint::$errorNames 改用 ERROR_NAMES 常量;允许使用必填参数创建约束;Choice 新增 match 选项。
6.4 6.0:清理与注解弱化
移除 Length 的 allowEmptyString 选项与 NumberConstraintTrait 特征;enableAnnotationMapping() 不再接受、也不再自动设置 Doctrine 注解读取器。
七、5.x 与 4.x:约束生态的奠基期
7.1 5.x 关键新增
- 5.4:
Cidr约束、CssColor约束、ConstraintViolationList::createFromMessage()、Count/Length的exactly错误 UID; - 5.3:
Unique的normalizer选项、Validation::createIsValidCallable()(返回布尔而非抛异常); - 5.2:
Cascade约束(简化嵌套类型化属性的级联校验)、Isin、ULID、UUIDv6 支持、validator 开始支持从 PHP 属性加载约束; - 5.1:
AtLeastOneOf(至少一个嵌套约束通过即合法)、Hostname、Country/Language的alpha3选项、Compound约束(复用约束集合)、Sequentially(顺序校验,任一违规即停止后续)、Count的divisibleBy、ExpressionLanguageSyntax。
7.2 5.0 破坏性变更
ExpressionValidator 构造器第一个参数必须是 ExpressionLanguage 实例或 null;移除 Url 的 checkDNS/dnsMessage、Email 的 checkMX/checkHost/strict;DateTime/Date/Time 校验器不再校验 \DateTimeInterface 实例;Bic、Country、Currency、Language、Locale 必须依赖 symfony/intl,Email 必须依赖 egulias/email-validator,Expression 必须依赖 symfony/expression-language;Locale 的 canonicalize 默认改为 true;移除 ValidatorBuilderInterface;元数据缓存切换为 PSR-6(setMappingCache)。
7.3 4.x 关键新增
- 4.4:
EnableAutoMapping/DisableAutoMapping约束、Range的minPropertyPath/maxPropertyPath、TypeValidator支持类型数组、compared_value_path违规参数; - 4.3:
Timezone、NotCompromisedPassword、Json、Unique、Positive/PositiveOrZero/Negative/NegativeOrZero、字符串约束的normalizer选项、Bic的iban/ibanPropertyPath、NotBlank的allowNull、UATP 卡支持; - 4.2:
DivisibleBy、UnexpectedValueException(校验器抛出的异常会被转换为违规)、DateTime的format默认选项; - 4.1:
Expression的values选项、弃用Url的checkDNS。
八、3.x 与 2.x:组件早期奠基
3.4:Valid 约束支持校验组、Image 的 minPixels/maxPixels、比较约束的 propertyPath 选项。
3.3:AddValidatorInitializersPass、AddConstraintValidatorsPass、ContainerConstraintValidatorFactory —— 将约束校验器接入服务容器。
3.2:YAML 配置文件支持 PHP 常量;测试基类 AbstractConstraintValidatorTest 弃用,改用 Test/ConstraintValidatorTestCase.php。
3.1:DateTime 新增 format 选项。
2.8:BIC(SWIFT-Code)校验器。
2.7:弃用 DefaultTranslator;Null/True/False 及对应校验器弃用,由 IsNull/IsTrue/IsFalse 替代。
2.6:FileValidator 禁止空文件;ExpressionValidator 对 null/空字符串也执行表达式;所有约束支持通用的 payload 选项;ConstraintViolationBuilderInterface::setCause()。
2.5:大规模内部重构——引入 Mapping\MetadataInterface 体系、Context\ExecutionContextInterface、Validator\RecursiveValidator 等分层架构;GroupSequence 实现 ArrayAccess/Countable/Traversable;Callback 可作用于属性;ConstraintViolation 支持参数与复数化消息。
2.4:新增基于表达式语言的约束;Image 增加 minRatio/maxRatio/allowSquare/allowLandscape/allowPortrait。
2.3:ISBN、ISSN、IBAN 校验器;Optional/Required 约束;完整比较约束族(EqualTo、NotEqualTo、LessThan、LessThanOrEqualTo、GreaterThan、GreaterThanOrEqualTo、IdenticalTo、NotIdenticalTo)。
2.2:CardScheme、Luhn 校验器;约束/违规/执行上下文接口体系建立;违规消息支持翻译与复数化。
2.1:Count、Length、Range 约束;ctype_* 类型断言;GroupSequenceProvider 动态组序列模式;Collection 校验器的 missing/extra 错误细化;ConstraintValidatorInterface 的 isValid 更名为 validate;Validation 与 ValidatorBuilderInterface 出现。
九、实践总结:如何用好这个组件的演进成果
- 新项目直接用 8.x 写法:所有约束一律使用命名参数构造器,自定义约束务必标注
#<a href="https://link.gitcode.com/i/5a5e399ee28833dcfe650996acefbcfb" target="_blank">HasNamedArguments]并将必填项写成必填构造参数,详见 [Attribute/HasNamedArguments.php; - 升级 8.0 时重点排查:XML/YAML 中的隐式
<value>配置、GroupSequence关联数组、Url的requireTld默认值、注解映射代码,四类破坏性变更均可在本 CHANGELOG 对应版本段落找到迁移示例; - 按需引入依赖:
Cron(dragonmantank/cron-expression)、Email(egulias/email-validator)、Expression(symfony/expression-language)、Bic/Country/Currency等(symfony/intl)属于可选依赖,未安装时约束构造器会直接抛LogicException,可从 composer.json 中核对建议依赖清单; - 验证组是分层校验的核心工具:
GroupSequence+cascadeCurrentGroup+Valid::restrictGroups组合,可以精确控制"先做廉价校验、再做昂贵校验"的执行顺序,以及嵌套对象在各组间的级联行为; - 测试优先:使用
ConstraintValidatorTestCase::validate()(8.1 起)与CompoundConstraintTestCase(7.2 起)编写约束校验器测试,配合enablePropertyMetadataExistenceCheck()在测试中尽早暴露元数据缺失问题。
从 2.1 到 8.2,Validator 组件始终围绕"声明式校验"这一核心演进:约束家族不断扩充(从基础字符串/数值校验扩展到媒体文件、格式语法、密码安全、标识符),声明方式从选项数组 + 注解全面迁移到命名参数 + 原生属性,验证组与级联机制日趋精细。掌握这份 CHANGELOG 的脉络,就等于掌握了 Symfony 校验体系的设计哲学与迁移地图。