programing

원칙 2 엔티티에서 변경/업데이트된 모든 필드를 가져올 수 있는 기본 제공 방법이 있습니까?

javaba 2023. 1. 9. 21:26
반응형

원칙 2 엔티티에서 변경/업데이트된 모든 필드를 가져올 수 있는 기본 제공 방법이 있습니까?

를 들어 엔티티를 해 봅시다.$e이치노

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

변경된 필드 배열을 가져올 수 있습니까?

이 예제의 경우 검색하려고 합니다.foo => a, bar => b

PS: 네, 모든 액세스 장치를 변경하여 이 기능을 수동으로 구현할 수 있지만, 이 기능을 쉽게 구현할 수 있는 방법을 찾고 있습니다.

하시면 됩니다.Doctrine\ORM\EntityManager#getUnitOfWorkDoctrine\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);

참고. 업데이트수신기 내에서 업데이트된 필드를 가져오려고 할 경우 이미 수행된 변경 집합은 다시 계산하지 마십시오.단순히 getEntityChangeSet을 호출하여 엔티티에 대한 모든 변경을 가져옵니다.

경고:코멘트에서 설명한 바와 같이 이 솔루션은 독트린이벤트 리스너 이외에서는 사용할 수 없습니다.이것으로 독트린의 행동이 깨질 것이다.

다음 퍼블릭(내부 아님) 기능을 확인합니다.

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

에서 설명한 방법을 사용하여 기업의 변경 사항을 확인하고자 하는 경우 크게 주의하시기 바랍니다.

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

$uow->computeChangeSets()method는 상기의 솔루션을 사용할 수 없게 하는 방법으로 영속적인 루틴에 의해 내부적으로 사용됩니다.그 있어요.@internal Don't call from the outside에 대한 에서 확인한 후$uow->computeChangeSets()다음 코드 조각은 메서드의 마지막에 실행됩니다(관리 대상 엔티티별로).

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

$actualData에 대한 현재 을 유지합니다.이것들은 에 써지는 대로$this->originalEntityData[$oid]이 변경은 아직 지속되지 않은 엔티티의 원래 속성으로 간주됩니다.

그,가 되면$em->persist($entity) 엔티티에 대한 변경을 저장하기 위해 메서드도 됩니다.또한 메서드도 포함됩니다.$uow->computeChangeSets()그러나 아직 지속되지 않은 변경은 엔티티의 원래 속성으로 간주되기 때문에 엔티티에 대한 변경은 찾을 수 없습니다.

통지 정책을 사용하여 변경을 추적할 수 있습니다.

먼저 Notify를 구현합니다.PropertyChanged 인터페이스:

/**
 * @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;
        }
    }
}

변경 내용이 반환됩니다.

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

그럼... 독트린의 수명주기를 벗어나서 변화를 찾으려면 어떻게 해야 하죠?위의 @Ocramius의 투고 코멘트에서도 언급했듯이, 실제 독트린의 지속성에 영향을 주지 않고 변경된 내용을 사용자에게 보여주는 "read only" 메서드를 작성할 수 있을지도 모릅니다.

여기 제가 생각하는 것의 예가 있습니다.

/**
 * 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];
}

여기서는 정적 메서드로 표현되지만 Unit Of Work 내부에서는 메서드가 될 수 있습니다...?

저는 독트린의 모든 내면에 정통하지 않기 때문에 부작용이 있는 것을 놓쳤거나 이 방법의 일부를 잘못 이해했을 수도 있지만, (매우) 간단한 테스트를 통해 기대하는 결과를 얻을 수 있을 것 같습니다.

이게 도움이 됐으면 좋겠네요!

아직 인정된 답변과는 다른 방법에 관심이 있는 경우(제 개인적인 의견으로는 이 방법보다 더 혼란스러웠습니다).

JMS Serializer Bundle을 설치하고 변경을 고려하는 각 엔티티 및 각 속성에 @Group("changed_entity_group")을 추가했습니다.이렇게 하면 이전 엔티티와 업데이트된 엔티티 간에 직렬화를 만들 수 있으며, 그 후에는 $oldJson == $updatedJson이라고 말하면 됩니다.관심 있는 속성이나 변경을 검토하고 싶은 속성이 JSON과 동일하지 않은 경우, 특별히 변경된 내용을 등록하고 싶은 경우에도 어레이로 변환하여 차이점을 검색할 수 있습니다.

저는 주로 여러 주체의 일부 특성에 관심이 있고 주체에 완전히 관심이 없었기 때문에 이 방법을 사용했습니다.예를 들어 @PrePersist @PreUpdate가 있고 last_update 날짜가 있는 경우 항상 업데이트되므로 엔티티가 작업 단위 등을 사용하여 업데이트되었음을 항상 확인할 수 있습니다.

이 방법이 누구에게나 도움이 되길 바랍니다.

내 경우 엔티티에서 오래된 관계 가치를 얻고 싶기 때문에 원칙을 사용한다.ORM\Persistent Collection:: getSnapshot 기반

업업의 UnitOfWork ★★★★★★★★★★★★★★★★★」computeChangeSets 아마도 권장되는 방법일 것입니다.

,이 청취자 내에서 새로운 엔티티를 계속 플러시하고 싶다면 많은 문제에 직면할 수 있습니다.보아하니, 제대로 듣는 유일한 사람은onFlush그 나름의 문제를 안고 있습니다.

이는 컨트롤러 할 수도 있고 심지어 .이 비교는 컨트롤러 및 서비스 내에서 간단히 사용할 수 있습니다.EntityManagerInterface(위 포스트의 @Mohamed Ramrami에서 영감을 받았습니다):

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

주의: 문자열, 데이터 시간, Bool, 정수를 비교하지만 개체에서는 오류가 발생합니다(Circular 참조 문제 때문에).이러한 오브젝트를 보다 상세하게 비교할 수 있지만, 예를 들어 텍스트 변경 검출은 이벤트 리스너를 처리하는 것보다 훨씬 간단하고 충분합니다.

상세 정보:

로부터의 동기 WSDB이 방법을 사용하여 두 엔티티를 비교했습니다(오래된 엔티티가 편집된 엔티티와 다른지 확인).

지속되지 않는 두 개의 개체를 가지도록 영구 엔티티를 복제합니다.

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

저는 1로 동작합니다.1 . import Entity Manager 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';
    }

언급URL : https://stackoverflow.com/questions/9057558/is-there-a-built-in-way-to-get-all-of-the-changed-updated-fields-in-a-doctrine-2

반응형