Search code examples
symfonydoctrine-ormsymfony-sonata

Doctrine inserting in postPersist event


I want add new Feed item on entity persist and update. I write this event listener (postUpdate is same):

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

But I got

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

and in log a have two insertations:

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

scientificdirection is Many to Many relationship table for entity what we want to persist. In frontend application everything work fine, but in Sonata Admin I got this problem :(


Solution

  • If you need to persist additional objects, the postPersist or postUpdate handler in Doctrine is, sadly, not the right place to go. I struggled with the same problem today, as I needed to generate some message entries in that handler.

    The problem at this point is that the postPersist handler is called during the flush event, and not after. So you can't persist additional objects here, as they are not getting flushed afterwards. Additionally, you can't call flush during an postPersist handler, as this might lead to ducplicate entries (as you have experienced).

    One way to go is using the onFlush handler from doctrine, documented here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/events.html#onflush

    This is just problematic if you need the inserted ids of the database object, as the entity hasn't yet been written to the database in that handler. If you don't need those ids, you are fine with the onFlush event in doctrine.

    For me, the solution was a little different. I'm currently working on a symfony2 project, and needed the ids of the inserted database objects (for callbacks and updates later on).

    I created a new service in symfony2, which basically just acts like a queue for my messages. During the postPersist update, I just fill the entries in the queue. I have another handler registered on kernel.response, which then takes those entries and persists them to the database. (Something along the line of this: http://symfony.com/doc/current/cookbook/service_container/event_listener.html)

    I hope I don't digress too much from the topic here, but as it is something I really struggled with, I hope that some people might find this useful.

    The service entries for this are:

     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 }
    

    You can't use the doctrine.listener for this, as this leads to a circular dependency (as you need the entity manager for the service, but the entity manager needs the service....)

    That worked like a charm. If you need more info on that, don't hesitate to ask, I'm glad to add some examples to this.