php 是否有一种内置方法可以获取 Doctrine 2 实体中所有更改/更新的字段
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9057558/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a built-in way to get all of the changed/updated fields in a Doctrine 2 entity
提问by zerkms
Let's suppose I retrieve an entity $e
and modify its state with setters:
假设我检索一个实体$e
并使用 setter 修改其状态:
$e->setFoo('a');
$e->setBar('b');
Is there any possibility to retrieve an array of fields that have been changed?
是否有可能检索已更改的字段数组?
In case of my example I'd like to retrieve foo => a, bar => b
as a result
在我的例子中,我想检索foo => a, bar => b
结果
PS: yes, I know I can modify all the accessors and implement this feature manually, but I'm looking for some handy way of doing this
PS:是的,我知道我可以修改所有访问器并手动实现此功能,但我正在寻找一些方便的方法来做到这一点
回答by Ocramius
You can use
Doctrine\ORM\EntityManager#getUnitOfWork
to get a Doctrine\ORM\UnitOfWork
.
您可以使用
Doctrine\ORM\EntityManager#getUnitOfWork
来获取Doctrine\ORM\UnitOfWork
.
Then just trigger changeset computation (works only on managed entities) via Doctrine\ORM\UnitOfWork#computeChangeSets()
.
然后通过Doctrine\ORM\UnitOfWork#computeChangeSets()
.触发变更集计算(仅适用于托管实体)。
You can use also similar methods like Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity)
if you know exactly what you want to check without iterating over the entire object graph.
您也可以使用类似的方法,例如Doctrine\ORM\UnitOfWork#recomputeSingleEntityChangeSet(Doctrine\ORM\ClassMetadata $meta, $entity)
如果您确切地知道要检查的内容而无需遍历整个对象图。
After that you can use Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity)
to retrieve all changes to your object.
之后,您可以使用Doctrine\ORM\UnitOfWork#getEntityChangeSet($entity)
来检索对对象的所有更改。
Putting it together:
把它放在一起:
$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);
Note.If trying to get the updated fields inside a preUpdate listener, don't recompute change set, as it has already been done. Simply call the getEntityChangeSet to get all of the changes made to the entity.
笔记。如果尝试在 preUpdate listener 中获取更新的字段,请不要重新计算更改集,因为它已经完成了。只需调用 getEntityChangeSet 即可获取对实体所做的所有更改。
Warning:As explained in the comments, this solution should not be used outside of Doctrine event listeners. This will break Doctrine's behavior.
警告:如评论中所述,不应在 Doctrine 事件侦听器之外使用此解决方案。这将打破 Doctrine 的行为。
回答by Slavik Derevianko
Big beware signfor those that want to check for the changes on the entity using the method described above.
对于那些想要使用上述方法检查实体更改的人,请务必小心。
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
The $uow->computeChangeSets()
method is used internally by the persisting routine in a way that renders the above solution unusable. That's also what's written in the comments to the method: @internal Don't call from the outside
.
After checking on the changes to the entities with $uow->computeChangeSets()
, the following piece of code is executed at the end of the method (per each managed entity):
该$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
array holds the current changes to the entity's properties. As soon as these are written into $this->originalEntityData[$oid]
, these not yet persisted changes are considered the original properties of the entity.
该$actualData
数组保存对实体属性的当前更改。一旦将这些写入$this->originalEntityData[$oid]
,这些尚未持久化的更改将被视为实体的原始属性。
Later, when the $em->persist($entity)
is called to save the changes to the entity, it also involves the method $uow->computeChangeSets()
, but now it won't be able to find the changes to the entity, as these not yet persisted changes are considered the original properties of the entity.
后来,当$em->persist($entity)
调用 保存对实体的更改时,也会涉及到方法$uow->computeChangeSets()
,但现在将无法找到对实体的更改,因为这些尚未持久化的更改被认为是实体的原始属性.
回答by Mohamed Ramrami
Check this public (and not internal) function:
检查这个公共(而不是内部)函数:
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
$this->em->getUnitOfWork()->getOriginalEntityData($entity);
From doctrine 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)
All you have to do is implement a toArray
or serialize
function in your entity and make a diff. Something like this :
你所要做的就是在你的实体中实现一个toArray
orserialize
函数并做一个差异。像这样的事情:
$originalData = $em->getUnitOfWork()->getOriginalEntityData($entity);
$toArrayEntity = $entity->toArray();
$changes = array_diff_assoc($toArrayEntity, $originalData);
回答by manix
You can track the changes with Notify policies.
您可以使用通知策略跟踪更改。
First, implements the NotifyPropertyChangedinterface:
首先,实现NotifyPropertyChanged接口:
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
{
$this->_listeners[] = $listener;
}
}
Then, just call the _onPropertyChangedon every method that changes data throw your entity as below:
然后,只需在每个更改数据的方法上调用_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;
}
}
}
回答by Benjamin Vison
In case someone is still interested in a different way than the accepted answer (it was not working for me and I found it messier than this way in my personal opinion).
如果有人仍然对与接受的答案不同的方式感兴趣(这对我不起作用,我个人认为它比这种方式更混乱)。
I installed the JMS Serializer Bundleand on each entity and on each property that I consider a change I added a @Group({"changed_entity_group"}). This way, I can then make a serialization between the old entity, and the updated entity and after that it's just a matter of saying $oldJson == $updatedJson. If the properties that you are interested in or that you would like to consider changes the JSON won't be the same and if you even want to register WHAT specifically changed then you can turn it into an array and search for the differences.
我安装了JMS Serializer Bundle,并在每个实体和我认为更改的每个属性上添加了 @Group({"changed_entity_group"})。这样,我就可以在旧实体和更新实体之间进行序列化,之后只需说 $oldJson == $updatedJson。如果您感兴趣或您想考虑更改的属性,JSON 将不相同,如果您甚至想注册具体更改的内容,那么您可以将其转换为数组并搜索差异。
I used this method since I was interested mainly in a few properties of a bunch of entities and not in the entity entirely. An example where this would be useful is if you have a @PrePersist @PreUpdate and you have a last_update date, that will always be updated therefore you will always get that the entity was updated using unit of work and stuff like that.
我使用这种方法是因为我主要对一堆实体的一些属性感兴趣,而不是完全对实体感兴趣。这将很有用的一个例子是,如果您有一个@PrePersist @PreUpdate 并且您有一个 last_update 日期,则该日期将始终更新,因此您将始终知道实体已使用工作单元等进行更新。
Hope this method is helpful to anyone.
希望这个方法对大家有帮助。
回答by Omar Makled
It will return changes
它将返回更改
$entityManager->getUnitOfWork()->getEntityChangeSet($entity)
回答by caponica
So... what to do when we want to find a changeset outside of the Doctrine lifecycle? As mentioned in my comment on @Ocramius' post above, perhaps it is possible to create a "readonly" method that doesn't mess with the actual Doctrine persistence but gives the user a view of what has changed.
那么……当我们想要在 Doctrine 生命周期之外找到变更集时该怎么办?正如我在上面对@Ocramius 的帖子的评论中提到的,也许可以创建一个“只读”方法,它不会与实际的 Doctrine 持久性相混淆,但可以让用户了解发生了什么变化。
Here's an example of what I'm thinking of...
这是我在想什么的一个例子......
/**
* 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];
}
It's phrased here as a static method but could become a method inside UnitOfWork...?
它在这里被称为静态方法,但可以成为 UnitOfWork 中的一个方法......?
I'm not up to speed on all the internals of Doctrine, so might have missed something that has a side effect or misunderstood part of what this method does, but a (very) quick test of it seems to give me the results I expect to see.
我没有跟上 Doctrine 的所有内部结构,所以可能错过了一些有副作用的东西或者误解了这个方法的部分功能,但是对它的(非常)快速测试似乎给了我我期望的结果查看。
I hope this helps somebody!
我希望这对某人有所帮助!
回答by kxo
In my case, for sync data from a remote WS
to a local DB
I used this way to compare two entities (check il old entity has diffs from the edited entity).
就我而言,对于从远程WS
到本地的同步数据,DB
我使用这种方式来比较两个实体(检查旧实体与编辑实体的差异)。
I symply clone the persisted entity to have two objects not persisted:
我简单地克隆了持久化的实体,使其有两个未持久化的对象:
<?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);
Another possibility rather than compare objects directly:
另一种可能性而不是直接比较对象:
<?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
}
回答by ghazaleh javaheri
回答by Khandaker Toihidul Islam
It works for me 1. import EntityManager 2. Now you can use this anywhere into the class.
它对我有用 1. 导入 EntityManager 2. 现在你可以在课堂的任何地方使用它。
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';
}