在 postPersist 事件中插入教义

Posted

技术标签:

【中文标题】在 postPersist 事件中插入教义【英文标题】:Doctrine inserting in postPersist event 【发布时间】:2012-05-09 14:09:58 【问题描述】:

我想在实体持久化和更新上添加新的 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, sciencedirection_id) 值 (?, ?) ([30,2]) 插入 interview_scientificdirection (interview_id, sciencedirection_id) 值 (?, ?) ([30,2])

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

【问题讨论】:

【参考方案1】:

如果您需要持久化其他对象,遗憾的是,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,因为这会导致循环依赖(因为您需要服务的实体管理器,但实体管理器需要服务......)

这就像一个魅力。如果您需要这方面的更多信息,请随时提出,我很高兴为此添加一些示例。

【讨论】:

你想在事件中创建一条记录并知道它需要ID消息队列吗? 不,我需要将要在 onFlush 处理程序中持久化的实体的 ID。并且由于工作单元刚刚准备好,您没有在 onFlush 处理程序完成后将被刷新的实体的 ID。还是我误解了你的问题? 是的,你误会了我的意思 :) 如果我需要实体的 ID,我可以在事件中创建新实体吗?如果没有消息队列软件会发生此事件吗? @jhoffrichter 为什么要将侦听器附加到请求事件?如果这段代码在控制台中执行怎么办?你试过 Doctrine 的 postFlush 吗? 这是一个有趣的解决方案,我也可以使用它。在我的例子中,我使用 Doctrine 生命周期事件来检测对多个实体的属性的更改,然后通过创建一个新的日志实体并尝试将其持久化到数据库来记录它们。 “排队”我需要制作的日志,然后将它们保存在内核事件中应该可以正常工作。【参考方案2】:

jhoffrichter 的解决方案运行良好。如果您使用控制台命令,您应该为事件 command.terminate 添加一个标签。否则它在控制台命令中不起作用。见https://***.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 

【讨论】:

【参考方案3】:

嗯,这就是我在 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 

希望对你有所帮助;)

【讨论】:

【参考方案4】:

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();
    

【讨论】:

这不好,因为这样 - 您将进行两个不同的交易。因此,其中只有一个可能会失败,只留下一个实体。那就是 - 除非你明确地做 $em->beginTransaction(); 嘿@chris,我不是想成为一个仇恨者,但 documentation 教义说“EntityManager#flush() 不能在 [postFlush's] 的侦听器中安全地调用。”您对此有何看法? @chris,正如 Ian 上面所说, postFlush() 中的 flush() 是不安全的。这真的有效吗?在我看来,它没有。

以上是关于在 postPersist 事件中插入教义的主要内容,如果未能解决你的问题,请参考以下文章

教义事件,更新持久化实体

休眠错误在@PostPersist 方法中保留一个实体

jpa @postpersist @postupdate 仅在事务提交后

在教义中只加入一行

教义坚持+坚持=插入+插入?

使用教义插入查询