Możesz użyć,
Doctrine\ORM\EntityManager#getUnitOfWork
aby uzyskać Doctrine\ORM\UnitOfWork
.
Następnie po prostu wyzwól obliczenia zestawu zmian (działa tylko na zarządzanych jednostkach) za pośrednictwem Doctrine\ORM\UnitOfWork#computeChangeSets()
.
Możesz również użyć podobnych metod, na przykład Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity)
jeśli wiesz dokładnie, co chcesz sprawdzić, bez iteracji po całym grafie obiektów.
Następnie możesz użyć, Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity)
aby pobrać wszystkie zmiany w obiekcie.
Składając to razem:
$entity = $em->find('My\Entity', 1);
$entity->setTitle('Changed Title!');
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
$changeset = $uow->getEntityChangeSet($entity);
Uwaga. Jeśli próbujesz pobrać zaktualizowane pola w odbiorniku preUpdate , nie ponownie zestawu zmian, ponieważ zostało to już zrobione. Po prostu wywołaj metodę getEntityChangeSet, aby uzyskać wszystkie zmiany wprowadzone w encji.
Ostrzeżenie: jak wyjaśniono w komentarzach, tego rozwiązania nie należy używać poza odbiornikami zdarzeń Doctrine. To przerwie zachowanie Doctrine.
Wielka uwaga na znak dla tych, którzy chcą sprawdzić zmiany w encji za pomocą metody opisanej powyżej.
$uow = $em->getUnitOfWork(); $uow->computeChangeSets();
$uow->computeChangeSets()
Sposób jest stosowany wewnętrznie w rutynowych utrzymującej się w taki sposób, że powoduje, że rozwiązanie wyżej użytku. To także to, co jest napisane w komentarzach do metody:@internal Don't call from the outside
. Po sprawdzeniu zmian w jednostkach za pomocą$uow->computeChangeSets()
, następujący fragment kodu jest wykonywany na końcu metody (na każdą zarządzaną jednostkę):if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; }
$actualData
Tablica posiada aktualne zmiany właściwości jednostki. Gdy tylko zostaną one zapisane$this->originalEntityData[$oid]
, te jeszcze nie utrwalone zmiany są uważane za oryginalne właściwości jednostki.Później, gdy
$em->persist($entity)
jest wywoływana w celu zapisania zmian w encji, obejmuje również metodę$uow->computeChangeSets()
, ale teraz nie będzie w stanie znaleźć zmian w encji, ponieważ te jeszcze nie utrwalone zmiany są uważane za oryginalne właściwości jednostki .źródło
$uow->computerChangeSets()
? lub jaką alternatywną metodę?Sprawdź tę funkcję publiczną (a nie wewnętrzną):
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
Z repozytorium doktryny :
/** * 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ć funkcję
toArray
lubserialize
w swojej encji i zrobić różnicę. Coś takiego :$originalData = $em->getUnitOfWork()->getOriginalEntityData($entity); $toArrayEntity = $entity->toArray(); $changes = array_diff_assoc($toArrayEntity, $originalData);
źródło
Możesz śledzić zmiany za pomocą zasad powiadamiania .
Po pierwsze, implementuje interfejs NotifyPropertyChanged :
/** * @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 dla każdej metody, która zmienia dane, rzuca twoją encję, 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; } } }
źródło
Zwróci zmiany
$entityManager->getUnitOfWork()->getEntityChangeSet($entity)
źródło
W przypadku, gdy ktoś jest nadal zainteresowany w inny sposób niż zaakceptowana odpowiedź (nie działała ona dla mnie i uważam, że jest bardziej niechlujna niż ta w mojej osobistej opinii).
Zainstalowałem pakiet JMS Serializer Bundle i na każdej encji i każdej właściwości, którą uważam za zmianę, dodałem @Group ({"modified_entity_group"}). W ten sposób mogę następnie dokonać serializacji między starą jednostką a zaktualizowaną jednostką, a potem wystarczy powiedzieć $ oldJson == $ updatedJson. Jeśli właściwości, które Cię interesują lub które chciałbyś rozważyć zmiany, JSON nie będzie taki sam, a jeśli nawet chcesz zarejestrować CO konkretnie zmieniono, możesz przekształcić je w tablicę i wyszukać różnice.
Użyłem tej metody, ponieważ interesowało mnie głównie kilka właściwości wielu podmiotów, a nie całość. Przykładem, w którym byłoby to przydatne, jest sytuacja, gdy masz @PrePersist @PreUpdate i masz datę ostatniej_aktualizacji, która będzie zawsze aktualizowana, dlatego zawsze otrzymasz informację, że jednostka została zaktualizowana przy użyciu jednostki pracy i tym podobnych.
Mam nadzieję, że ta metoda jest pomocna dla każdego.
źródło
Więc ... co zrobić, gdy chcemy znaleźć zestaw zmian poza cyklem życia Doktryny? Jak wspomniałem w moim komentarzu do posta @Ocramius powyżej, być może jest możliwe utworzenie metody „tylko do odczytu”, która nie zakłóca rzeczywistej trwałości Doctrine, 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 tu wyrażona jako metoda statyczna, ale może stać się metodą wewnątrz UnitOfWork ...?
Nie jestem na bieżąco ze wszystkimi wewnętrznymi elementami Doctrine, więc mogłem przegapić coś, co ma efekt uboczny lub źle zrozumiał część tego, co robi ta metoda, ale (bardzo) szybki test wydaje mi się, że spodziewam się wyników zobaczyć.
Mam nadzieję, że to komuś pomoże!
źródło
hasChanges
igetChanges
(ta ostatnia, aby uzyskać tylko zmienione pola zamiast całego zestawu zmian).W moim przypadku do synchronizacji danych z pilota
WS
do lokalnegoDB
użyłem w ten sposób porównania dwóch encji (sprawdź, czy il stara encja ma różnice w stosunku do edytowanej encji).Symptycznie klonuję utrwaloną istotę, aby dwa obiekty nie zostały utrwalone:
<?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 bezpośredniego porównywania obiektów:
<?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 }
źródło
w moim przypadku chcę uzyskać starą wartość relacji w encji, więc używam bazy Doctrine \ ORM \ PersistentCollection :: getSnapshot na tej podstawie
źródło
U mnie to działa 1. import EntityManager 2. Teraz możesz użyć tego w dowolnym miejscu w klasie.
use Doctrine\ORM\EntityManager; $preData = $this->em->getUnitOfWork()->getOriginalEntityData($entity); // $preData['active'] for old data and $entity->getActive() for new data if($preData['active'] != $entity->getActive()){ echo 'Send email'; }
źródło
Praca z
UnitOfWork
icomputeChangeSets
w obrębie Słuchaczy Doktryna zdarzeń jest prawdopodobnie preferowaną metodą.Jednak : jeśli chcesz przetrwać i opróżnić nową jednostkę w tym słuchaczu, możesz napotkać wiele problemów. Jak się wydaje, jedynym właściwym słuchaczem byłby
onFlush
własny zestaw problemów.Proponuję więc proste, ale lekkie porównanie, którego można używać w kontrolerach, a nawet usługach, po prostu wstrzykując
EntityManagerInterface
(zainspirowane @Mohamed Ramrami w poście powyżej):$uow = $entityManager->getUnitOfWork(); $originalEntityData = $uow->getOriginalEntityData($blog); // for nested entities, as suggested in the docs $defaultContext = [ AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) { return $object->getId(); }, ]; $normalizer = new Serializer([new DateTimeNormalizer(), new ObjectNormalizer(null, null, null, null, null, null, $defaultContext)]); $yourEntityNormalized = $normalizer->normalize(); $originalNormalized = $normalizer->normalize($originalEntityData); $changed = []; foreach ($originalNormalized as $item=>$value) { if(array_key_exists($item, $yourEntityNormalized)) { if($value !== $yourEntityNormalized[$item]) { $changed[] = $item; } } }
Uwaga : poprawnie porównuje łańcuchy, czasy dat, wartości logiczne, liczby całkowite i zmiennoprzecinkowe, ale nie działa poprawnie na obiektach (z powodu problemów z cyklicznymi odwołaniami). Można by bardziej szczegółowo porównać te obiekty, ale np. W przypadku wykrywania zmiany tekstu jest to wystarczające i znacznie prostsze niż obsługa detektorów zdarzeń.
Więcej informacji:
źródło