Sie können verwenden
Doctrine\ORM\EntityManager#getUnitOfWork
, um eine zu bekommen Doctrine\ORM\UnitOfWork
.
Dann lösen Sie einfach die Änderungssatzberechnung aus (funktioniert nur bei verwalteten Entitäten) über Doctrine\ORM\UnitOfWork#computeChangeSets()
.
Sie können auch ähnliche Methoden verwenden, z. B. Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity)
wenn Sie genau wissen, was Sie überprüfen möchten, ohne das gesamte Objektdiagramm zu durchlaufen.
Danach können Sie Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity)
alle Änderungen an Ihrem Objekt abrufen.
Etwas zusammensetzen:
$entity = $em->find('My\Entity', 1);
$entity->setTitle('Changed Title!');
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
$changeset = $uow->getEntityChangeSet($entity);
Hinweis. Wenn Sie versuchen, die aktualisierten Felder in einem PreUpdate-Listener abzurufen , berechnen Sie den Änderungssatz nicht neu, da dies bereits geschehen ist. Rufen Sie einfach getEntityChangeSet auf, um alle an der Entität vorgenommenen Änderungen abzurufen.
Warnung: Wie in den Kommentaren erläutert, sollte diese Lösung nicht außerhalb von Doctrine-Ereignis-Listenern verwendet werden. Dies wird das Verhalten der Lehre brechen.
Großes Vorsichtzeichen für diejenigen, die mit der oben beschriebenen Methode nach Änderungen an der Entität suchen möchten.
Die
$uow->computeChangeSets()
Methode wird intern von der persistierenden Routine so verwendet, dass die obige Lösung unbrauchbar wird. Das steht auch in den Kommentaren zur Methode :@internal Don't call from the outside
. Nach dem Überprüfen der Änderungen an den Entitäten mit$uow->computeChangeSets()
wird am Ende der Methode (für jede verwaltete Entität) der folgende Code ausgeführt:if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; }
Das
$actualData
Array enthält die aktuellen Änderungen an den Eigenschaften der Entität. Sobald diese geschrieben sind$this->originalEntityData[$oid]
, werden diese noch nicht beibehaltenen Änderungen als die ursprünglichen Eigenschaften der Entität betrachtet.Später, wenn das
$em->persist($entity)
aufgerufen wird, um die Änderungen an der Entität zu speichern, wird auch die Methode$uow->computeChangeSets()
einbezogen. Jetzt können die Änderungen an der Entität nicht mehr gefunden werden, da diese noch nicht beibehaltenen Änderungen als die ursprünglichen Eigenschaften der Entität betrachtet werden .quelle
$uow->computerChangeSets()
? oder welche alternative Methode?Überprüfen Sie diese öffentliche (und nicht interne) Funktion:
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
Aus dem Doktrin- 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)
Alles, was Sie tun müssen, ist eine
toArray
oderserialize
Funktion in Ihrer Entität zu implementieren und einen Unterschied zu machen. Etwas wie das :quelle
Sie können die Änderungen mit Benachrichtigungsrichtlinien verfolgen .
Implementiert zunächst die NotifyPropertyChanged- Schnittstelle:
/** * @Entity * @ChangeTrackingPolicy("NOTIFY") */ class MyEntity implements NotifyPropertyChanged { // ... private $_listeners = array(); public function addPropertyChangedListener(PropertyChangedListener $listener) { $this->_listeners[] = $listener; } }
Rufen Sie dann einfach _onPropertyChanged für jede Methode auf, die Daten ändert, und werfen Sie Ihre Entität wie folgt aus:
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; } } }
quelle
Es werden Änderungen zurückgegeben
quelle
Für den Fall, dass jemand immer noch an einer anderen Art als der akzeptierten Antwort interessiert ist (es hat bei mir nicht funktioniert und ich fand es meiner persönlichen Meinung nach chaotischer als auf diese Weise).
Ich habe das JMS Serializer Bundle installiert und auf jeder Entität und auf jeder Eigenschaft, die ich als Änderung betrachte, eine @Group ({"defined_entity_group"}) hinzugefügt. Auf diese Weise kann ich dann eine Serialisierung zwischen der alten Entität und der aktualisierten Entität vornehmen. Danach muss nur noch $ oldJson == $ updatedJson gesagt werden. Wenn sich die Eigenschaften, an denen Sie interessiert sind oder die Sie berücksichtigen möchten, nicht ändern, ist der JSON nicht identisch. Wenn Sie sogar registrieren möchten, WAS speziell geändert wurde, können Sie ihn in ein Array umwandeln und nach den Unterschieden suchen.
Ich habe diese Methode verwendet, da ich mich hauptsächlich für einige Eigenschaften einer Reihe von Entitäten interessierte und nicht für die Entität vollständig. Ein Beispiel, bei dem dies nützlich wäre, ist, wenn Sie ein @ PrePersist @ PreUpdate und ein Datum für das letzte Update haben, das immer aktualisiert wird. Daher erhalten Sie immer, dass die Entität mit einer Arbeitseinheit und dergleichen aktualisiert wurde.
Hoffe, diese Methode ist für jeden hilfreich.
quelle
Also ... was tun, wenn wir einen Änderungssatz außerhalb des Doctrine-Lebenszyklus finden möchten? Wie in meinem Kommentar zu @Ocramius 'Beitrag oben erwähnt, ist es möglicherweise möglich, eine "schreibgeschützte" Methode zu erstellen, die nicht mit der tatsächlichen Doctrine-Persistenz in Konflikt gerät, sondern dem Benutzer einen Überblick darüber gibt, was sich geändert hat.
Hier ist ein Beispiel dafür, woran ich denke ...
/** * 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]; }
Es wird hier als statische Methode formuliert, könnte aber zu einer Methode in UnitOfWork werden ...?
Ich bin nicht mit allen Interna von Doctrine auf dem Laufenden, habe also möglicherweise etwas übersehen, das einen Nebeneffekt hat oder einen Teil der Funktionsweise dieser Methode missverstanden hat, aber ein (sehr) schneller Test scheint mir die erwarteten Ergebnisse zu liefern sehen.
Ich hoffe das hilft jemandem!
quelle
hasChanges
undgetChanges
(letztere, um nur die geänderten Felder anstelle des gesamten Änderungssatzes zu erhalten).In meinem Fall habe ich zum Synchronisieren von Daten von einer entfernten
WS
zu einer lokalenDB
Entität auf diese Weise zwei Entitäten verglichen (überprüfen Sie, ob die alte Entität von der bearbeiteten Entität abweicht).Ich klone die persistierte Entität sympathisch, damit zwei Objekte nicht persistiert werden:
<?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);
Eine andere Möglichkeit, anstatt Objekte direkt zu vergleichen:
<?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 }
quelle
In meinem Fall möchte ich den alten Wert der Beziehung in der Entität erhalten, daher verwende ich die Doctrine \ ORM \ PersistentCollection :: getSnapshot-Basis auf dieser Basis
quelle
Es funktioniert für mich 1. EntityManager importieren 2. Jetzt können Sie dies überall in der Klasse verwenden.
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'; }
quelle
Die Arbeit mit
UnitOfWork
undcomputeChangeSets
innerhalb eines Doctrine Event Listeners ist wahrscheinlich die bevorzugte Methode.Allerdings : Wenn Sie eine neue Entität in diesem Listener beibehalten und löschen möchten, kann dies zu erheblichen Problemen führen. Wie es scheint, wäre der einzig richtige Zuhörer
onFlush
mit seinen eigenen Problemen.Daher schlage ich einen einfachen, aber leichten Vergleich vor, der innerhalb von Controllern und sogar Diensten verwendet werden kann, indem einfach das injiziert wird
EntityManagerInterface
(inspiriert von @Mohamed Ramrami im obigen Beitrag):$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; } } }
Hinweis : Es vergleicht Zeichenfolgen, Datums- und Uhrzeitangaben, Bools, Ganzzahlen und Gleitkommazahlen korrekt, schlägt jedoch bei Objekten fehl (aufgrund der Zirkelreferenzprobleme). Man könnte diese Objekte genauer vergleichen, aber für die Erkennung von Textänderungen ist dies ausreichend und viel einfacher als die Handhabung von Ereignis-Listenern.
Mehr Info:
quelle