Czy istnieje wbudowany sposób na uzyskanie wszystkich zmienionych / zaktualizowanych pól w jednostce Doctrine 2

Załóżmy, że odzyskuję encję {[1] } i modyfikuję jej stan za pomocą setterów:

$e->setFoo('a');
$e->setBar('b');

Czy istnieje możliwość odzyskania tablicy pól, które zostały zmienione?

W przypadku mojego przykładu chciałbym odzyskać foo => a, bar => b jako wynik

PS: tak, Wiem, że mogę zmodyfikować wszystkie Accesory i zaimplementować tę funkcję ręcznie, ale szukam jakiegoś poręcznego sposobu na zrobienie tego

Author: Jon Winstanley, 2012-01-30

7 answers

Możesz użyć Doctrine\ORM\EntityManager#getUnitOfWork Aby uzyskać Doctrine\ORM\UnitOfWork.

Następnie uruchom obliczenia zestawu zmian (działa tylko na zarządzanych obiektach) za pomocą Doctrine\ORM\UnitOfWork#computeChangeSets().

Możesz również użyć podobnych metod jak Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity), jeśli wiesz dokładnie, co chcesz sprawdzić bez iteracji na całym wykresie obiektu.

Następnie możesz użyć Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity), Aby pobrać wszystkie zmiany w swoim obiekcie.

Złożenie:

$entity = $em->find('My\Entity', 1);
$entity->setTitle('Changed Title!');
$uow = $em->getUnitOfWork();
$uow->computeChangeSets(); // do not compute changes if inside a listener
$changeset = $uow->getEntityChangeSet($entity);

Uwaga. Jeśli próbujesz uzyskać zaktualizowane pola wewnątrz preUpdate listener , nie przeliczaj zestawu zmian, jak to już zostało zrobione. Po prostu zadzwoń do getEntityChangeSet, aby uzyskać wszystkie zmiany wprowadzone do jednostki.

 128
Author: Ocramius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-12 16:49:30

Big beware sign dla tych, którzy chcą sprawdzić zmiany na encji za pomocą metody opisanej powyżej.

$uow = $em->getUnitOfWork();
$uow->computeChangeSets();

Metoda $uow->computeChangeSets() jest używana wewnętrznie przez procedurę persisting w sposób, który sprawia, że powyższe rozwiązanie jest bezużyteczne. To też jest napisane w komentarzach do metody: @internal Don't call from the outside. Po sprawdzeniu zmian w encjach za pomocą $uow->computeChangeSets(), na końcu metody wykonywany jest następujący fragment kodu (dla każdego zarządzanego encji):

if ($changeSet) {
    $this->entityChangeSets[$oid]   = $changeSet;
    $this->originalEntityData[$oid] = $actualData;
    $this->entityUpdates[$oid]      = $entity;
}

The $actualData tablica przechowuje bieżące zmiany właściwości encji. Gdy tylko zostaną one zapisane w $this->originalEntityData[$oid], te nie utrzymujące się jeszcze zmiany są uważane za pierwotne właściwości podmiotu.

Później, kiedy $em->persist($entity) zostanie wywołana w celu zapisania zmian w encji, to również dotyczy metody $uow->computeChangeSets(), ale teraz nie będzie w stanie znaleźć zmian w encji, ponieważ te jeszcze nie utrzymane zmiany są uważane za oryginalne właściwości encji.

 32
Author: Slavik Derevianko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-15 16:16:09

Sprawdź tę publiczną (a nie wewnętrzną) funkcję:

$this->em->getUnitOfWork()->getOriginalEntityData($entity);

Z doctrine repo :

/**
 * Gets the original data of an entity. The original data is the data that was
 * present at the time the entity was reconstituted from the database.
 *
 * @param object $entity
 *
 * @return array
 */
public function getOriginalEntityData($entity)

Wszystko co musisz zrobić to zaimplementować toArray lub serialize funkcję w swoim obiekcie i zrobić diff. Coś takiego:

$originalData = $em->getUnitOfWork()->getOriginalEntityData($entity);
$toArrayEntity = $entity->toArray();
$changes = array_diff_assoc($toArrayEntity, $originalData);
 19
Author: Mohamed Ramrami,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-22 11:03:40

Możesz śledzić zmiany za pomocą powiadom politykę .

Najpierw implementuje interfejsNotifyPropertyChanged :

/**
 * @Entity
 * @ChangeTrackingPolicy("NOTIFY")
 */
class MyEntity implements NotifyPropertyChanged
{
    // ...

    private $_listeners = array();

    public function addPropertyChangedListener(PropertyChangedListener $listener)
    {
        $this->_listeners[] = $listener;
    }
}

Następnie po prostu wywołaj _onPropertyChanged na każdej metodzie, która zmienia dane rzucaj swój podmiot jak poniżej:

class MyEntity implements NotifyPropertyChanged
{
    // ...

    protected function _onPropertyChanged($propName, $oldValue, $newValue)
    {
        if ($this->_listeners) {
            foreach ($this->_listeners as $listener) {
                $listener->propertyChanged($this, $propName, $oldValue, $newValue);
            }
        }
    }

    public function setData($data)
    {
        if ($data != $this->data) {
            $this->_onPropertyChanged('data', $this->data, $data);
            $this->data = $data;
        }
    }
}
 5
Author: manix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-09 00:50:19

Więc... co zrobić, gdy chcemy znaleźć zestaw zmian poza cyklem życia doktryny? Jak wspomniano w moim komentarzu do postu @Ocramius powyżej, być może jest możliwe stworzenie metody "readonly", która nie zadziera z rzeczywistą doktryną, ale daje użytkownikowi pogląd na to, co się zmieniło.

Oto przykład tego, o czym myślę...
/**
 * Try to get an Entity changeSet without changing the UnitOfWork
 *
 * @param EntityManager $em
 * @param $entity
 * @return null|array
 */
public static function diffDoctrineObject(EntityManager $em, $entity) {
    $uow = $em->getUnitOfWork();

    /*****************************************/
    /* Equivalent of $uow->computeChangeSet($this->em->getClassMetadata(get_class($entity)), $entity);
    /*****************************************/
    $class = $em->getClassMetadata(get_class($entity));
    $oid = spl_object_hash($entity);
    $entityChangeSets = array();

    if ($uow->isReadOnly($entity)) {
        return null;
    }

    if ( ! $class->isInheritanceTypeNone()) {
        $class = $em->getClassMetadata(get_class($entity));
    }

    // These parts are not needed for the changeSet?
    // $invoke = $uow->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
    // 
    // if ($invoke !== ListenersInvoker::INVOKE_NONE) {
    //     $uow->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($em), $invoke);
    // }

    $actualData = array();

    foreach ($class->reflFields as $name => $refProp) {
        $value = $refProp->getValue($entity);

        if ($class->isCollectionValuedAssociation($name) && $value !== null) {
            if ($value instanceof PersistentCollection) {
                if ($value->getOwner() === $entity) {
                    continue;
                }

                $value = new ArrayCollection($value->getValues());
            }

            // If $value is not a Collection then use an ArrayCollection.
            if ( ! $value instanceof Collection) {
                $value = new ArrayCollection($value);
            }

            $assoc = $class->associationMappings[$name];

            // Inject PersistentCollection
            $value = new PersistentCollection(
                $em, $em->getClassMetadata($assoc['targetEntity']), $value
            );
            $value->setOwner($entity, $assoc);
            $value->setDirty( ! $value->isEmpty());

            $class->reflFields[$name]->setValue($entity, $value);

            $actualData[$name] = $value;

            continue;
        }

        if (( ! $class->isIdentifier($name) || ! $class->isIdGeneratorIdentity()) && ($name !== $class->versionField)) {
            $actualData[$name] = $value;
        }
    }

    $originalEntityData = $uow->getOriginalEntityData($entity);
    if (empty($originalEntityData)) {
        // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
        // These result in an INSERT.
        $originalEntityData = $actualData;
        $changeSet = array();

        foreach ($actualData as $propName => $actualValue) {
            if ( ! isset($class->associationMappings[$propName])) {
                $changeSet[$propName] = array(null, $actualValue);

                continue;
            }

            $assoc = $class->associationMappings[$propName];

            if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
                $changeSet[$propName] = array(null, $actualValue);
            }
        }

        $entityChangeSets[$oid] = $changeSet; // @todo - remove this?
    } else {
        // Entity is "fully" MANAGED: it was already fully persisted before
        // and we have a copy of the original data
        $originalData           = $originalEntityData;
        $isChangeTrackingNotify = $class->isChangeTrackingNotify();
        $changeSet              = $isChangeTrackingNotify ? $uow->getEntityChangeSet($entity) : array();

        foreach ($actualData as $propName => $actualValue) {
            // skip field, its a partially omitted one!
            if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
                continue;
            }

            $orgValue = $originalData[$propName];

            // skip if value haven't changed
            if ($orgValue === $actualValue) {
                continue;
            }

            // if regular field
            if ( ! isset($class->associationMappings[$propName])) {
                if ($isChangeTrackingNotify) {
                    continue;
                }

                $changeSet[$propName] = array($orgValue, $actualValue);

                continue;
            }

            $assoc = $class->associationMappings[$propName];

            // Persistent collection was exchanged with the "originally"
            // created one. This can only mean it was cloned and replaced
            // on another entity.
            if ($actualValue instanceof PersistentCollection) {
                $owner = $actualValue->getOwner();
                if ($owner === null) { // cloned
                    $actualValue->setOwner($entity, $assoc);
                } else if ($owner !== $entity) { // no clone, we have to fix
                    // @todo - what does this do... can it be removed?
                    if (!$actualValue->isInitialized()) {
                        $actualValue->initialize(); // we have to do this otherwise the cols share state
                    }
                    $newValue = clone $actualValue;
                    $newValue->setOwner($entity, $assoc);
                    $class->reflFields[$propName]->setValue($entity, $newValue);
                }
            }

            if ($orgValue instanceof PersistentCollection) {
                // A PersistentCollection was de-referenced, so delete it.
    // These parts are not needed for the changeSet?
    //            $coid = spl_object_hash($orgValue);
    //
    //            if (isset($uow->collectionDeletions[$coid])) {
    //                continue;
    //            }
    //
    //            $uow->collectionDeletions[$coid] = $orgValue;
                $changeSet[$propName] = $orgValue; // Signal changeset, to-many assocs will be ignored.

                continue;
            }

            if ($assoc['type'] & ClassMetadata::TO_ONE) {
                if ($assoc['isOwningSide']) {
                    $changeSet[$propName] = array($orgValue, $actualValue);
                }

    // These parts are not needed for the changeSet?
    //            if ($orgValue !== null && $assoc['orphanRemoval']) {
    //                $uow->scheduleOrphanRemoval($orgValue);
    //            }
            }
        }

        if ($changeSet) {
            $entityChangeSets[$oid]     = $changeSet;
    // These parts are not needed for the changeSet?
    //        $originalEntityData         = $actualData;
    //        $uow->entityUpdates[$oid]   = $entity;
        }
    }

    // These parts are not needed for the changeSet?
    //// Look for changes in associations of the entity
    //foreach ($class->associationMappings as $field => $assoc) {
    //    if (($val = $class->reflFields[$field]->getValue($entity)) !== null) {
    //        $uow->computeAssociationChanges($assoc, $val);
    //        if (!isset($entityChangeSets[$oid]) &&
    //            $assoc['isOwningSide'] &&
    //            $assoc['type'] == ClassMetadata::MANY_TO_MANY &&
    //            $val instanceof PersistentCollection &&
    //            $val->isDirty()) {
    //            $entityChangeSets[$oid]   = array();
    //            $originalEntityData = $actualData;
    //            $uow->entityUpdates[$oid]      = $entity;
    //        }
    //    }
    //}
    /*********************/

    return $entityChangeSets[$oid];
}

Jest tutaj sformułowana jako metoda statyczna, ale może stać się metodą wewnątrz UnitOfWork...?

Nie jestem na bieżąco ze wszystkimi wewnętrzne doktryny, więc może przegapić coś, co ma efekt uboczny lub źle część tego, co ta metoda robi, ale (bardzo) szybki test to wydaje się dać mi wyniki oczekuję zobaczyć.

Mam nadzieję, że to komuś pomoże!
 1
Author: caponica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-22 22:47:45

W przypadku, gdy ktoś jest jeszcze zainteresowany innym sposobem niż zaakceptowana odpowiedź(nie działało to dla mnie i moim osobistym zdaniem okazało się bardziej messier niż ten sposób).

Zainstalowałem JMS Serializer Bundle i na każdym obiekcie i na każdej właściwości, które uważam za zmianę, dodałem @Group ({"changed_entity_group"}). W ten sposób, mogę następnie zrobić serializację pomiędzy starą encją, a zaktualizowaną encją, a potem jest to tylko kwestia powiedzenia $oldJson == $updatedJson. Jeśli właściwości, które Cię interesują lub które chcesz rozważyć zmiany, JSON nie będzie taki sam i jeśli chcesz nawet zarejestrować to, co konkretnie się zmieniło, możesz przekształcić go w tablicę i wyszukać różnice.

Użyłem tej metody, ponieważ byłem zainteresowany głównie kilkoma właściwościami grupy Bytów, a nie w całości. Przykład, w którym byłoby to przydatne, to jeśli masz @ PrePersist @PreUpdate i masz last_update data, która będzie zawsze aktualizowana, dlatego zawsze otrzymasz, że jednostka została zaktualizowana za pomocą jednostki pracy i podobnych rzeczy.

Mam nadzieję, że ta metoda jest pomocna dla każdego.

 1
Author: Benjamin Vison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-22 12:17:30

W moim przypadku, do synchronizacji danych ze zdalnego WS do lokalnego DB użyłem tego sposobu, aby porównać dwa encje (sprawdź il Stary encja ma diffs od edytowanego encji).

Sklonowałem persisted byt, aby mieć dwa obiekty nie persisted:

<?php

$entity = $repository->find($id);// original entity exists
if (null === $entity) {
    $entity    = new $className();// local entity not exists, create new one
}
$oldEntity = clone $entity;// make a detached "backup" of the entity before it's changed
// make some changes to the entity...
$entity->setX('Y');

// now compare entities properties/values
$entityCloned = clone $entity;// clone entity for detached (not persisted) entity comparaison
if ( ! $em->contains( $entity ) || $entityCloned != $oldEntity) {// do not compare strictly!
    $em->persist( $entity );
    $em->flush();
}

unset($entityCloned, $oldEntity, $entity);

Inna możliwość zamiast porównywać obiekty bezpośrednio:

<?php
// here again we need to clone the entity ($entityCloned)
$entity_diff = array_keys(
    array_diff_key(
        get_object_vars( $entityCloned ),
        get_object_vars( $oldEntity )
    )
);
if(count($entity_diff) > 0){
    // persist & flush
}
 0
Author: kxxxxoo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-07 06:33:35