Doctrine 2 - changements Log dans la relation manyToMany

j'utilise exploitables d'ici comportementale extension journal des changements dans mes entités. Je veux aussi enregistrer les changements dans les relations de manyToMany. Je veux montrer à l'utilisateur ce genre de journal des modifications:

+--------------------------------------------------+
| Article "My Article" change log:                 |
+-------+------------+-----------------------------+
| Who   | When       | What                        |
+-------+------------+-----------------------------+
| Admin | 2015-07-01 | Removed tags "tag1", "tag2" |
| Admin | 2015-07-01 | Added tags "tag3"           |
+-------+------------+-----------------------------+

Événement problème

je pense la Doctrine n'a pas de déclencher des événements lors de la relation manyToMany changements, donc Loggable (listening doctrine events) ne sauve pas l'entrée de journal. Je peux travailler autour de lui en créant ma propre table manyToMany, Mais voici le deuxième problème:

ManyToMany problème

quand je crée entité représentant ManyToMany relation sans @ JoinTable annotation, Je ne sais pas, comment écrire la nouvelle entité pour se comporter comme l'ancienne JoinTable. Je ne veux pas de BC break. Pouvez-vous me donner un indice, comment la Doctrine gère cela?

avez-vous une recommandation, comment enregistrer les changements dans les relations manyToMany?

13
demandé sur Community 2015-08-07 09:27:44

1 réponses

Solution sans créer vos propres tables de jointure.

j'ai modifié le LoggableListener que j'ai créé pour outrepasser le LoggableListener Gedmo, ma version fonctionne, jouer avec ce jusqu'à ce que vous obtenez il fonctionne.

fondamentalement, étendre le Gedmo LoggableListener avec votre propre version et outrepasser / ajouter quelques fonctions modifiées:

prePersistLogEntry est activé pour vous permettre de modifier la logEntry si vous le souhaitez. Mes entités logEntry contiennent une entité utilisateur et le nom complet des utilisateurs au lieu de leur nom d'utilisateur.

getCollectionsChangeSetData est une nouvelle fonction pour extraire la collection et obtenir l'accès à la Doctrine méthodes de Collectionspersistantes. [http://www.doctrine-project.org/api/orm/2.1/class-Doctrine.ORM.PersistentCollection.html]

stripCollectionArray nouvelle fonction pour extraire les informations désirées des entités de collecte et les insérer dans un tableau php pour la persistance de la LogEntry.

pour information, Si vous prévoyez d'utiliser la fonctionnalité revert de L'extension de doctrine Loggable, vous devrez également étendre et annuler la méthode revert dans le répertoire LogEntryRepository. La méthode revert actuelle ne reconnaîtra pas l'id des associations ManyToMany sauvegardées dans la LogEntry. C'est pourquoi la fonction stripCollectionArray sauvegarde également les valeurs 'id' et 'class' dans la LogEntry.

bon Chance.

<?php

namespace AppBundle\Listener;

use Doctrine\Common\EventArgs;
use Gedmo\Loggable\Mapping\Event\LoggableAdapter;
use Gedmo\Tool\Wrapper\AbstractWrapper;
use Gedmo\Loggable\LoggableListener as GedmoLoggableListener;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use AppBundle\Entity\Clause;
use AppBundle\Entity\GuidanceNote;
use AppBundle\Entity\Standard;
use Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry;
use Doctrine\ORM\PersistentCollection;

/**
 * Loggable listener
 *
 * Extends the Gedmo loggable listener to provide some custom functionality.
 *
 *
 * @author Mark Ogilvie <mark.ogilvie@specshaper.com>
 */
class LoggableListener extends GedmoLoggableListener {

    // Token storage to get user
    private $tokenStorage;

    // Injet token storage in the services.yml
    public function __construct(TokenStorageInterface $token) {
        $this->tokenStorage = $token;
    }

    /**
     * Manipulate the LogEntry entity prior to persisting. 
     * In this case add a user, and set entity information
     * according to the custom entity family group.
     * 
     * @param EventArgs $eventArgs
     *
     * @return void
     */
    protected function prePersistLogEntry($logEntry, $object) {

        $user = $this->tokenStorage->getToken()->getUser();

        $logEntry instanceof AbstractLogEntry;

        $logEntry
                ->setUser($user)
                ->setChangedObject('text.default')
                ->setUsername($user->getFullName())
        ;

        switch (true) {
            case $object instanceof Clause:
                $logEntry->setChangedObject('text.clause')
                        ->setEntity($object)
                ;
                break;
            case $object instanceof GuidanceNote:
                $logEntry->setChangedObject('text.guidanceNote')
                        ->setEntity($object->getClause())
                ;
                break;
            case $object instanceof Standard:
                $logEntry->setChangedObject('text.standard')
                ;
                break;
        }
    }

    /**
     * Returns an objects changeset data
     * 
     * Modified to create an array which has old and new values instead
     * of just the new.
     * 
     * Also added reference to UoW collection changes to pick up ManyToMany
     * relationships
     *
     * @param LoggableAdapter $ea
     * @param object $object
     * @param object $logEntry
     *
     * @return array
     */
    protected function getObjectChangeSetData($ea, $object, $logEntry) {
        $om = $ea->getObjectManager();
        $wrapped = AbstractWrapper::wrap($object, $om);
        $meta = $wrapped->getMetadata();
        $config = $this->getConfiguration($om, $meta->name);
        $uow = $om->getUnitOfWork();

        // Define an array to return as the change set data.
        $returnArray = array();

        foreach ($ea->getObjectChangeSet($uow, $object) as $field => $changes) {
            if (empty($config['versioned']) || !in_array($field, $config['versioned'])) {
                continue;
            }

            $value = $changes[1];
            if ($meta->isSingleValuedAssociation($field) && $value) {
                if ($wrapped->isEmbeddedAssociation($field)) {
                    $value = $this->getObjectChangeSetData($ea, $value, $logEntry);
                } else {
                    $oid = spl_object_hash($value);
                    $wrappedAssoc = AbstractWrapper::wrap($value, $om);
                    $value = $wrappedAssoc->getIdentifier(false);
                    if (!is_array($value) && !$value) {
                        $this->pendingRelatedObjects[$oid][] = array(
                            'log' => $logEntry,
                            'field' => $field,
                        );
                    }
                }
            }

            $returnArray[$field]['previous'] = $changes[0];
            $returnArray[$field]['new'] = $value;
        }

        // For each collection add it to the return array in our custom format.
        foreach ($uow->getScheduledCollectionUpdates() as $col) {
            $associations = $this->getCollectionChangeSetData($col);
            $returnArray = array_merge($returnArray, $associations);
        }   

        return $returnArray;
    }

    /**
     * New custom function to get information about changes to entity relationships
     * Use the PersistentCollection methods to extract the info you want.
     * 
     * @param PersistentCollection $col
     * @return array
     */
    private function getCollectionChangeSetData(PersistentCollection $col) {

        $fieldName = $col->getMapping()['fieldName'];

        // http://www.doctrine-project.org/api/orm/2.1/class-Doctrine.ORM.PersistentCollection.html
        // $col->toArray() returns the onFlush array of collection items;
        // $col->getSnapshot() returns the prePersist array of collection items
        // $col->getDeleteDiff() returns the deleted items
        // $col->getInsertDiff() returns the inserted items
        // These methods return persistentcollections. You need to process them to get just the title/name
        // of the entity you want.
        // Instead of creating two records, you can create an array of added and removed fields.
        // Use private a newfunction stripCollectionArray to process the entity into the array

        $newValues[$fieldName]['new'] = $this->stripCollectionArray($col->toArray());
        $newValues[$fieldName]['previous'] = $this->stripCollectionArray($col->getSnapshot());

        return $newValues;
    }

    /**
     * Function to process your entity into the desired format for inserting
     * into the LogEntry
     * 
     * @param type $entityArray
     * @return type
     */
    private function stripCollectionArray($entityArray) {
        $returnArr = [];
        foreach ($entityArray as $entity) {
            $arr = [];
            $arr['id'] = $entity->getId();
            $arr['class'] = get_class($entity);

            if (method_exists($entity, 'getName')) {
                $arr['name'] = $entity->getName();
            } elseif (method_exists($entity, 'getTitle')) {
                $arr['name'] = $entity->getTitle();
            } else {
                $arr['name'] = get_class($entity);
            }
            $returnArr[] = $arr;
        }


        return $returnArr;
    }

}
6
répondu mark 2015-10-03 00:12:50