18

エンティティの永続化と更新に新しいフィードアイテムを追加したい。私はこのイベントリスナーを書きます(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」

ログには2つの挿入があります。

INSERT INTOインタビュー_scientificdirection(interview_id、scientificdirection_id)VALUES(?、?)([30,2])INSERT INTOインタビュー_scientificdirection(interview_id、scientificdirection_id)VALUES(?、?)([30,2])

科学的方向性は、私たちが維持したいエンティティの多対多の関係テーブルです。フロントエンドアプリケーションではすべてが正常に機能しますが、SonataAdminではこの問題が発生しました:(

4

4 に答える 4

33

追加のオブジェクトを永続化する必要がある場合、残念ながら、DoctrineのpostPersistまたはpostUpdateハンドラーは適​​切な場所ではありません。そのハンドラーでいくつかのメッセージエントリを生成する必要があったため、今日も同じ問題に苦労しました。

この時点での問題は、postPersistハンドラーがフラッシュイベントの後でではなく、フラッシュイベントの間に呼び出されることです。したがって、追加のオブジェクトは後でフラッシュされないため、ここで永続化することはできません。さらに、postPersistハンドラーの実行中にflushを呼び出すことはできません。これは、(経験したように)重複したエントリにつながる可能性があるためです。

進む方法の1つは、Doctrineの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

postFlushイベントのチェンジセットはすでに空であるため、Francescからの回答は間違っています。jhoffrichterの2番目の答えは機能する可能性がありますが、やり過ぎです。正しい方法は、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を参照してください

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 }
于 2013-11-02T18:04:45.060 に答える
2

さて、これが私がSF2.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 }

それが役に立てば幸い ;)

于 2013-05-28T18:48:43.353 に答える