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

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()方法时,它会优先从**一级缓存(内存)**返回已经被修改的实体实例,而不会重新从数据库查询,所以你拿到的是已经被请求参数修改后的对象。

正确的处理方式

  1. 直接获取单个字段的旧值(推荐)
    如果你只需要某个字段的原始值,不需要完整实体,直接用PreUpdateEventArgs提供的getOldValue()方法:

    // 获取company字段的原始ID
    $originalCompanyId = $eventArgs->getOldValue('company');
    // 获取licenseValue的原始值
    $originalLicenseValue = $eventArgs->getOldValue('licenseValue');
    

    这个方法是preUpdate事件的原生能力,无需额外查询,性能最优。

  2. 获取完整的旧实体数据
    如果必须拿到完整的原始实体,有两种可行方案:

    • 在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();
          // ...
      }
      
      注意:如果使用克隆,要确保实体的关联对象也能正确克隆,避免引用问题。
  3. 特殊场景:强制从数据库查询
    如果你一定要在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:45:20