Gibt es eine integrierte Möglichkeit, alle geänderten / aktualisierten Felder in einer Doctrine 2-Entität abzurufen

Nehmen wir an, ich erhalte eine Entität $e und modifiziere ihren Zustand mit Setter:

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

Gibt es eine Möglichkeit, ein Array von Feldern abzurufen, die geändert wurden?

Im Falle meines Beispiels möchte ich foo => a, bar => b als Ergebnis erhalten

PS: Ja, ich weiß, dass ich alle Accessoren modifizieren und diese function manuell implementieren kann, aber ich suche nach einer praktischen Möglichkeit, dies zu tun

Sie können Doctrine\ORM\EntityManager#getUnitOfWork , um Doctrine\ORM\UnitOfWork .

Dann lösen Sie die Changeset-Berechnung (funktioniert nur für verwaltete Entitäten) über Doctrine\ORM\UnitOfWork#computeChangeSets() .

Sie können auch ähnliche Methoden wie Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity) wenn Sie genau wissen, was Sie überprüfen möchten, ohne das gesamte Objektdiagramm durchlaufen zu müssen.

Danach können Sie Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity) , um alle Änderungen an Ihrem Objekt abzurufen.

Etwas zusammensetzen:

 $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); 

Hinweis. Wenn Sie versuchen, die aktualisierten Felder in einem PreUpdate-Listener abzurufen , müssen Sie die Änderungsmenge nicht erneut berechnen, wie dies bereits geschehen ist. Rufen Sie einfach den Befehl getEntityChangeSet auf, um alle an der Entität vorgenommenen Änderungen abzurufen.

Große Vorsicht Zeichen für diejenigen, die mit der oben beschriebenen Methode auf die Änderungen an der Entität prüfen möchten.

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

Die Methode $uow->computeChangeSets() 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 das folgende Codeteil am Ende der Methode ausgeführt (für jede verwaltete Entität):

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

Das Array $actualData enthält die aktuellen Änderungen an den Eigenschaften der Entität. Sobald diese in $this->originalEntityData[$oid] , werden diese noch nicht $this->originalEntityData[$oid] Änderungen als ursprüngliche Eigenschaften der Entität angesehen.

Später, wenn $em->persist($entity) aufgerufen wird, um die Änderungen an der Entität zu speichern, beinhaltet es auch die Methode $uow->computeChangeSets() , aber jetzt wird es nicht mehr in der Lage sein, die Änderungen zu finden Entität, da diese noch nicht beibehaltenen Änderungen als ursprüngliche Eigenschaften der Entität gelten.

Überprüfen Sie diese öffentliche (und nicht interne) function:

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

Aus 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 oder serialize in Ihrer Entität zu implementieren und ein Diff zu toArray . Etwas wie das :

 $originalData = $em->getUnitOfWork()->getOriginalEntityData($entity); $toArrayEntity = $entity->toArray(); $changes = array_diff_assoc($toArrayEntity, $originalData); 

Sie können die Änderungen mit Benachrichtigungsrichtlinien verfolgen.

Implementiert zuerst 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 den onPropertyChanged für jede Methode auf, die Daten ändert, und casting Sie Ihre Entity 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; } } } 

Also … was tun, wenn wir außerhalb des Doctrine Lifecycles einen Changeset finden wollen? Wie in meinem Kommentar zu @Ocramius ‘Beitrag oben erwähnt, ist es vielleicht möglich, eine “readonly” -Methode zu erstellen, die sich nicht mit der tatsächlichen Doktrin-Persistenz auseinandersetzt, sondern dem Benutzer einen Überblick darüber gibt, was sich geändert hat.

Hier ist ein Beispiel für das, 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 ist hier als statische Methode formuliert, könnte aber eine Methode innerhalb von UnitOfWork werden …?

Ich bin mit den Interna von Doctrine nicht auf dem neuesten Stand, also hätte ich vielleicht etwas übersehen, was einen Nebeneffekt hat oder einen Teil davon mißversteht, aber ein (sehr) schneller Test scheint mir die Ergebnisse zu liefern, die ich erwarte sehen.

Ich hoffe, das hilft jemandem!

Für den Fall, dass jemand an einem anderen Weg als der angenommenen Antwort interessiert ist (es funktionierte nicht für mich und ich fand es in meiner persönlichen Meinung unordentlicher als diese Art).

Ich habe das JMS Serializer Bundle installiert und auf jeder Entität und auf jeder Eigenschaft, die ich für eine Änderung halte, habe ich eine @Group ({“changed_entity_group”}) hinzugefügt. Auf diese Weise kann ich dann eine Serialisierung zwischen der alten Entität und der aktualisierten Entität vornehmen und danach muss nur noch $ oldJson == $ updatedJson gesagt werden. Wenn die Eigenschaften, an denen Sie interessiert sind oder die Sie berücksichtigen möchten, sich ändern, wird das JSON nicht dasselbe sein und wenn Sie WAT spezifisch ändern möchten, können Sie es in ein Array umwandeln und nach den Unterschieden suchen.

Ich habe diese Methode verwendet, da ich mich hauptsächlich für einige Eigenschaften einer Menge von Entitäten und nicht für die Entität interessiert habe. Ein Beispiel, wo dies nützlich wäre, ist, wenn Sie ein @ PrePersist @ PreUpdate haben und Sie ein Last_Aktualisierungsdatum haben, das immer aktualisiert wird. Deshalb werden Sie immer sehen, dass die Entität mit Arbeitseinheit und so aktualisiert wurde.

Hoffe, diese Methode ist für jeden hilfreich.

In meinem Fall habe ich zum Synchronisieren von Daten von einem entfernten WS zu einer lokalen DB auf diese Weise zwei Entitäten verglichen (überprüfe, ob die alte Entität Diffs von der editierten Entität hat).

Ich klicke bewusst die persistente Entität, um zwei Objekte nicht bestehen zu lassen:

 < ?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 als 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 }