2012-04-29 3 views
13

Я хочу добавить новый элемент фида на сущность и обновить. Я пишу этот слушатель событий (postUpdate такого же):Вставка доктрины в событие 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); 
     $em->flush(); 
    } 
} 

Но я получил

Integrity нарушение ограничения: 1062 Дублированного запись '30 -2' для ключа 'PRIMARY'

и в журнале a имеют две вставки:

INSERT INTO interview_scientificdire фикция (interview_id, scientificdirection_id) VALUES (?,?) ([30,2]) INSERT INTO interview_scientificdirection (interview_id, scientificdirection_id) VALUES (?,?) ([30,2])

scientificdirection это таблица отношений Many to Many для сущности, которую мы хотим сохранить. Во внешнем приложении все работает нормально, но в Sonata Admin У меня возникла эта проблема :(

ответ

1

С доктриной 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(); 
} 
+0

Thx! Можете ли вы дать мне ссылку на документацию об этом событии? В этом методе действительно разрешен флеш? – nucleartux

+0

Я не могу найти ссылку в документации, я нашел ее в коде ([UnitOfWork :: commit()] (https://github.com/doctrine/doctrine2/blob/master/lib/Doctrine/ORM/ UnitOfWork.php # L245)). Эти события запускаются в самом начале и в конце флеша, поэтому его можно снова сбросить (я попытался). –

+1

Имеет ли postFlush что-нибудь в getScheduledEntityInsertions(). Если это так, я не нахожу его. Это пусто для меня; Я думаю, потому что вставки уже произошли. –

26

Если вам нужно сохранить дополнительные объекты, обработчик postPersist или postUpdate в Doctrine, к сожалению, не подходит для правильного места. с той же самой проблемой сегодня, как мне нужно, чтобы сгенерировать записи сообщений в этом обработчике.

проблема на данный момент является то, что обработчик postPersist называется во флеш событий, а не после. Таким образом, вы не можете сохраняйте дополнительные объекты здесь, так как они не будут очищаться после этого. Кроме того, вы не можете вызывать флеш во время обработчика postPersist, поскольку это может привести к записи в ducplicate (как вы уже испытали).

Один путь использует обработчик onFlush от доктрины, описываемые здесь: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#onflush

Это просто проблематично, если вам нужно вставленные идентификаторы объекта базы данных, поскольку объект еще не был написан на базы данных в этом обработчике. Если вам не нужны эти идентификаторы, вы в порядке с событиемFlush в доктрине.

Для меня решение было немного иным. В настоящее время я работаю над проектом symfony2 и нуждаюсь в идентификаторах вставленных объектов базы данных (для обратных вызовов и обновлений позже).

Я создал новую службу в 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 для этого, так как это приводит к круговой зависимости (как вам нужно диспетчер сущностей для службы, но менеджер объект нуждается в обслуживании ....)

Это работало как шарм. Если вам нужна дополнительная информация об этом, не стесняйтесь спрашивать, я рад добавить к этому несколько примеров.

+0

Вы хотите сказать, чтобы создать запись в событии и знают, что нужно ID очереди сообщений? – nucleartux

+0

Нет, мне нужны идентификаторы сущности, которая вот-вот останется в обработчике onFlush. И поскольку подразделение работы только готовится, у вас нет идентификаторов объектов, которые будут очищаться после завершения обработчика onFlush. Или я не понял ваш вопрос? – jhoffrichter

+0

Да, вы меня неправильно поняли :) Могу ли я создать новый объект в случае, если мне нужен идентификатор сущности, что происходит с этим событием без программного обеспечения для очередей сообщений? – nucleartux

26

Ответ от Франческа не так, как в ревизиях случае postFlush уже пусты. Второй ответ jhoffrichter мог бы сработать, но он излишний. Правильный способ - сохранить объект в событии postPersist и снова вызвать флеш в событии postFlush. Но вы должны сделать это, только если вы что-то изменили в событии 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(); 
    } 
} 
+4

Это хорошо работает - спасибо за обмен! – caponica

+0

Это нехорошо, потому что таким образом - вы сделаете две разные транзакции. Следовательно, только один из них может не выходить из одного объекта. То есть - если вы явно не делаете $ em-> beginTransaction(); –

+9

Эй, @chris, я не пытаюсь быть ненавистником, но Доктрина [документация] (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html# postflush) говорит, что «EntityManager # flush() НЕ может быть безопасно вызван внутри [postFlush] слушателей». Что вы думаете об этом? –

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 } 

Надеется, что это помогает;)

2

Решение от jhoff richter работает очень хорошо. Если вы используете консольные команды, вы должны добавить тег для события 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 }