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

Php 是否有一种内置方式来获取条令2实体中所有更改/更新的字段,php,symfony,doctrine-orm,doctrine,Php,Symfony,Doctrine Orm,Doctrine,假设我检索一个实体$e,并使用setter修改其状态: $e->setFoo('a'); $e->setBar('b'); 是否有可能检索已更改的字段数组 在我的示例中,我希望检索foo=>a,bar=>b PS:是的,我知道我可以修改所有访问器并手动实现此功能,但我正在寻找一些方便的方法来实现这一点 Doctrine\ORM\EntityManager#getUnitOfWork获取Doctrine\ORM\UnitOfWork 然后通过Doctrine\ORM\UnitOfWo

假设我检索一个实体
$e
,并使用setter修改其状态:

$e->setFoo('a');
$e->setBar('b');
是否有可能检索已更改的字段数组

在我的示例中,我希望检索
foo=>a,bar=>b

PS:是的,我知道我可以修改所有访问器并手动实现此功能,但我正在寻找一些方便的方法来实现这一点
Doctrine\ORM\EntityManager#getUnitOfWork
获取
Doctrine\ORM\UnitOfWork

然后通过
Doctrine\ORM\UnitOfWork\computeChangeSets()
触发变更集计算(仅适用于托管实体)

如果您不需要在整个对象图上进行迭代就能准确地知道要检查什么,那么您也可以使用类似的方法,如
doctor\ORM\UnitOfWork#重新计算SingleEntityChangeSet(doctor\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);
注意。如果试图在预更新侦听器中获取更新后的字段,请不要重新计算更改集,因为已经这样做了。只需调用getEntityChangeSet即可获取对实体所做的所有更改


警告:如评论中所述,此解决方案不应在条令事件侦听器之外使用。这将破坏条令的行为。

对于那些希望使用上述方法检查实体上的更改的人,请注意大号

$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
持久化例程在内部使用
$uow->computeChangeSets()
方法,导致上述解决方案无法使用。这也是方法注释中所写的:
@internal不要从外部调用。
使用
$uow->computeChangeSets()
检查对实体的更改后,将在方法末尾执行以下代码段(每个受管实体):

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


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

您可以使用跟踪更改

首先,实现NotifyPropertyChanged接口:

然后,只需对更改实体数据的每个方法调用\u 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中的一个方法

我对学说的所有内部内容都不了解,所以可能错过了一些有副作用的东西,或者误解了这种方法的部分作用,但是(非常)快速的测试似乎给了我预期的结果


我希望这能帮助别人

以防有人对与公认答案不同的方式感兴趣(这对我不起作用,我个人认为比这种方式更混乱)

我安装了每个实体上的每个属性,并在每个我考虑更改的属性中添加了一个@组({“CuxEdEnTyTyTyGROUP”})。这样,我就可以在旧实体和更新实体之间进行序列化,之后只需说$oldJson==$updatedJson。如果您感兴趣的属性或您想考虑的更改,JSON将不相同,如果您甚至想登记特定更改的内容,那么可以将其转换成数组并搜索差异。 我之所以使用这种方法,是因为我主要感兴趣的是一堆实体的一些属性,而不是整个实体。例如,如果您有一个@PrePersist@PreUpdate,并且您有一个最后的更新日期,该日期将始终被更新,因此您将始终得到实体是使用工作单元等更新的

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

检查此公共(而非内部)功能:

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

根据学说:

您所要做的就是在实体中实现
toArray
序列化
函数,并进行如下区分:

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

在我的例子中,对于从远程
WS
到本地
DB
的同步数据,我使用这种方法来比较两个实体(检查il旧实体与编辑的实体是否存在差异)

我将同步克隆持久化实体,使其具有两个未持久化的对象:

<?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);

它将返回更改

$entityManager->getUnitOfWork()->getEntityChangeSet($entity)

在我的例子中,我希望获取实体中关系的旧值,因此我使用了Doctrine\ORM\PersistentCollection::getSnapshot base,它对我来说很有用 1.导入实体管理器 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';
    }

在条令事件侦听器中使用
UnitOfWork
computeChangeSets
可能是首选方法

但是:如果您想在这个侦听器中持久化并刷新一个新实体,您可能会遇到很多麻烦。看起来,这场比赛正在进行中
<?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
}
$entityManager->getUnitOfWork()->getEntityChangeSet($entity)
  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';
    }
$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;
        }
    }
}