18

我想在实体上添加新的 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 键 'PRIMARY' 的重复条目 '30-2'

并且在日志 a 中有两个插入:

INSERT INTO interview_scientificdirection (interview_id, sciencedirection_id) VALUES (?, ?) ([30,2]) INSERT INTO interview_scientificdirection (interview_id, sciencedirection_id) VALUES (?, ?) ([30,2])

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

4

4 回答 4

33

如果您需要持久化其他对象,遗憾的是,Doctrine 中的 postPersist 或 postUpdate 处理程序不是正确的选择。我今天遇到了同样的问题,因为我需要在该处理程序中生成一些消息条目。

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

一种方法是使用来自学说的 onFlush 处理程序,记录在这里:https ://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/events.html#onflush

如果您需要插入数据库对象的 id,这只是有问题,因为实体尚未在该处理程序中写入数据库。如果您不需要这些 id,则可以使用 onFlush 事件。

对我来说,解决方案有点不同。我目前正在开发一个 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,因为这会导致循环依赖(因为您需要服务的实体管理器,但实体管理器需要服务......)

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

于 2012-06-15T15:48:54.530 回答
31

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();
    }
}
于 2013-05-21T01:53:54.123 回答
3

jhoffrichter 的解决方案运行良好。如果您使用控制台命令,您应该为事件 command.terminate 添加一个标签。否则它在控制台命令中不起作用。见https://stackoverflow.com/a/19737608/1526162

配置.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 }
于 2013-11-02T18:04:45.060 回答
2

Well, heres how i have done in SF 2.0 and 2.2:

Listener class:

<?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 }

Hope it helps ;)

于 2013-05-28T18:48:43.353 回答