Symfony反序列化时如何校验对象属性初始化并抛出异常
问题场景
现有如下DTO类:
class SimpleDto implements GetPhoneInterface { public string $name; public int $value; }
对应JSON数据:
{"name":"Jane"}
执行反序列化操作:
$serializer = self::getContainer()->get(SerializerInterface::class); $dto = $serializer->deserialize($json, $dtoClass);
得到的$dto中$value处于未初始化状态,需要实现:反序列化时若存在未初始化属性则抛出异常;同时询问是否可通过Validator组件实现校验。
另外尝试继承ObjectNormalizer实现,但该类为final无法继承,需更优方案。
解决方案
方案一:通过序列化组件的服务装饰器实现
由于ObjectNormalizer是final类无法继承,可采用服务装饰器模式包装原有逻辑,在反序列化完成后添加未初始化属性校验:
1. 编写装饰器类
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; class InitializedPropertiesValidatorNormalizer implements DenormalizerInterface, NormalizerInterface { private DenormalizerInterface&NormalizerInterface $decorated; public function __construct(DenormalizerInterface&NormalizerInterface $decorated) { $this->decorated = $decorated; } public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []) { // 先执行原有反序列化逻辑 $object = $this->decorated->denormalize($data, $type, $format, $context); // 校验所有属性是否初始化 $reflection = new \ReflectionObject($object); $errors = []; foreach ($reflection->getProperties() as $property) { if (!$property->isInitialized($object)) { $errors[] = sprintf('属性 "%s" 未初始化', $property->getName()); } } if (!empty($errors)) { throw new NotNormalizableValueException(implode('; ', $errors)); } return $object; } public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { return $this->decorated->supportsDenormalization($data, $type, $format, $context); } // 序列化相关方法直接委托给被装饰的normalizer public function normalize(mixed $object, ?string $format = null, array $context = []) { return $this->decorated->normalize($object, $format, $context); } public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $this->decorated->supportsNormalization($data, $format, $context); } public function getSupportedTypes(?string $format): array { return $this->decorated->getSupportedTypes($format); } }
2. 配置服务装饰器
在services.yaml中注册装饰器,替换原有ObjectNormalizer的调用链:
services: App\Serializer\InitializedPropertiesValidatorNormalizer: decorates: Symfony\Component\Serializer\Normalizer\ObjectNormalizer arguments: ['@.inner']
配置完成后,所有反序列化操作都会自动触发未初始化属性校验,存在问题时直接抛出异常。
方案二:使用Validator组件自定义约束校验
若希望更灵活地控制校验规则(比如区分必填/可选属性),可通过Validator组件实现:
1. 自定义Initialized约束
use Symfony\Component\Validator\Constraint; #[\Attribute] class Initialized extends Constraint { public string $message = '属性 "{{ property }}" 未初始化'; public function getTargets(): string { return self::PROPERTY_CONSTRAINT; } }
2. 编写约束验证器
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; class InitializedValidator extends ConstraintValidator { public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof Initialized) { return; } $property = $this->context->getProperty(); $object = $this->context->getObject(); if (!$property || !$object) { return; } $reflectionProperty = new \ReflectionProperty($object::class, $property->getName()); if (!$reflectionProperty->isInitialized($object)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ property }}', $property->getName()) ->addViolation(); } } }
3. 给DTO属性添加约束
use App\Validator\Constraints as AppAssert; class SimpleDto implements GetPhoneInterface { #[AppAssert\Initialized] public string $name; #[AppAssert\Initialized] public int $value; }
4. 反序列化后执行校验
$serializer = self::getContainer()->get(SerializerInterface::class); $validator = self::getContainer()->get(ValidatorInterface::class); $dto = $serializer->deserialize($json, $dtoClass); $violations = $validator->validate($dto); if (count($violations) > 0) { // 处理校验错误,示例:抛出异常 throw new \RuntimeException((string) $violations); }
方案对比
- 装饰器方案:无需手动触发校验,逻辑嵌入序列化流程,适合全局强制校验场景。
- Validator方案:可针对单个属性配置规则,灵活性更高,适合需要区分必填/可选属性的场景。
内容的提问来源于stack exchange,提问作者sveta600
相关产品推荐
相关产品推荐

