34

我需要检查持久实体是否已更改并需要在数据库上更新。我所做的(但没有工作)如下:

$product = $entityManager->getRepository('Product')->find(3);
$product->setName('A different name');

var_export($entityManager->getUnitOfWork()->isScheduledForUpdate($product));

该代码总是打印false,我也尝试在检查工作单元之前刷新,但没有工作。

有人有建议吗?

4

9 回答 9

28

首先我会检查你的setName函数实际上正在做某事($this-> name = $name...)如果它已经在工作,那么你可以在你的 services.yml 上定义一个事件监听器,当你叫同花顺。

entity.listener:
  class: YourName\YourBundle\EventListener\EntityListener
  calls:
    - [setContainer,  ["@service_container"]]
  tags:
    - { name: doctrine.event_listener, event: onFlush }

然后定义 EntityListener

namespace YourName\YourBundle\EventListener;

use Doctrine\ORM\Event;
use Symfony\Component\DependencyInjection\ContainerAware;

class EntityListener extends ContainerAware
{   

    /**
     * Gets all the entities to flush
     *
     * @param Event\OnFlushEventArgs $eventArgs Event args
     */
    public function onFlush(Event\OnFlushEventArgs $eventArgs)
    {   
        $em = $eventArgs->getEntityManager();
        $uow = $em->getUnitOfWork();

        //Insertions
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            # your code here for the inserted entities
        }

        //Updates
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            # your code here for the updated entities
        }

        //Deletions
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
            # your code here for the deleted entities
        }
    }
}

如果您需要知道哪些实体正在被更改,但在将它们保存到数据库对其进行处理,只需将更改的实体存储在私有数组中,然后定义一个从数组中获取实体的 onFlush 事件。

顺便说一句,要触发此类事件,您需要在实体上添加 @ORM\HasLifecycleCallbacks。

于 2012-05-29T13:48:13.277 回答
18

我不需要/不想为我的案例创建监听器,所以我最终得到了

$product->setName('A different name');
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($product)) {
    // My entity has changed
}
于 2015-03-06T15:55:21.393 回答
15

Doctrine2 文档。17. 变更跟踪政策

如果您像我一样使用第三种形式(17.3. Notify),您可以测试您的实体是否发生更改:

$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
$aChangeSet = $uow->getEntityChangeSet($oEntity);

如果没有任何改变,它将返回空白数组。

于 2015-11-25T18:11:34.010 回答
14

如果您需要访问具有旧值和新值的实体字段,您可能还想查看PreUpdate事件。

一个例子主要来自提供的链接:

<?php
class NeverAliceOnlyBobListener
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if ($eventArgs->getEntity() instanceof User) {
            if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
                $oldValue = $eventArgs->getOldValue('name');
                $eventArgs->setNewValue('name', 'Bob');
            }
        }
    }
}
于 2013-04-17T13:44:30.573 回答
3

如果您只需要比较对象的新旧状态,那么这可能会更简单:

$originalEntityData = $entityManager->getUnitOfWork()->getOriginalEntityData($entityObject);
于 2020-03-07T11:33:13.933 回答
2

这个问题已经很老了,但可能仍然有一些人可能会从不同的角度面对这个问题。效果很好,UnitOfWork但它只返回更改数组。当某人实际上并不知道哪些字段可能已更改并且只想将整个实体作为对象进行比较时,这可能会很$oldEntity痛苦$newEntity。即使事件的名称是 preUpdate 如果有人会尝试从数据库中获取数据,如下所示:

$er->find($id);

返回的实体将包含所有更改。解决方法很简单,但它有一些钩子:

public function preUpdate(Entity $entity, PreUpdateEventArgs $args)
{
    $entity = clone $entity; //as Doctrine under the hood 
                             //uses reference to manage entities you might want 
                             //to work on the entity copy. Otherwise,        
                             //the below refresh($entity) will affect both 
                             //old and new entity.
    $em = $args->getEntityManager();
    $currentEntity = $em->getRepository('AppBundle:Entity')->find($entity->getId());
    $em->refresh($currentEntity);

}

对于那些正在使用另一个事件(例如 preFlush)的人,我已经快速检查了它并且解决方法效果不佳,因为该refresh()方法可能会丢弃任何刷新更改,因此需要做的是在侦听器中再次调用刷新并创建一些静态$alreadyFlushed切换以避免循环引用。

于 2017-01-10T10:09:45.397 回答
0

根据我的需求、此处的答案和文档modifiedAt,我为实体中的时间戳提出了以下解决方案。

/**
 * @Doctrine\ORM\Mapping\PreUpdate()
 *
 * @param \Doctrine\ORM\Event\PreUpdateEventArgs $args
 * @return $this
 */
public function preUpdateModifiedAt(\Doctrine\ORM\Event\PreUpdateEventArgs $args)
{
    $this->setModifiedAt(new \DateTime('now'));

    return $this;
}

这是基于文档所说Event,而不是其他可用的,例如PostPersistand PreFlush

PreUpdate 是使用最严格的事件,因为它是在 EntityManager#flush() 方法中的实体调用更新语句之前调用的。请注意,当计算的变更集为空时,不会触发此事件。

使用PreUpdate与其他方法相反,您可以将所有计算和计算密集型功能留给 Doctrine 已经定义的过程。手动触发变更集的计算,例如上面的这些 答案是服务器 CPU 密集型的。onFlush 事件,例如在接受的答案中使用的是一个选项(以演示的方式),但如果您依赖于检测实体的更改,则不是,就像您可以使用上面的函数(preUpdateModifiedAt(PreUpdateEventArgs $args))一样。

于 2017-11-14T12:55:03.250 回答
0

我同意@Andrew Atkinson 的说法:

如果您需要访问具有旧值和新值的实体字段,您可能还想查看PreUpdate事件。

但我不同意他提出的例子,根据我的经验,有一种更好的方法来检查是否发生了变化。

<?php
class Spock
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if (!empty($eventArgs->getEntityChangeSet())) {
            // fill this how you see fit
        }
    }
}

这样 if 只会在确实有某些字段发生更改或没有更改的情况下才会被触发。

至于如果这个或那个字段被改变了怎么办,那么是的,我推荐他的解决方案。

于 2018-08-06T07:01:56.427 回答
-1

我对 Doctrine 和每个记录 postFlush 的人感到好奇,因为在某些情况下,你有一个正在进行的事务。我想指出还有 postTransactionCommit,这可能更安全,具体取决于您在 postFlush 事件中尝试实现的目标。

于 2017-07-04T07:22:32.570 回答