क्या डॉक्ट्रिन 2 इकाई में सभी परिवर्तित / अद्यतन फ़ील्ड प्राप्त करने के लिए एक अंतर्निहित तरीका है


81

मान लीजिए कि मैं एक इकाई को पुनः प्राप्त करता हूं $eऔर बसने वालों के साथ अपनी स्थिति को संशोधित करता हूं :

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

क्या खेतों की एक सरणी को पुनः प्राप्त करने की कोई संभावना है जिसे बदल दिया गया है?

मेरे उदाहरण के मामले में मैं foo => a, bar => bपरिणाम के रूप में प्राप्त करना चाहूंगा

पुनश्च: हाँ, मुझे पता है कि मैं सभी एक्सेसरों को संशोधित कर सकता हूं और इस सुविधा को मैन्युअल रूप से लागू कर सकता हूं, लेकिन मैं ऐसा करने का कोई आसान तरीका ढूंढ रहा हूं

जवाबों:


150

आप 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);

ध्यान दें। यदि पूर्व-अद्यतन श्रोता के अंदर अपडेट किए गए फ़ील्ड को प्राप्त करने की कोशिश कर रहे हैं , तो परिवर्तन को फिर से स्थापित न करें, क्योंकि यह पहले ही हो चुका है। इकाई में किए गए सभी परिवर्तनों को प्राप्त करने के लिए बस getEntityChangeSet को कॉल करें।

चेतावनी: जैसा कि टिप्पणियों में बताया गया है, इस समाधान का उपयोग डॉक्ट्रिन इवेंट श्रोताओं के बाहर नहीं किया जाना चाहिए। इससे डॉक्ट्रिन का व्यवहार टूट जाएगा।


4
नीचे दी गई टिप्पणी कहती है कि यदि आप $ em-> computerChangeSets () कहते हैं कि यह नियमित रूप से $ em-> जारी रहेगा () है जिसे आप बाद में कॉल करते हैं क्योंकि ऐसा नहीं लगेगा कि कुछ भी बदला हुआ है। यदि हां, तो समाधान क्या है, क्या हम उस फ़ंक्शन को नहीं कहते हैं?
चाडविक मेयर

4
आप UnitOfWork के जीवनचक्र ईवेंट श्रोताओं के बाहर इस API का उपयोग करने वाले नहीं हैं।
ओकरामियस

6
आपको नहीं करना चाहिए यही नहीं ORM का उपयोग करने के लिए किया जाता है। ऐसे मामलों में मैन्युअल रूप से भिन्नता का उपयोग करें, लागू किए गए कार्यों से पहले और बाद में डेटा की एक प्रति रखकर।
ओकरामियस

6
@ ऑकरामियस, यह वह नहीं हो सकता है जिसका उपयोग इसके लिए किया जाना चाहिए, लेकिन यह निस्संदेह उपयोगी होगा । यदि केवल साइड इफेक्ट के बिना परिवर्तनों की गणना करने के लिए डॉक्ट्रिन का उपयोग करने का एक तरीका था। उदाहरण के लिए, यदि UOW में कोई नया तरीका / वर्ग था, तो आप बदलावों की एक सरणी के लिए पूछ सकते हैं। लेकिन जो वास्तविक दृढ़ता चक्र को किसी भी तरह से बदल / प्रभावित नहीं करेगा। क्या यह संभव है?
कैपोनिका

3
$ Em-> getUnitOfWork () -> getOriginalEntityData ($ संस्था)
वैक्स केज

41

उन लोगों के लिए बड़ा सावधान संकेत जो उपर्युक्त विधि का उपयोग करके इकाई पर होने वाले परिवर्तनों की जांच करना चाहते हैं।

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

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

$actualDataसरणी इकाई की संपत्तियों के लिए वर्तमान परिवर्तन रखती है। जैसे ही इन्हें लिखा जाता है $this->originalEntityData[$oid], ये अभी तक बने नहीं रहने वाले परिवर्तन को इकाई का मूल गुण माना जाता है।

बाद में, जब $em->persist($entity)इकाई में परिवर्तनों को सहेजने के लिए कहा जाता है, तो इसमें विधि भी शामिल होती है $uow->computeChangeSets(), लेकिन अब यह इकाई में परिवर्तनों को खोजने में सक्षम नहीं होगा, क्योंकि ये अभी तक निरंतर परिवर्तन नहीं माना जाता है जो कि इकाई के मूल गुणों को माना जाता है। ।


1
यह ठीक वैसा ही है जैसा @Ocramius चेक किए गए उत्तर में निर्दिष्ट किया गया है
zerkms

1
$ uow = क्लोन $ em-> getUnitOfWork (); उस समस्या को हल करता है
tvlooy

1
यूओडब्ल्यू का क्लोनिंग समर्थित नहीं है और अवांछित परिणाम पैदा कर सकता है।
ओकरामियस

9
@ शालिक डेरेविन्को तो आप क्या सुझाव देते हैं? बस फोन नहीं है $uow->computerChangeSets()? या क्या वैकल्पिक विधि?
चाडविक मेयर

हालांकि यह पोस्ट वास्तव में उपयोगी है (यह उपरोक्त उत्तर के लिए एक बड़ी चेतावनी है) यह अपने आप में एक समाधान नहीं है। मैंने इसके बजाय स्वीकृत उत्तर को संपादित किया है।
Matthieu Napoli

39

इस सार्वजनिक (और आंतरिक नहीं) फ़ंक्शन की जाँच करें:

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

1
इस स्थिति को कैसे लागू किया जाए जब एंटिटी दूसरे से संबंधित हो (OneToOne हो सकती है)? यह मामला जब मैं शीर्ष-lvl इकाई पर getOriginalEntityData चलाता हूं, तो इसकी संबंधित इकाइयां मूल डेटा वास्तव में मूल नहीं हैं, बल्कि अद्यतन हैं।
mu4ddi3

5

आप सूचित नीतियों के साथ परिवर्तनों को ट्रैक कर सकते हैं

सबसे पहले, 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;
        }
    }
}

7
एक इकाई के अंदर श्रोता ?! पागलपन! गंभीरता से, ट्रैकिंग नीति एक अच्छे समाधान की तरह दिखती है, क्या इकाई के बाहर श्रोताओं को परिभाषित करने का कोई तरीका है (मैं Symfony2 DoctrineBundle का उपयोग कर रहा हूं)।
गिल्डस

यह गलत समाधान है। आपको डोमेन ईवेंट की ओर देखना चाहिए। github.com/gpslab/domain-event
ghost404


2

यदि कोई व्यक्ति अभी भी स्वीकृत उत्तर की तुलना में एक अलग तरीके से रुचि रखता है (यह मेरे लिए काम नहीं कर रहा था और मैंने इसे अपने व्यक्तिगत विचार में इस तरह से गड़बड़ पाया)।

मैंने JMS Serializer बंडल स्थापित किया है और प्रत्येक इकाई पर और प्रत्येक संपत्ति पर जिसे मैं एक बदलाव मानता हूं, मैंने एक @ समूह ({"change_entity_group"}) जोड़ा। इस तरह, मैं फिर पुरानी इकाई, और अद्यतन इकाई के बीच एक क्रमांकन बना सकता हूं और उसके बाद यह केवल $ oldJson == $ अपडेट किया गया। यदि आप जिन गुणों में रुचि रखते हैं या जिन्हें आप बदलावों पर विचार करना चाहते हैं, JSON समान नहीं होगा और यदि आप विशेष रूप से परिवर्तित WHAT को पंजीकृत करना चाहते हैं तो आप इसे एक सरणी में बदल सकते हैं और अंतरों की खोज कर सकते हैं।

मैंने इस पद्धति का उपयोग किया क्योंकि मैं मुख्य रूप से संस्थाओं के एक समूह के कुछ गुणों में रुचि रखता था और पूरी तरह से इकाई में नहीं था। एक उदाहरण जहां यह उपयोगी होगा यदि आपके पास @PrePersist @PreUpdate है और आपके पास अंतिम_ दिनांक तिथि है, तो वह हमेशा अपडेट की जाएगी इसलिए आपको हमेशा यह मिलेगा कि इकाई को कार्य और सामान की तरह उपयोग करके अपडेट किया गया था।

आशा है कि यह विधि किसी के लिए उपयोगी है।


1

इसलिए ... क्या करना है जब हम सिद्धांत जीवन चक्र के बाहर एक परिवर्तन खोजना चाहते हैं? जैसा कि ऊपर @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 के अंदर एक विधि बन सकती है ...?

मैं डॉक्ट्रिन के सभी इंटर्नल पर गति करने के लिए नहीं हूं, इसलिए ऐसा कुछ याद किया जा सकता है जिसका साइड इफेक्ट या गलत तरीका है कि यह विधि क्या करती है, लेकिन इसका (बहुत) त्वरित परीक्षण मुझे लगता है कि मुझे परिणाम देने की उम्मीद है देखना।

मुझे आशा है कि यह किसी की मदद करता है!


1
ठीक है, अगर हम कभी मिलते हैं, तो आपको एक कुरकुरा उच्च पांच मिलता है! इसके लिए बहुत-बहुत धन्यवाद। 2 अन्य कार्यों में भी उपयोग करना बहुत आसान है: hasChangesऔर getChanges(बाद में पूरे बदलाव के बदले बदले हुए क्षेत्र प्राप्त करने के लिए)।
13

0

मेरे मामले में, दूरस्थ 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);

सीधे वस्तुओं की तुलना करने के बजाय एक और संभावना:

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

0

मेरे मामले में मैं, इकाई में संबंध के पुराने मूल्य प्राप्त करने के तो मैं पर सिद्धांत \ ORM \ PersistentCollection :: getSnapshot आधार का उपयोग करना चाहते इस


0

यह मेरे लिए काम करता है 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';
    }

0

के साथ कार्य करना UnitOfWorkऔर computeChangeSets एक सिद्धांत घटना श्रोता के भीतर शायद पसंदीदा तरीका है।

हालाँकि : यदि आप इस श्रोता के भीतर एक नई इकाई को बनाए रखना चाहते हैं, तो आप बहुत परेशानी का सामना कर सकते हैं। जैसा कि लगता है, एकमात्र उचित श्रोता onFlushअपनी समस्याओं के सेट के साथ होगा ।

इसलिए मैं एक सरल लेकिन हल्की तुलना का सुझाव देता हूं, जिसे कंट्रोलर और यहां तक ​​कि सेवाओं के भीतर इस्तेमाल किया जा सकता है EntityManagerInterface( बस ऊपर पोस्ट में @ मोहम्मद रामरमी द्वारा प्रेरित ):

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

ध्यान दें : यह तार, डेटाटाइम्स, बूल, पूर्णांक और फ़्लोट्स की तुलना सही ढंग से करता है, हालांकि वस्तुओं पर (परिपत्र संदर्भ समस्याओं के कारण) विफल रहता है। कोई इन वस्तुओं की गहराई से तुलना कर सकता है, लेकिन उदाहरण के लिए टेक्स्ट चेंज डिटेक्शन इवेंट श्रोताओं से निपटने के लिए पर्याप्त और अधिक सरल है।

और जानकारी:

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.