Puede utilizar
Doctrine\ORM\EntityManager#getUnitOfWork
para obtener un Doctrine\ORM\UnitOfWork
.
Luego, simplemente active el cálculo del conjunto de cambios (funciona solo en entidades administradas) a través de Doctrine\ORM\UnitOfWork#computeChangeSets()
.
También puede usar métodos similares como Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity)
si supiera exactamente lo que desea verificar sin iterar sobre todo el gráfico del objeto.
Después de eso, puede usar Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity)
para recuperar todos los cambios en su objeto.
Poniendo todo junto:
$entity = $em->find('My\Entity', 1);
$entity->setTitle('Changed Title!');
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
$changeset = $uow->getEntityChangeSet($entity);
Nota. Si intenta obtener los campos actualizados dentro de un oyente previo a la actualización , no vuelva a calcular el conjunto de cambios, como ya se ha hecho. Simplemente llame a getEntityChangeSet para obtener todos los cambios realizados en la entidad.
Advertencia: como se explica en los comentarios, esta solución no debe usarse fuera de los detectores de eventos de Doctrine. Esto romperá el comportamiento de Doctrine.
Gran cartel de cuidado para aquellos que quieran verificar los cambios en la entidad utilizando el método descrito anteriormente.
El
$uow->computeChangeSets()
método es utilizado internamente por la rutina persistente de una manera que inutiliza la solución anterior. Eso es también lo que está escrito en los comentarios al método:@internal Don't call from the outside
. Después de verificar los cambios en las entidades con$uow->computeChangeSets()
, se ejecuta el siguiente fragmento de código al final del método (por cada entidad administrada):if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; }
La
$actualData
matriz contiene los cambios actuales en las propiedades de la entidad. Tan pronto como se escriben$this->originalEntityData[$oid]
, estos cambios aún no persistentes se consideran las propiedades originales de la entidad.Más tarde, cuando
$em->persist($entity)
se llama a para guardar los cambios en la entidad, también involucra el método$uow->computeChangeSets()
, pero ahora no podrá encontrar los cambios en la entidad, ya que estos cambios aún no persistentes se consideran las propiedades originales de la entidad. .fuente
$uow->computerChangeSets()
? o qué método alternativo?Verifique esta función pública (y no interna):
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
Desde el repositorio de doctrina :
/** * 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)
Todo lo que tienes que hacer es implementar una función
toArray
oserialize
en tu entidad y hacer una diferencia. Algo como esto :fuente
Puede realizar un seguimiento de los cambios con Notificar políticas .
Primero, implementa la interfaz NotifyPropertyChanged :
/** * @Entity * @ChangeTrackingPolicy("NOTIFY") */ class MyEntity implements NotifyPropertyChanged { // ... private $_listeners = array(); public function addPropertyChangedListener(PropertyChangedListener $listener) { $this->_listeners[] = $listener; } }
Luego, simplemente llame a _onPropertyChanged en cada método que cambie los datos arroje su entidad de la siguiente manera:
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; } } }
fuente
Devolverá cambios
fuente
En caso de que alguien todavía esté interesado de una manera diferente a la respuesta aceptada (no funcionó para mí y lo encontré más complicado que así en mi opinión personal).
Instalé el JMS Serializer Bundle y en cada entidad y en cada propiedad que considero un cambio agregué un @Group ({"modified_entity_group"}). De esta manera, puedo hacer una serialización entre la entidad anterior y la entidad actualizada y, después de eso, solo es cuestión de decir $ oldJson == $ updatedJson. Si las propiedades que le interesan o que le gustaría considerar cambian, el JSON no será el mismo y si incluso desea registrar QUÉ cambió específicamente, puede convertirlo en una matriz y buscar las diferencias.
Usé este método porque estaba interesado principalmente en algunas propiedades de un grupo de entidades y no en la entidad por completo. Un ejemplo en el que esto sería útil es si tiene un @PrePersist @PreUpdate y tiene una fecha de última actualización, que siempre se actualizará, por lo que siempre obtendrá que la entidad se actualizó usando la unidad de trabajo y cosas así.
Espero que este método sea útil para cualquiera.
fuente
Entonces ... ¿qué hacer cuando queremos encontrar un conjunto de cambios fuera del ciclo de vida de Doctrine? Como mencioné anteriormente en mi comentario sobre la publicación de @Ocramius, tal vez sea posible crear un método de "solo lectura" que no interfiera con la persistencia real de Doctrine pero le dé al usuario una vista de lo que ha cambiado.
Aquí hay un ejemplo de lo que estoy pensando ...
/** * 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]; }
Está redactado aquí como un método estático, pero ¿podría convertirse en un método dentro de UnitOfWork ...?
No estoy al tanto de todos los aspectos internos de Doctrine, por lo que podría haber pasado por alto algo que tiene un efecto secundario o haber entendido mal una parte de lo que hace este método, pero una prueba (muy) rápida parece darme los resultados que espero. para ver.
¡Espero que esto ayude a alguien!
fuente
hasChanges
ygetChanges
(la última para obtener solo los campos modificados en lugar de todo el conjunto de cambios).En mi caso, para sincronizar datos de un remoto
WS
a un localDB
, utilicé esta forma para comparar dos entidades (verifique que la entidad anterior tenga diferencias con la entidad editada).Simplemente clono la entidad persistente para que no persistan dos objetos:
<?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);
Otra posibilidad en lugar de comparar objetos directamente:
<?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 }
fuente
en mi caso, quiero obtener el valor anterior de la relación en la entidad, así que uso la base Doctrine \ ORM \ PersistentCollection :: getSnapshot en esto
fuente
Me funciona 1. Importar EntityManager 2. Ahora puedes usar esto en cualquier lugar de la clase.
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'; }
fuente
Trabajar con
UnitOfWork
ycomputeChangeSets
dentro de un escucha de eventos de Doctrine es probablemente el método preferido.Sin embargo : si desea persistir y vaciar una nueva entidad dentro de este oyente, puede enfrentarse a muchas molestias. Como parece, el único oyente adecuado sería
onFlush
con su propio conjunto de problemas.Entonces sugiero una comparación simple pero liviana, que se puede usar dentro de los Controladores e incluso en los Servicios simplemente inyectando el
EntityManagerInterface
(inspirado en @Mohamed Ramrami en la publicación anterior):$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; } } }
Nota : compara cadenas, fechas, bools, enteros y flotantes correctamente, pero falla en los objetos (debido a los problemas de referencia circular). Uno podría comparar estos objetos con más profundidad, pero, por ejemplo, para la detección de cambios de texto, esto es suficiente y mucho más simple que manejar Event Listeners.
Más información:
fuente