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

Symfony 4.4自定义Serializer序列化大量实体性能骤降问题

Symfony 3.4升级到4.4后序列化性能暴跌的解决办法

最近把项目从Symfony 3.4升级到4.4,遇到了一个头疼的问题:序列化大量实体的时候性能直接崩了。原来3.4里序列化1444个对象只需要7秒,现在4.4里直接超过30秒触发超时。

原因是Symfony 4.2开始弃用了setCallbacks()和setCircularReferenceHandler()方法,我按照官方指引改成了通过ObjectNormalizer传递上下文的方式,功能倒是正常了,但性能掉得离谱。

升级前的代码(3.4版本)

class MyEntitySerializer extends Serializer {
    public function __construct(ObjectNormalizer $normalizer, JsonEncoder $encoder) {
        $dateCallback = function ($dateTime) {
            return $dateTime instanceof \DateTime ? date_format($dateTime,'d/m/Y') : null;
        };
        $normalizer->setCallbacks([
            'debut' => $dateCallback,
            'fin' => $dateCallback,
            'demandeDate' => $dateCallback,
            'dateCreation' => $dateCallback,
        ]);
        $normalizer->setCircularReferenceHandler(function ($object) {
            if ($object instanceof Affaire) {
                return $object->getCode();
            } elseif ($object instanceof ComplementAffaire) {
                return $object->getDescription();
            }
            return $object->getId();
        });
        parent::__construct([$normalizer], [$encoder]);
    }
}

升级后的代码(4.4版本,性能问题版本)

class MyEntitySerializer extends Serializer {
    public function __construct(ObjectNormalizer $normalizer, JsonEncoder $encoder) {
        $dateCallback = function ($dateTime) {
            return $dateTime instanceof \DateTime ? date_format($dateTime,'d/m/Y') : null;
        };
        $defaultContext = [
            AbstractNormalizer::CALLBACKS => [
                'debut' => $dateCallback,
                'fin' => $dateCallback,
                'demandeDate' => $dateCallback,
                'dateCreation' => $dateCallback,
            ],
            AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) {
                if ($object instanceof Affaire) {
                    return $object->getCode();
                } elseif ($object instanceof ComplementAffaire) {
                    return $object->getDescription();
                }
                return $object->getId();
            }
        ];
        $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
        $normalizer = new ObjectNormalizer($classMetadataFactory,null,null,null,null,null,$defaultContext);
        parent::__construct([$normalizer], [$encoder]);
    }
}

我试过注入ClassMetadataFactoryInterface,也清过var/cache/和vendor/重新安装依赖,但性能还是没上来。

问题根源和解决方案

其实核心问题出在类元数据没有缓存,以及重复创建ObjectNormalizer实例。

1. 缓存类元数据(最关键)

升级后的代码里每次创建ObjectNormalizer时,都新建了一个ClassMetadataFactory,而且没有加缓存层。这会导致每次序列化都要重新解析实体类的注解,处理大量对象时这个开销会被无限放大。

解决办法是用CacheClassMetadataFactory把元数据缓存起来,比如用文件系统缓存:

use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

class MyEntitySerializer extends Serializer {
    public function __construct(JsonEncoder $encoder) {
        $dateCallback = function ($dateTime) {
            return $dateTime instanceof \DateTime ? $dateTime->format('d/m/Y') : null; // 换成DateTime自身的format更高效
        };
        
        // 创建缓存适配器,把元数据存在项目缓存目录
        $cache = new FilesystemAdapter('serializer_metadata', 0, dirname(__DIR__).'/var/cache');
        
        // 用缓存包裹类元数据工厂
        $classMetadataFactory = new CacheClassMetadataFactory(
            new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())),
            $cache
        );
        
        $defaultContext = [
            AbstractNormalizer::CALLBACKS => [
                'debut' => $dateCallback,
                'fin' => $dateCallback,
                'demandeDate' => $dateCallback,
                'dateCreation' => $dateCallback,
            ],
            AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) {
                if ($object instanceof Affaire) {
                    return $object->getCode();
                } elseif ($object instanceof ComplementAffaire) {
                    return $object->getDescription();
                }
                return $object->getId();
            }
        ];
        
        $normalizer = new ObjectNormalizer($classMetadataFactory,null,null,null,null,null,$defaultContext);
        parent::__construct([$normalizer], [$encoder]);
    }
}

2. 复用容器里的ObjectNormalizer实例

升级后的代码里明明注入了ObjectNormalizer,但又自己new了一个,完全浪费了Symfony容器里已经配置好的、可能已经带缓存的实例。更优的做法是直接修改注入实例的默认上下文:

class MyEntitySerializer extends Serializer {
    public function __construct(ObjectNormalizer $normalizer, JsonEncoder $encoder) {
        $dateCallback = function ($dateTime) {
            return $dateTime instanceof \DateTime ? $dateTime->format('d/m/Y') : null;
        };
        
        // 合并现有默认上下文与自定义配置
        $defaultContext = array_merge(
            $normalizer->getDefaultContext(),
            [
                AbstractNormalizer::CALLBACKS => [
                    'debut' => $dateCallback,
                    'fin' => $dateCallback,
                    'demandeDate' => $dateCallback,
                    'dateCreation' => $dateCallback,
                ],
                AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) {
                    if ($object instanceof Affaire) {
                        return $object->getCode();
                    } elseif ($object instanceof ComplementAffaire) {
                        return $object->getDescription();
                    }
                    return $object->getId();
                }
            ]
        );
        
        $normalizer->setDefaultContext($defaultContext);
        
        parent::__construct([$normalizer], [$encoder]);
    }
}

3. 其他优化小技巧

  • 生产环境一定要启用缓存:dev环境缓存可能不会持久化,切换到prod环境运行php bin/console cache:clear --env=prod再测试
  • 优化日期处理:用$dateTime->format()代替date_format(),前者是对象方法性能更好;也可以考虑用Symfony自带的DateTimeNormalizer统一处理日期格式
  • 减少序列化数据量:如果不需要序列化所有字段,用AbstractNormalizer::GROUPS定义序列化组,或者用AbstractNormalizer::IGNORED_ATTRIBUTES忽略不需要的字段,能大幅减少处理时间
  • 简化回调逻辑:确保回调函数里没有数据库查询、复杂计算等耗时操作,保持逻辑极简

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 13:32:55