在postPersist事件中插入主义

时间:2012-04-29 17:16:50

标签: symfony doctrine-orm symfony-sonata

我想在实体持久化和更新上添加新的Feed项。我写这个事件监听器(postUpdate是相同的):

public function postPersist(LifecycleEventArgs $args)
{
    $entity = $args->getEntity();
    $em = $args->getEntityManager();

    if ($entity instanceof FeedItemInterface) {
        $feed = new FeedEntity();
        $feed->setTitle($entity->getFeedTitle());
        $feed->setEntity($entity->getFeedEntityId());
        $feed->setType($entity->getFeedType());
        if($entity->isFeedTranslatable()) {
            $feed->getEnTranslation()->setTitle($entity->getFeedTitle('en'));
        }
        $em->persist($feed);
        $em->flush();
    }
}

但我得到了

  

完整性约束违规:1062密钥的重复条目“30 -2”   '主要'

并且在log a中有两个插入:

  

INSERT INTO interview_scientificdirection(interview_id,   scientificdirection_id)VALUES(?,?)([30,2])INSERT INTO   interview_scientificdirection(interview_id,scientificdirection_id)   价值观(?,?)([30,2])

科学方向是多对多的关系表,实体我们想要坚持。 在前端应用程序中一切正常,但在Sonata Admin我遇到了这个问题:(

6 个答案:

答案 0 :(得分:27)

如果你需要保留其他对象,那么Doc​​trine中的postPersist或postUpdate处理程序可能不是正确的选择。我今天遇到了同样的问题,因为我需要在该处理程序中生成一些消息条目。

此时的问题是postPersist处理程序在刷新事件期间被称为,而不是之后。因此,您无法在此处保留其他对象,因为它们之后不会被刷新。另外,你不能在postPersist处理程序中调用flush,因为这可能会导致ducplicate条目(正如你所经历的那样)。

一种方法是使用doctrine中的onFlush处理程序,在此处记录:http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#onflush

如果您需要插入数据库对象的id,这只是一个问题,因为该实体尚未写入该处理程序中的数据库。如果你不需要那些id,那么你可以使用doctrine中的ofFlush事件。

对我来说,解决方案有点不同。我目前正在研究一个symfony2项目,并且需要插入数据库对象的id(稍后用于回调和更新)。

我在symfony2中创建了一个新服务,它基本上就像我的消息队列一样。在postPersist更新期间,我只填写队列中的条目。我在kernel.response上注册了另一个处理程序,然后获取这些条目并将它们持久保存到数据库中。 (根据这一点:http://symfony.com/doc/current/cookbook/service_container/event_listener.html

我希望我不要在这里偏离这个话题太多,但是因为这是我真正努力的事情,我希望有些人可能觉得这很有用。

此服务条目为:

 amq_messages_chain:
   class: Acme\StoreBundle\Listener\AmqMessagesChain

 amqflush:
   class: Acme\StoreBundle\Listener\AmqFlush
   arguments: [ @doctrine.orm.entity_manager, @amq_messages_chain, @logger ]
   tags:
     - { name: kernel.event_listener, event: kernel.response, method: onResponse, priority: 5 }

 doctrine.listener:
  class: Acme\StoreBundle\Listener\AmqListener
  arguments: [ @logger, @amq_messages_chain ]
  tags:
    - { name: doctrine.event_listener, event: postPersist }
    - { name: doctrine.event_listener, event: postUpdate }
    - { name: doctrine.event_listener, event: prePersist }

您不能使用doctrine.listener,因为这会导致循环依赖(因为您需要服务的实体管理器,但实体管理器需要服务....)

这就像一个魅力。如果您需要更多信息,请不要犹豫,我很乐意为此添加一些示例。

答案 1 :(得分:27)

Francesc的回答是错误的,因为postFlush事件中的更改集已经为空。 jhoffrichter的第二个答案可行,但是有点矫枉过正。 正确的方法是将实体持久化在postPersist事件中,并在postFlush事件中再次调用flush。但是,只有在postPersist事件中更改了某些内容时才必须执行此操作,否则会创建无限循环。

public function postPersist(LifecycleEventArgs $args) {

    $entity = $args->getEntity();
    $em = $args->getEntityManager();

    if($entity instanceof FeedItemInterface) {
        $feed = new FeedEntity();
        $feed->setTitle($entity->getFeedTitle());
        $feed->setEntity($entity->getFeedEntityId());
        $feed->setType($entity->getFeedType());
        if($entity->isFeedTranslatable()) {
            $feed->getEnTranslation()->setTitle($entity->getFeedTitle('en'));
        }
        $em->persist($feed);
        $this->needsFlush = true;
    }
}

public function postFlush(PostFlushEventArgs $eventArgs)
{
    if ($this->needsFlush) {
        $this->needsFlush = false;
        $eventArgs->getEntityManager()->flush();
    }
}

答案 2 :(得分:2)

嗯,这是我在SF 2.0和2.2中的表现:

听众课程:

<?php
namespace YourNamespace\EventListener;

use Doctrine\ORM\Mapping\PostPersist;


/*
 * ORMListener class
 *
 * @author:        Marco Aurélio Simão
 * @description:   Listener para realizar operações em qualquer objeto manipulado pelo Doctrine 2.2
 */

use Doctrine\ORM\UnitOfWork;

use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\Mapping\PrePersist;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Mapping\PostUpdate;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Enova\EntitiesBundle\Entity\Entidades;

use Doctrine\ORM\Event\LifecycleEventArgs;

use Enova\EntitiesBundle\Entity\Tagged;
use Enova\EntitiesBundle\Entity\Tags;

class ORMListener
{
    protected $extra_update;

    public function __construct($container)
    {
        $this->container    = $container;
        $this->extra_update = false;
    }

    public function onFlush(OnFlushEventArgs $args)
    {
        $securityContext = $this->container->get('security.context');
        $em              = $args->getEntityManager();

        $uow             = $em->getUnitOfWork();
        $cmf             = $em->getMetadataFactory();

        foreach ($uow->getScheduledEntityInsertions() AS $entity)
        {
            $meta = $cmf->getMetadataFor(get_class($entity));

            $this->updateTagged($em, $entity);
        }

        foreach ($uow->getScheduledEntityUpdates() as $entity)
        {
            $meta = $cmf->getMetadataFor(get_class($entity));

            $this->updateTagged($em, $entity);
        }
    }

    public function updateTagged($em, $entity)
    {
      $entityTags = $entity->getTags();

      $a = array_shift($entityTags);
      //in my case, i have already sent the object from the form, but you could just replace this part for new Object() etc

      $uow      = $em->getUnitOfWork();
      $cmf      = $em->getMetadataFactory();
      $meta     = $cmf->getMetadataFor(get_class($a));

      $em->persist($a);

      $uow->computeChangeSet($meta, $a);
    }

}

Config.yml:

services:
    updated_by.listener:
        class: YourNamespace\EventListener\ORMListener
        arguments: [@service_container]
        tags:
            - { name: doctrine.event_listener, event: onFlush, method: onFlush }

希望它有所帮助;)

答案 3 :(得分:2)

jhoffrichter的解决方案非常有效。如果使用控制台命令,则应为事件command.terminate添加标记。否则它在Console命令中不起作用。见https://stackoverflow.com/a/19737608/1526162

config.yml

amq_messages_chain:
   class: Acme\StoreBundle\Listener\AmqMessagesChain

amqflush:
   class: Acme\StoreBundle\Listener\AmqFlush
   arguments: [ @doctrine.orm.entity_manager, @amq_messages_chain, @logger ]
   tags:
     - { name: kernel.event_listener, event: kernel.response, method: onResponse, priority: 5 }
     - { name: kernel.event_listener, event: command.terminate, method: onResponse }

doctrine.listener:
  class: Acme\StoreBundle\Listener\AmqListener
  arguments: [ @logger, @amq_messages_chain ]
  tags:
    - { name: doctrine.event_listener, event: postPersist }
    - { name: doctrine.event_listener, event: postUpdate }
    - { name: doctrine.event_listener, event: prePersist }

答案 4 :(得分:0)

在刷新事件之后,请勿致电$em->flush()

答案 5 :(得分:-1)

从Doctrine 2.2开始,您可以将监听器附加到postFlush事件:

function postFlush($args) {
    $em = $args->getEntityManager();
    foreach ($em->getUnitOfWork()->getScheduledEntityInsertions() as $entity) {
        if ($entity instanceof FeedItemInterface) {
            $feed = new FeedEntity;
            ...
            $em->persist($feed);
        }
    }
    $em->flush();
}