Symfony4.4中Doctrine生命周期事件内如何读写数据库?
问题
在基于Symfony4.4的应用中,我在Doctrine事件监听器的preUpdate()方法内,尝试用以下代码查询数据库中的实体数据:
$entity = $entityManager->getRepository(MyEntity::class)->find($entity->getId());
我通过Postman调用PUT /api/update-my-entity接口,仅修改请求参数中的company id:
{ "id":"6532", "licenseValue":"AABBTT", "company":30, "type":"E145", "deleteAutorized":true, "editedBy":"admin" }
但查询得到的是请求输入的数据,而非数据库原有数据。想了解在Doctrine事件监听器中,哪个回调方法可以正确与数据库进行读写交互?
更新:以下是preUpdate()函数中的测试代码:
/** * @param MyEntity $entity * @param PreUpdateEventArgs $eventArgs * @return void */ public function preUpdate(MyEntity $myEntity, PreUpdateEventArgs $eventArgs): void { $entity = $eventArgs->getEntity(); if (!$entity instanceof MyEntity) { return; } $entityManager = $eventArgs->getObjectManager(); /**@var MyEntity */ $myEntity = $entityManager->getRepository(MyEntity::class)->find($myEntity->getId()); dump($entity->getId()); dump($myEntity->getId()); // 返回请求输入值而非数据库值 dump($myEntity->getLicenseValue()); // 返回请求输入值 dump($myEntity->getCompany()->getId()); // 返回请求输入值 die("Ok"); }
解决方案
为什么preUpdate里查不到数据库原始数据
Doctrine的UnitOfWork会跟踪实体的所有变更,当你调用find()方法时,它会优先从**一级缓存(内存)**返回已经被修改的实体实例,而不会重新从数据库查询,所以你拿到的是已经被请求参数修改后的对象。
正确的处理方式
直接获取单个字段的旧值(推荐)
如果你只需要某个字段的原始值,不需要完整实体,直接用PreUpdateEventArgs提供的getOldValue()方法:// 获取company字段的原始ID $originalCompanyId = $eventArgs->getOldValue('company'); // 获取licenseValue的原始值 $originalLicenseValue = $eventArgs->getOldValue('licenseValue');这个方法是
preUpdate事件的原生能力,无需额外查询,性能最优。获取完整的旧实体数据
如果必须拿到完整的原始实体,有两种可行方案:- 在
preFlush事件中处理preFlush事件触发于flush()执行初期,此时实体的变更还未被写入数据库,但UnitOfWork已经记录了变更。你可以通过创建查询并强制绕过一级缓存来获取原始数据:use Doctrine\ORM\Query; public function preFlush(PreFlushEventArgs $eventArgs): void { $entityManager = $eventArgs->getObjectManager(); $uow = $entityManager->getUnitOfWork(); // 遍历所有待更新的实体 foreach ($uow->getScheduledEntityUpdates() as $entity) { if ($entity instanceof MyEntity) { // 创建查询并强制刷新缓存 $originalEntity = $entityManager->createQueryBuilder() ->select('e') ->from(MyEntity::class, 'e') ->where('e.id = :id') ->setParameter('id', $entity->getId()) ->getQuery() ->setHint(Query::HINT_REFRESH, true) ->getOneOrNullResult(); // 使用$originalEntity操作原始数据 } } } - 在
postLoad事件中缓存原始数据
在实体首次加载时(postLoad事件),把实体的状态保存到一个临时存储中,比如实体的私有属性或者监听器的成员变量,后续在preUpdate时直接读取缓存的原始数据:
注意:如果使用克隆,要确保实体的关联对象也能正确克隆,避免引用问题。// 在MyEntity类中添加私有属性存储原始状态 private $originalData; // 监听器的postLoad方法 public function postLoad(MyEntity $entity): void { // 克隆实体保存原始状态 $entity->originalData = clone $entity; // 或者只保存需要的字段 // $entity->originalData = [ // 'company' => $entity->getCompany()->getId(), // 'licenseValue' => $entity->getLicenseValue() // ]; } // 在preUpdate中使用 public function preUpdate(MyEntity $entity, PreUpdateEventArgs $eventArgs): void { $originalCompanyId = $entity->originalData->getCompany()->getId(); // ... }
- 在
特殊场景:强制从数据库查询
如果你一定要在preUpdate中直接查询数据库原始数据,可以使用原生SQL(绕过Doctrine缓存):// 使用原生SQL查询 $conn = $entityManager->getConnection(); $stmt = $conn->prepare('SELECT * FROM my_entity WHERE id = :id'); $stmt->execute(['id' => $entity->getId()]); $originalData = $stmt->fetchAssociative();
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

