web-dev-qa-db-fra.com

Comment vérifier si l'entité a changé dans Doctrine 2?

Je dois vérifier si une entité persistante a changé et doit être mise à jour dans la base de données . Ce que j'ai fait (et ne fonctionnait pas) était le suivant:

$product = $entityManager->getRepository('Product')->find(3);
$product->setName('A different name');

var_export($entityManager->getUnitOfWork()->isScheduledForUpdate($product));

Ce code imprime toujours false, j'ai également essayé de vider le flush avant de vérifier l'unité de travail, mais cela n'a pas fonctionné.

Quelqu'un a une suggestion?

24
eagleoneraptor

La première chose à vérifier, c'est que votre fonction setName est en train de faire quelque chose ($ this-> name = $ name ...). Si cela fonctionne déjà, vous pouvez définir un écouteur d'événement sur votre service.yml cela se déclenche lorsque vous appelez la couleur.

entity.listener:
  class: YourName\YourBundle\EventListener\EntityListener
  calls:
    - [setContainer,  ["@service_container"]]
  tags:
    - { name: doctrine.event_listener, event: onFlush }

Ensuite, vous définissez EntityListener

namespace YourName\YourBundle\EventListener;

use Doctrine\ORM\Event;
use Symfony\Component\DependencyInjection\ContainerAware;

class EntityListener extends ContainerAware
{   

    /**
     * Gets all the entities to flush
     *
     * @param Event\OnFlushEventArgs $eventArgs Event args
     */
    public function onFlush(Event\OnFlushEventArgs $eventArgs)
    {   
        $em = $eventArgs->getEntityManager();
        $uow = $em->getUnitOfWork();

        //Insertions
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            # your code here for the inserted entities
        }

        //Updates
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            # your code here for the updated entities
        }

        //Deletions
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
            # your code here for the deleted entities
        }
    }
}

Si vous avez besoin de savoir quelles entités sont modifiées, mais faites quelque chose avec elles après elles ont été enregistrées dans la base de données, stockez simplement les entités modifiées dans un tableau privé, puis définissez un événement onFlush qui récupère les entités. du tableau.

En passant, pour déclencher ce genre d’événements, vous devez ajouter les @ORM\HasLifecycleCallbacks sur l’entité.

24
Sergi

Vous pouvez également consulter l'événement PreUpdate , si vous devez accéder aux champs d'entité avec leurs anciennes et leurs nouvelles valeurs.

Voici un exemple tiré principalement du lien fourni:

<?php
class NeverAliceOnlyBobListener
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if ($eventArgs->getEntity() instanceof User) {
            if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
                $oldValue = $eventArgs->getOldValue('name');
                $eventArgs->setNewValue('name', 'Bob');
            }
        }
    }
}
11
Andrew Atkinson

Je n'avais pas besoin/ne voulais créer d'auditeurs pour mon cas, alors je me suis retrouvé

$product->setName('A different name');
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($product)) {
    // My entity has changed
}
11

Doctrine2 Docs. 17. Stratégies de suivi des modifications

Si vous utilisez un troisième formulaire (17.3. Notify) comme je le fais, vous pouvez tester si votre entité est modifiée en effectuant les opérations suivantes:

$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
$aChangeSet = $uow->getEntityChangeSet($oEntity);

Si rien n'a changé, il retournera un tableau vide.

6
Mikl

La question est assez ancienne, mais il se peut qu'un groupe de personnes soit confronté à ce problème d'un point de vue différent. La UnitOfWork fonctionne très bien, mais elle ne renvoie que le tableau des modifications. Cela peut être pénible lorsque quelqu'un ne sait pas réellement quels champs ont changé et veut simplement obtenir l'entité entière en tant qu'objet pour comparer $oldEntity et $newEntity. Même si le nom de l'événement est preUpdate si quelqu'un essaie d'extraire les données de la base de données comme suit:

$er->find($id);

l'entité retournée contiendra toutes les modifications . La solution de contournement est assez simple mais elle comporte quelques points d'ancrage:

public function preUpdate(Entity $entity, PreUpdateEventArgs $args)
{
    $entity = clone $entity; //as Doctrine under the hood 
                             //uses reference to manage entities you might want 
                             //to work on the entity copy. Otherwise,        
                             //the below refresh($entity) will affect both 
                             //old and new entity.
    $em = $args->getEntityManager();
    $currentEntity = $em->getRepository('AppBundle:Entity')->find($entity->getId());
    $em->refresh($currentEntity);

}

Pour ceux qui utilisent un autre événement, tel que preFlush, je l'ai rapidement vérifié et la solution de contournement n'a pas bien fonctionné, car la méthode refresh() ignore tous les changements de vidage. créez une option statique $alreadyFlushed pour éviter les références circulaires.

2
Mikołaj Król

Je suis d'accord avec @ Andrew Atkinson quand il a dit:

Vous voudrez peut-être aussi regarder l'événement PreUpdate , si vous avez besoin de accès aux champs d'entité avec leurs anciennes et nouvelles valeurs.

Mais je ne suis pas d'accord avec l'exemple qu'il a proposé. D'après mon expérience, il existe un meilleur moyen de vérifier si quelque chose a changé ou non.

<?php
class Spock
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if (!empty($eventArgs->getEntityChangeSet())) {
            // fill this how you see fit
        }
    }
}

De cette façon, le if ne sera déclenché que s’il ya un champ qui a changé ou non.

Pour savoir comment le faire si tel ou tel domaine était modifié, alors oui, je recommande sa solution.

0
Rafael

Je suis curieux de connaître Doctrine et tous ceux qui documentent postFlush, car dans certains cas, une transaction est en cours ... événement postFlush.

0
Tristan

En fonction de mes besoins, des réponses ici et de la documentation , j’ai proposé la solution suivante pour un horodatage modifiedAt dans une entité. 

/**
 * @Doctrine\ORM\Mapping\PreUpdate()
 *
 * @param \Doctrine\ORM\Event\PreUpdateEventArgs $args
 * @return $this
 */
public function preUpdateModifiedAt(\Doctrine\ORM\Event\PreUpdateEventArgs $args)
{
    $this->setModifiedAt(new \DateTime('now'));

    return $this;
}

Ceci est basé sur ce que les docs disent à propos de ceci Event par opposition aux autres disponibles, tels que PostPersist et PreFlush:

PreUpdate est l'événement le plus restrictif à utiliser, car il s'appelle juste avant qu'une instruction update soit appelée pour une entité à l'intérieur du fichier Méthode EntityManager # flush (). Notez que cet événement n'est pas déclenché lorsque le jeu de règles calculé est vide.

Utiliser PreUpdate par opposition aux autres vous permet de laisser tous les calculs et toutes les fonctions gourmandes en calculs au processus déjà défini par Doctrine. Déclencher manuellement le calcul des changesets, tel que dans cesréponses ci-dessus sont gourmands en ressources processeur. L'événement onFlush, tel qu'utilisé dans la réponse acceptée est une option (de la manière illustrée), mais pas si vous comptez sur la détection d'un changement apporté à l'entité, comme vous pouvez le faire avec la fonction ci-dessus (preUpdateModifiedAt(PreUpdateEventArgs $args)).

0
rkeet