PHP/Symfony/Doctrine升级后,如何让Doctrine返回数据库新实体实例?
问题背景
我们正在将Web API从以下版本升级:
- PHP 7.4
- Symfony 4.4
- Doctrine 2.8
升级至: - PHP 8.2
- Symfony 5.4
- Doctrine 2.19
核心场景与问题:
- 主实体包含多个
Collection类型的关联属性,通过POST请求接收JSON数据,经JMS Serializer反序列化后插入数据库,Collection对应JSON数组。 - 原流程:插入主实体后执行
flush→refresh→findOneBy,返回数据库中的实体以匹配后续GET请求结果,旧版本(读写分离架构)下该流程正常,读连接会返回带空Collection的全新实体实例。 - 升级改为单连接后出现异常:当POST请求缺失
Collection属性时,返回的实体中该属性为null;排查发现persist的实例与findOneBy返回的是同一对象,refresh未生效。 - 限制:Doctrine官方建议构造函数初始化
Collection,但JMS Serializer不调用构造函数,也无法为缺失属性设置默认Collection;暂时无法重构原有流程,已恢复读写分离,希望找到Doctrine原生解决方案。
解决方案
1. 绕开UnitOfWork缓存,强制获取全新实例
Doctrine的findOneBy会优先返回UnitOfWork中已托管的对象,要模拟旧版本读写分离的全新实例获取逻辑,可通过以下方式绕开缓存:
// 方式1:清除指定实体的UnitOfWork缓存后查询 $entityManager->clear(YourMainEntity::class); $freshEntity = $entityManager->findOneBy(['id' => $mainEntity->getId()]); // 方式2:新建EntityManager实例查询(单连接场景下直接从数据库拉取) $newEntityManager = $entityManager->create($entityManager->getConnection(), $entityManager->getConfiguration()); $freshEntity = $newEntityManager->findOneBy(['id' => $mainEntity->getId()]);
该方案无需修改实体或序列化配置,对现有代码侵入极小,完全匹配旧版本行为。
2. 利用Doctrine PostLoad事件自动初始化空Collection
注册Doctrine PostLoad事件订阅者,在实体从数据库加载后自动将null的Collection属性初始化为空ArrayCollection:
use Doctrine\ORM\Events; use Doctrine\Common\EventSubscriber; use Doctrine\ORM\Event\PostLoadEventArgs; use App\Entity\YourMainEntity; use Doctrine\Common\Collections\ArrayCollection; class CollectionInitializerSubscriber implements EventSubscriber { public function getSubscribedEvents(): array { return [Events::postLoad]; } public function postLoad(PostLoadEventArgs $args): void { $entity = $args->getObject(); if (!$entity instanceof YourMainEntity) { return; } // 逐个处理Collection属性 if ($entity->getRelatedEntities() === null) { $entity->setRelatedEntities(new ArrayCollection()); } if ($entity->getAnotherEntities() === null) { $entity->setAnotherEntities(new ArrayCollection()); } } }
在Symfony中注册该订阅者后,所有从数据库加载的实体都会自动完成空Collection初始化,无需手动干预。
3. 配置JMS Serializer为缺失属性设置默认值
通过实体setter方法处理默认值,并配置JMS Serializer确保反序列化时触发setter:
// 实体类中修改setter use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; class YourMainEntity { private ?Collection $relatedEntities = null; public function setRelatedEntities(?Collection $relatedEntities): void { $this->relatedEntities = $relatedEntities ?? new ArrayCollection(); } }
配置JMS Serializer元数据强制使用setter:
# config/serializer/Entity/YourMainEntity.yml App\Entity\YourMainEntity: properties: relatedEntities: access_type: public_method setter: setRelatedEntities nullable: true
这样即使POST请求中缺失该属性,JMS Serializer也会调用setRelatedEntities(null),自动初始化为空ArrayCollection。
4. 手动修正refresh后的属性值
Doctrine的refresh仅同步标量属性,可在refresh后手动检查并初始化null的Collection属性:
$entityManager->flush(); $entityManager->refresh($mainEntity); // 手动初始化空Collection if ($mainEntity->getRelatedEntities() === null) { $mainEntity->setRelatedEntities(new ArrayCollection()); }
该方案实现简单,适合Collection属性数量较少的场景。
内容的提问来源于stack exchange,提问作者Matthew Krauss
相关产品推荐
相关产品推荐

