tutorial symfony2 generate español consultas php symfony doctrine2 doctrine

php - symfony2 - doctrine:generate:entities



¿Existe una forma incorporada de obtener todos los campos modificados/actualizados en una entidad de Doctrine 2? (7)

En caso de que alguien todavía esté interesado de una manera diferente a la respuesta aceptada (no funcionó para mí y me pareció más desordenado de esta manera 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 ({"changed_entity_group"}). De esta forma, 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, entonces puede convertirlo en una matriz y buscar las diferencias.

Usé este método ya que 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 @PrePersist @PreUpdate y tiene una fecha last_update, que siempre se actualizará, por lo que siempre obtendrá que la entidad se actualizó usando una unidad de trabajo y cosas así.

Espero que este método sea útil para cualquiera.

Supongamos que recupero una entidad $e y modifico su estado con setters:

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

¿Hay alguna posibilidad de recuperar una matriz de campos que se han cambiado?

En el caso de mi ejemplo, me gustaría recuperar foo => a, bar => b como resultado

PD: sí, sé que puedo modificar todos los accesores e implementar esta función de forma manual, pero estoy buscando alguna forma práctica de hacerlo


En mi caso, para sincronizar datos de un WS remoto con un DB local, utilicé esta forma para comparar dos entidades (compruebe que la entidad anterior tiene difs de la entidad editada).

Simplicidad clono la entidad persistente para tener dos objetos no persistentes:

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


Entonces ... ¿qué hacer cuando queremos encontrar un conjunto de cambios fuera del ciclo de vida de Doctrine? Como mencioné en mi comentario sobre la publicación anterior de @Ocramius, quizás es posible crear un método de "solo lectura" que no entre en conflicto con la persistencia real de Doctrine, pero le da 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 una parte incomprendida 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!


Puede rastrear los cambios con las políticas de notificación .

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, eche 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; } } }


Puede usar Doctrine/ORM/EntityManager#getUnitOfWork para obtener Doctrine/ORM/UnitOfWork .

A continuación, solo active el cálculo del conjunto de cambios (funciona solo en entidades administradas) a través de Doctrine/ORM/UnitOfWork#computeChangeSets() .

También puede utilizar métodos similares como Doctrine/ORM/UnitOfWork#recomputeSingleEntityChangeSet(Doctrine/ORM/ClassMetadata $meta, $entity) si sabe exactamente lo que desea verificar sin iterar sobre todo el gráfico de objetos.

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(); // do not compute changes if inside a listener $changeset = $uow->getEntityChangeSet($entity);

Nota. Si intenta obtener los campos actualizados dentro de un oyente preUpdate , no vuelva a calcular el conjunto de cambios, como ya se hizo. Simplemente llame al getEntityChangeSet para obtener todos los cambios realizados en la entidad.


Verifique esta función pública (y no interna):

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

Del repo 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 tiene que hacer es implementar una función toArray o serialize en su entidad y crear una diferencia. Algo como esto :

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


Signo de gran cuidado para aquellos que desean verificar los cambios en la entidad utilizando el método descrito anteriormente.

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

El $uow->computeChangeSets() se usa internamente por la rutina persistente de una manera que inutiliza la solución anterior. Eso también es 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() , el siguiente fragmento de código se ejecuta al final del método (por cada entidad gestionada):

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

La matriz $actualData contiene los cambios actuales en las propiedades de la entidad. Tan pronto como se escriben en $this->originalEntityData[$oid] , estos cambios que aún no se han conservado se consideran las propiedades originales de la entidad.

Más tarde, cuando se llama a $em->persist($entity) para guardar los cambios en la entidad, también implica el método $uow->computeChangeSets() , pero ahora no podrá encontrar los cambios en el entidad, ya que estos cambios aún no persistentes se consideran las propiedades originales de la entidad.