web-dev-qa-db-fra.com

Doctrine insérant dans l'événement postPersist

Je veux ajouter un nouvel élément de fil sur l'entité persister et mettre à jour. J'écris cet écouteur d'événement (postUpdate est identique):

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

Mais j'ai eu 

Violation de contrainte d'intégrité: 1062 Entrée en double '30 -2 'pour la clé ' PRIMARY '

et dans le log a avoir deux insertions:

INSERT INTO interview_scientificdirection (interview_id, Scientificdirection_id) VALEURS (?,?) ([30,2]) INSERT INTO Interview_scientificdirection (interview_id, scientificdirection_id) VALEURS (?,?) ([30,2])

scientificdirection est une table de relations Many to Many pour les entités que nous voulons conserver. Dans l'application frontale, tout fonctionne correctement, mais dans Sonata Admin, j'ai ce problème :(

14
nucleartux

La réponse de Francesc est fausse, car les changesets de l'événement postFlush sont déjà vides. La deuxième réponse de jhoffrichter pourrait fonctionner, mais est excessive. La bonne façon de procéder est de conserver l'entité dans l'événement postPersist et d'appeler flush à nouveau dans l'événement postFlush. Mais vous ne devez le faire que si vous avez changé quelque chose dans l'événement postPersist, sinon vous créez une boucle sans fin.

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

Si vous devez conserver des objets supplémentaires, le gestionnaire postPersist ou postUpdate de Doctrine n'est malheureusement pas l'endroit idéal. J'ai eu du mal à faire face au même problème aujourd'hui, car je devais générer des entrées de message dans ce gestionnaire.

Le problème à ce stade est que le gestionnaire postPersist est appelé pendant l'événement de vidage, et non après. Donc, vous ne pouvez pas conserver d'autres objets ici, car ils ne seront pas vidés par la suite. De plus, vous ne pouvez pas appeler un flush pendant un gestionnaire postPersist, car cela pourrait conduire à des doublons (comme vous en avez fait l'expérience).

Une solution consiste à utiliser le gestionnaire onFlush from doctrine, documenté ici: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#onflush

Cela pose problème si vous avez besoin des identifiants insérés de l'objet de base de données, car l'entité n'a pas encore été écrite dans la base de données dans ce gestionnaire. Si vous n'avez pas besoin de ces identifiants, l'événement ofFlush dans la doctrine vous convient.

Pour moi, la solution était un peu différente. Je travaille actuellement sur un projet symfony2 et j'avais besoin des identifiants des objets de base de données insérés (pour les rappels et les mises à jour ultérieurement).

J'ai créé un nouveau service dans symfony2, qui agit fondamentalement comme une file d'attente pour mes messages. Lors de la mise à jour postPersist, je viens de remplir les entrées de la file d'attente J'ai un autre gestionnaire enregistré sur kernel.response, qui prend ensuite ces entrées et les conserve dans la base de données. (Quelque chose du genre: http://symfony.com/doc/current/cookbook/service_container/event_listener.html )

J'espère ne pas trop m'éloigner du sujet ici, mais comme c'est quelque chose avec lequel j'ai vraiment eu du mal, j'espère que certaines personnes pourront trouver cela utile.

Les entrées de service pour ceci sont:

 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 }

Vous ne pouvez pas utiliser le doctrine.listener pour cela, car cela conduit à une dépendance circulaire (car vous avez besoin du gestionnaire d'entités pour le service, mais le gestionnaire d'entités a besoin du service ...).

Cela a fonctionné comme un charme. Si vous avez besoin de plus d'informations à ce sujet, n'hésitez pas à demander, je suis heureux d'ajouter quelques exemples à cela.

26
jhoffrichter

Eh bien, voici ce que j'ai fait dans SF 2.0 et 2.2:

Classe d'écoute:

<?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 }

J'espère que ça aide ;)

2
Marco

La solution de jhoffrichter fonctionne très bien. Si vous utilisez les commandes de la console, vous devez ajouter une balise pour l'événement command.terminate. Sinon, cela ne fonctionne pas dans les commandes de la console. Voir 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 }
2
Stefan Bergfeld