2017-07-10 2 views
0

J'ai besoin de créer un journal des modifications dans l'API pour les actions utilisateur sur les entités.Enregistrement du résultat getEntityChangeSet() dans Doctrine EventListener

Par exemple:

entité des mises à jour de l'utilisateur Concédant je dois attraper les changements et les enregistrer dans la base de données différente table.

La première partie, j'ai pu faire avec la doctrine de l'événement Listener

class ChangelogEventListener 
{ 
    public function preUpdate($obj, PreUpdateEventArgs $eventArgs) 
    { 
     if ($obj instanceof LoggableInterface) { 
      dump($eventArgs->getEntityChangeSet()); 
     } 
    } 
} 

Et avec les auditeurs d'événements d'entité de marquage

/** 
* @ORM\EntityListeners(value={"AppBundle\EventSubscriber\Changelogger\ChangelogEventListener"}) 
*/ 
class Licensor implements LoggableInterface 

Mais je ne sais pas s'il est même possible et si elle fait sens d'accéder au gestionnaire d'entités ORM dans un événement preUpdate.

Si ce n'est pas le cas, quelle est la bonne façon de le faire?

J'ai essayé avec EventListener de Symfony au lieu de Doctrine mais je n'ai pas accès à getEntityChangeSet().

Répondre

1

Découvrez Doctrine events, et plus précisément le preUpdate event. Cet événement est le plus restrictif, mais vous avez accès à tous les champs qui ont changé et à leurs anciennes/nouvelles valeurs. Vous pouvez modifier les valeurs ici sur l'entité en cours de mise à jour, sauf si c'est une entité associée.

Extrayez this answer, ce qui suggère d'utiliser un event subscriber, puis de persister dans une entité de journalisation.

Il est également this blog post qui utilise l'événement preUpdate pour sauver un tas de changesets à la classe d'auditeur interne, puis postFlush il persiste des entités qui sont en cours de modification, et appelle à nouveau flush. Cependant, je ne recommande ce, comme Doctrine documentation explicitly states:

postFlush est appelé à la fin de EntityManager # flush(). EntityManager # flush() ne peut PAS être appelé en toute sécurité à l'intérieur de ses écouteurs.

Si vous êtes allé la route de ce billet de blog que vous seriez mieux utiliser l'événement onFlush() puis faire appel computeChangeSets() après votre persist(), comme la première réponse que j'ai posté.

Vous pouvez trouver un semblable example here:

+0

Puis-je appeler $ uow-> getEntityChangeSet(); à l'intérieur de la méthode onFlush est-il sécuritaire? – Robert

+0

Vous devriez pouvoir oui. –

+1

J'ai été capable de le faire sur les événements onFlush et postFlush parce que lorsque les objets ont été créés parce que je n'utilise pas uuid, je n'ai pas d'identifiants dans la méthode onFlush. J'accepterai votre réponse comme indiqué sur la bonne solution. Merci. – Robert

1

Vous feriez mieux d'utiliser un écouteur d'événement pour une telle chose. Ce que vous voulez ressembler plus à un déclencheur de base de données pour enregistrer les modifications. Voir l'exemple ci-dessous (testé et fonctionne bien) qui enregistre User entité change dans UserAudit entité. À des fins de démonstration, il ne regarde que les champs username et password mais vous pouvez le modifier comme vous le souhaitez.

Remarque: Si vous voulez un écouteur d'entité , consultez this example.

services.yml

services: 
    application_backend.event_listener.user_entity_audit: 
     class: Application\BackendBundle\EventListener\UserEntityAuditListener 
     arguments: [ @security.context ] 
     tags: 
      - { name: doctrine.event_listener, event: preUpdate } 
      - { name: doctrine.event_listener, event: postFlush } 

UserEntityAuditListener

namespace Application\BackendBundle\EventListener; 

use Application\BackendBundle\Entity\User; 
use Application\BackendBundle\Entity\UserAudit; 
use Doctrine\ORM\Event\PostFlushEventArgs; 
use Doctrine\ORM\Event\PreUpdateEventArgs; 
use Symfony\Component\Security\Core\SecurityContextInterface; 

class UserEntityAuditListener 
{ 
    private $securityContext; 
    private $fields = ['username', 'password']; 
    private $audit = []; 

    public function __construct(SecurityContextInterface $securityContextInterface) 
    { 
     $this->securityContext = $securityContextInterface; 
    } 

    public function preUpdate(PreUpdateEventArgs $args) // OR LifecycleEventArgs 
    { 
     $entity = $args->getEntity(); 

     if ($entity instanceof User) { 
      foreach ($this->fields as $field) { 
       if ($args->getOldValue($field) != $args->getNewValue($field)) { 
        $audit = new UserAudit(); 
        $audit->setField($field); 
        $audit->setOld($args->getOldValue($field)); 
        $audit->setNew($args->getNewValue($field)); 
        $audit->setUser($this->securityContext->getToken()->getUsername()); 

        $this->audit[] = $audit; 
       } 
      } 
     } 
    } 

    public function postFlush(PostFlushEventArgs $args) 
    { 
     if (! empty($this->audit)) { 
      $em = $args->getEntityManager(); 

      foreach ($this->audit as $audit) { 
       $em->persist($audit); 
      } 

      $this->audit = []; 
      $em->flush(); 
     } 
    } 
} 
+0

Je ne suis pas sûr de cette réponse, car il est mentionné sur la documentation Doctrine PostFlush: "EntityManager # flush() ne peut pas être appelé en toute sécurité à l'intérieur de ses écouteurs." – Robert

+0

Ce vidage supplémentaire est strictement pour vider votre entité d'audit, pas l'original. L'original est déjà vidé avant la chasse d'eau supplémentaire. Cette phrase particulière dans la documentation est superficielle et courte. Vous pouvez toujours essayer de voir comment cela fonctionne avant d'arriver à une conclusion. Exemple travaillé et fonctionne toujours parfaitement bien pour de nombreuses années pour moi. – BentCoder

+0

pourquoi n'utilisez-vous pas getEntityChangeSet? –