You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 12:51:04