Подтвердить что ты не робот

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

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

и в log a имеют две вставки:

INSERT INTO interview_scientificdirection (interview_id, sciencedirection_id) ЦЕННОСТИ (?,?) ([30,2]) INSERT INTO interview_scientificdirection (interview_id, sciencedirection_id) ЦЕННОСТИ (?,?) ([30,2])

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

4b9b3361

Ответ 1

С Doctrine 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();
}

Ответ 2

Ответ от Francesc неверен, поскольку изменения в событии 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();
    }
}

Ответ 3

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

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

Ответ 4

Ну, как я это делал в 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 }

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

Ответ 5

Решение от jhoffrichter работает очень хорошо. Если вы используете консольные команды, вы должны добавить тег для события command.terminate. В противном случае он не будет работать внутри команд консоли. См. fooobar.com/info/402620/...

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 }