Вопрос по symfony – Вставка доктрины в событие postPersist

15

Я хочу добавить новый элемент в ленту при сохранении и обновлении объекта. Я пишу слушатель этого события (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();
    }
}

Но я получил

Integrity constraint violation: 1062 Duplicate entry '30-2' for key 'PRIMARY'

и в логе есть две вставки:

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

Научное направление - это таблица отношений «многие ко многим» для сущности, которую мы хотим сохранить. В приложении frontend все работает нормально, но в Sonata Admin у меня появилась такая проблема :(

Ваш Ответ

5   ответов
27

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

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

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

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

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

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

Вы хотите сказать, чтобы создать запись в событии и знаете его ID нужной очереди сообщений? nucleartux
Это интересное решение, и я тоже могу его использовать. В моем случае я использую события жизненного цикла Doctrine для обнаружения изменений в свойствах нескольких объектов, а затем регистрирую их, создавая новый объект журнала и пытаясь сохранить его в базе данных. & Quot; & Quot очереди; журналы, которые мне нужно сделать, а затем сохранить их в событии ядра, должны работать хорошо.
Да, вы меня не так поняли :) Могу ли я создать новую сущность в событии, если мне нужен Id сущности, что происходит в этом событии без программного обеспечения очереди сообщений? nucleartux
Нет, мне нужны были идентификаторы объекта, который вот-вот будет сохранен в обработчике onFlush. А поскольку единица работы только готовится, у вас нет идентификаторов сущностей, которые будут сброшены после завершения обработчика onFlush. Или я неправильно понял ваш вопрос?
@jhoffrichter why are you attaching your listener to a request event? What if this code is executed in a console? Have you tried with Doctrine's postFlush? – Francesc Rosas Jun 27 '12 at 17:01
27

так как наборы изменений в событии postFlush уже пусты. Второй ответ от jhoffrichter может сработать, но это излишне. Правильный путь - сохранить сущность в событии postPersist и снова вызвать flush в событии 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();
    }
}
Это нехорошо, потому что таким образом вы совершите две разные транзакции. Следовательно, только один из них может потерпеть неудачу, оставив только одну сущность. То есть - если вы явно не делаете $ em-> gt; beginTransaction ();
Это хорошо работает - спасибо, что поделились!
Эй, Крис, я не пытаюсь быть ненавистником, но Учениеdocumentation говорит, что EntityManager # flush () нельзя безопасно вызывать внутри слушателей [postFlush & reg;]. Что вы думаете об этом?
@chris, как сказал выше Ян, flush () внутри postFlush () не является безопасным. Это действительно работает? Мне кажется, что это не так.
2

сольные команды, вы должны добавить тег для события 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 }
-1

tFlush:

function postFlush($args) {
    $em = $args->getEntityManager();
    foreach ($em->getUnitOfWork()->getScheduledEntityInsertions() as $entity) {
        if ($entity instanceof FeedItemInterface) {
            $feed = new FeedEntity;
            ...
            $em->persist($feed);
        }
    }
    $em->flush();
}
Я не могу найти какую-либо ссылку в документации, я нашел ее в коде (UnitOfWork::commit()). Эти события запускаются в самом начале и в конце сброса, поэтому его можно безопасно сбросить снова (я пытался).
Спасибо! Можете ли вы дать мне ссылку на документацию об этом событии? В этом методе действительно допускается промывка? nucleartux
Обратите внимание, чтоthe postFlush docs явно сказать, что вы не должны использовать flush () из postFlush ()
@AnthonyTopper, если вы вставите какой-либо объект послеpostPersist, чтениеUnitOfWork::commit() source code очень помогает понять рабочий процесс.
Имеет ли postFlush что-нибудь в getScheduledEntityInsertions (). Если это произойдет, я не нахожу это. Это пусто для меня; Я думаю, потому что вставки уже произошли.
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 }

Надеюсь, поможет ;)

Похожие вопросы