是否有一种内置的方式来获取Doctrine 2实体中所有已更改/更新的字段

假设我检索一个实体$e并用setter修改它的状态:

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

有没有可能检索已更改的字段数组?

在我的例子的情况下,我想检索foo => a, bar => b作为结果

PS:是的,我知道我可以修改所有访问器并手动实现此function,但我正在寻找一些方便的方法来做到这一点

您可以使用Doctrine\ORM\EntityManager#getUnitOfWork获取Doctrine\ORM\UnitOfWork

然后通过Doctrine\ORM\UnitOfWork#computeChangeSets()触发更改集计算(仅对受pipe实体起作用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(); 

$uow->computeChangeSets()方法由持久化例程在内部使用,以使上述解决scheme不可用。 这也是写在方法的评论: @internal Don't call from the outside 。 使用$uow->computeChangeSets()检查实体的更改后,将在方法的末尾(每个受pipe实体)执行以下代码片段:

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

$actualData数组保存对实体属性的当前更改。 只要这些写入$this->originalEntityData[$oid] ,这些尚未保留的更改就被视为实体的原始属性。

后来,当$em->persist($entity)被调用来保存对实体的更改时,它还涉及$uow->computeChangeSets() ,但现在它将无法find对实体,因为这些尚未保留的更改被视为实体的原始属性。

检查这个公共(而不是内部)function:

$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函数,并做一个diff。 像这样的东西:

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

如果有人对接受的答案感兴趣的方式不同(这不适合我,而且我个人认为这种方式比这种方式更加混乱)。

我安装了JMS序列化程序包,并在每个实体上以及每个属性上考虑更改,我添加了@Group({“changed_entity_group”})。 这样,我就可以在旧的实体和更新的实体之间进行序列化,之后它只是说$ oldJson == $ updatedJson的问题。 如果你感兴趣的属性或你想考虑改变JSON将不会是相同的,如果你甚至想要注册具体的变化,那么你可以把它变成一个数组,并寻找差异。

我使用这种方法,因为我主要对一些实体的一些属性感兴趣,而不是完全在实体中。 一个例子,这将是有用的,如果你有一个@PrePersist @PreUpdate,你有一个last_updatedate,总是会被更新,因此你总是会得到该实体更新使用工作单位和类似的东西。

希望这种方法对任何人都有帮助。

那么…当我们想在Doctrine生命周期之外find一个变更集时该怎么办? 正如我在上面@Ocramius的post的评论中所提到的,或许可以创build一个“只读”方法,它不会混淆实际的Doctrine持久性,而是让用户了解已经改变了什么。

这是我想到的一个例子…

 /** * 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内的方法…?

我不太了解所有内部的教义,所以可能错过了一些有副作用或者误解了这个方法的一部分的东西,但是一个非常快速的testing似乎给了我期望的结果查看。

我希望这可以帮助别人!