如何检查Doctrine 2中是否更改了实体?

时间:2012-05-29 13:29:56

标签: php doctrine doctrine-orm

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

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

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

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

有人有建议吗?

9 个答案:

答案 0 :(得分:24)

我首先要检查你的 setName 函数是否正在做某事($ this-> name = $ name ...)如果它已经有效,那么你可以定义一个您调用flush时触发的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。

答案 1 :(得分:11)

如果您需要访问包含旧值和新值的实体字段,您可能还需要查看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');
            }
        }
    }
}

答案 2 :(得分:11)

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

$product->setName('A different name');
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($product)) {
    // My entity has changed
}

答案 3 :(得分:6)

Doctrine2 Docs. 17. Change Tracking Policies

如果您像我一样使用第三种形式(17.3。通知),您可以测试您的实体是否已更改:

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

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

答案 4 :(得分: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()方法可能会丢弃任何刷新更改,所以需要做什么完成是在侦听器中再次调用flush并创建一些静态$alreadyFlushed切换以避免循环引用。

答案 5 :(得分:1)

如果只需要比较对象的旧状态和新状态,则可能会更简单:

$originalEntityData = $entityManager->getUnitOfWork()->getOriginalEntityData($entityObject);

答案 6 :(得分:0)

我很好奇Doctrine和每个人记录postFlush,因为在某些情况下,你有一个正在进行的交易。 我想指出还有postTransactionCommit,根据你想要在postFlush事件中实现的目标,它可能更安全。

答案 7 :(得分:0)

根据我的需求,此处的答案以及the docs,我为实体中的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;
}

这基于what the docs say有关此Event的内容,而不是其他可用内容,例如PostPersistPreFlush

  

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

使用PreUpdate而不是其他允许您将所有计算和计算密集型函数留给Doctrine已定义的进程。手动触发变更集的计算(例如上面的these answers是服务器CPU密集型的。 onFlush事件,例如在the accepted answer中使用的是一个选项(以演示的方式),但如果依赖于检测对实体的更改,则不会如上所述(preUpdateModifiedAt(PreUpdateEventArgs $args)

答案 8 :(得分:0)

我同意@Andrew Atkinson的话:

  

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

但是,根据我的经验,我不同意他提出的示例,这是一种更好的方法来检查是否有所更改。

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

这样,仅当确实有某些字段发生更改时,才会触发if。

关于如果更改了该字段或该字段怎么办,那么,我推荐他的解决方案。