Есть ли встроенный способ получить все измененные/обновленные поля в учение 2 лица
предположим, я получаю сущность $e
и изменить его состояние с помощью сеттеров:
$e->setFoo('a');
$e->setBar('b');
есть ли возможность получить массив полей, которые были изменены?
в случае моего примера я хотел бы получить foo => a, bar => b
в результате
PS: да, я знаю, что могу изменить все методы доступа и реализовать эту функцию вручную, но я ищу какой-то удобный способ сделать это
7 ответов:
вы можете использовать
Doctrine\ORM\EntityManager#getUnitOfWork
иDoctrine\ORM\UnitOfWork
.затем просто запустите вычисление набора изменений (работает только на управляемых объектах) через
Doctrine\ORM\UnitOfWork#computeChangeSets()
.вы можете использовать также подобные методы, как
Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity)
если вы точно знаете, что хотите проверить, не повторяя весь граф объектов.после этого вы можете использовать
Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity)
для получения всех изменений объекта.собираем вместе:
$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);
Примечание. если пытаюсь чтобы получить обновленные поля внутри прослушивателя preUpdate, не пересчитать набор изменений, как это уже было сделано. Просто вызовите getEntityChangeSet, чтобы получить все изменения, внесенные в сущность.
большой осторожно знак для тех, кто хочет проверить изменения на лицо, используя метод, описанный выше.
$uow = $em->getUnitOfWork(); $uow->computeChangeSets();
The
$uow->computeChangeSets()
метод используется внутренне сохраняющейся подпрограммой таким образом, что делает вышеуказанное решение непригодным для использования. Это также то, что написано в комментариях к методу:@internal Don't call from the outside
. После проверки изменений в сущностях с помощью$uow->computeChangeSets()
, в конце метода выполняется следующий фрагмент кода (для каждого управляемого сущность):if ($changeSet) { $this->entityChangeSets[$oid] = $changeSet; $this->originalEntityData[$oid] = $actualData; $this->entityUpdates[$oid] = $entity; }
The
$actualData
массив содержит текущие изменения свойств объекта. Как только они будут записаны в$this->originalEntityData[$oid]
, эти еще не сохраненные изменения считаются исходными свойствами объекта.позже, когда
$em->persist($entity)
вызывается для сохранения изменений в сущности, он также включает в себя метод$uow->computeChangeSets()
, но теперь он не сможет найти изменения в сущности, так как эти еще не сохраненные изменения считаются оригинальными свойства сущности.
проверьте эту публичную (а не внутреннюю) функцию:
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
учение РЕПО:
/** * 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)
все, что вам нужно сделать, это реализовать
toArray
илиserialize
функция в вашей сущности и сделать разницу. Что-то вроде этого :$originalData = $em->getUnitOfWork()->getOriginalEntityData($entity); $toArrayEntity = $entity->toArray(); $changes = array_diff_assoc($toArrayEntity, $originalData);
Вы можете отслеживать изменения с уведомить политики.
во-первых, реализует NotifyPropertyChanged:
/** * @Entity * @ChangeTrackingPolicy("NOTIFY") */ class MyEntity implements NotifyPropertyChanged { // ... private $_listeners = array(); public function addPropertyChangedListener(PropertyChangedListener $listener) { $this->_listeners[] = $listener; } }
тогда просто позвоните _onPropertyChanged на каждом методе, который изменяет данные бросить вашу сущность, как показано ниже:
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; } } }
Так... что делать, если мы хотим найти набор изменений вне жизненного цикла доктрины? Как упоминалось в моем комментарии к сообщению @Ocramius выше, возможно, можно создать метод "только для чтения", который не связывается с фактическим сохранением доктрины, но дает пользователю представление о том, что изменилось.
вот пример того, о чем я думаю...
/** * 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]; }
он сформулирован здесь как статический метод, но может стать методом внутри UnitOfWork...?
Я не до скорости на всех внутренних доктринах, поэтому, возможно, пропустил что-то, что имеет побочный эффект или неправильно понял часть того, что делает этот метод, но (очень) быстрый тест, похоже, дает мне результаты, которые я ожидаю увидеть.
Я надеюсь, что это поможет кому-то!
в случае, если кто-то все еще заинтересован в другом способе, чем принятый ответ (он не работал для меня, и я нашел его более беспорядочным, чем этот способ, по моему личному мнению).
Я установил JMS Serializer Bundle и на каждом объекте и на каждом свойстве, которое я считаю изменением, я добавил @Group ({"changed_entity_group"}). Таким образом, я могу сделать сериализацию между старой сущностью и обновленной сущностью, а после этого просто сказать $oldJson = = $updatedJson. Если свойства, которые вас интересуют или которые вы хотели бы рассмотреть изменения, JSON не будет одинаковым, и если вы даже хотите зарегистрировать то, что конкретно изменилось, вы можете превратить его в массив и искать различия.
Я использовал этот метод, так как меня интересовали в основном несколько свойств группы сущностей, а не в сущности полностью. Пример, где это было бы полезно, если у вас есть @PrePersist @PreUpdate и у вас есть дата last_update, которая всегда будет обновляться, поэтому вы всегда будете получать, что сущность была обновлена с использованием единицы работы и тому подобное.
надеюсь, что этот способ поможет всем.
в моем случае, для синхронизации данных с удаленным
WS
местныйDB
я использовал этот способ для сравнения двух сущностей (проверьте, что старая сущность имеет отличия от редактируемой сущности).Я символически клонирую сохраненный объект, чтобы два объекта не сохранялись:
<?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);
другая возможность, а не сравнивать объекты напрямую:
<?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 }