[+BUGFIX] Extbase [Persistence]: Fixed updating of 1:n related childs. Resolves ...
authorJochen Rau <j.rau@web.de>
Tue, 18 Jan 2011 01:59:08 +0000 (01:59 +0000)
committerJochen Rau <j.rau@web.de>
Tue, 18 Jan 2011 01:59:08 +0000 (01:59 +0000)
Merge commit '3e5bbbba16521d06e49ad983ca52a7a9549eba91'

typo3/sysext/extbase/Classes/DomainObject/AbstractEntity.php
typo3/sysext/extbase/Tests/Unit/DomainObject/AbstractEntityTest.php

index 04909bf..b88e4f8 100644 (file)
@@ -120,37 +120,67 @@ abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainO
                if ($this->uid !== NULL && is_array($this->_cleanProperties) && $this->uid != $this->_getCleanProperty('uid')) throw new Tx_Extbase_Persistence_Exception_TooDirty('The uid "' . $this->uid . '" has been modified, that is simply too much.', 1222871239);
                if ($propertyName === NULL) {
                        foreach ($this->_getCleanProperties() as $propertyName => $cleanPropertyValue) {
-                               if ($this->isPropertyDirty($cleanPropertyValue, $this->$propertyName) === TRUE) return TRUE;
+                               if ($this->_propertyValueHasChanged($cleanPropertyValue, $this->$propertyName) === TRUE) return TRUE;
                        }
                } else {
-                       if ($this->isPropertyDirty($this->_getCleanProperty($propertyName), $this->$propertyName) === TRUE) return TRUE;
+                       if ($this->_propertyValueHasChanged($this->_getCleanProperty($propertyName), $this->$propertyName) === TRUE) return TRUE;
                }
                return FALSE;
        }
 
        /**
-        * Checks the $value against the $cleanState.
+        * Checks if the current value of a property differs from the previous value.
         *
         * @param mixed $previousValue
         * @param mixed $currentValue
         * @return boolan
         */
-       protected function isPropertyDirty($previousValue, $currentValue) {
+       protected function _propertyValueHasChanged($previousValue, $currentValue) {
                $result = FALSE;
-               // In case it is an object and it implements the ObjectMonitoringInterface, we call _isDirty() instead of a simple comparison of objects.
-               // We do this, because if the object itself contains a lazy loaded property, the comparison of the objects might fail even if the object didn't change
                if (is_object($currentValue)) {
-                       if ($currentValue instanceof Tx_Extbase_Persistence_ObjectMonitoringInterface) {
-                               $result = !is_object($previousValue) || $currentValue->_isDirty() || (get_class($previousValue) !== get_class($currentValue));
+                       if ($currentValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
+                               if ($previousValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
+                                       $result = $this->_getHash($previousValue) !== $this->_getHash($currentValue);
+                               } else {
+                                       $result = TRUE;
+                               }
+                       } elseif ($currentValue instanceof Tx_Extbase_Persistence_ObjectMonitoringInterface) {
+                               $result = $currentValue->_isDirty();
                        } else {
-                               // For all other objects we do only a simple comparison (!=) as we want cloned objects to return the same values.
-                               $result = ($previousValue != $currentValue);
+                               $result = $previousValue != $currentValue;
                        }
                } else {
                        $result = ($previousValue !== $currentValue);
                }
                return $result;
        }
-       
+
+       /**
+        * Generates the value hash for the object
+        *
+        * @var Tx_Extbase_DomainObject_AbstractDomainObject $domainObject The domain object to generate the hash code from
+        * @return string The hash code
+        */
+       protected function _getHash(Tx_Extbase_DomainObject_AbstractDomainObject $domainObject) {
+               $hashSource = get_class($domainObject);
+               $properties = $domainObject->_getProperties();
+               if ($domainObject instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
+                       unset($properties['uid']);
+               }
+               foreach ($properties as $propertyName => $propertyValue) {
+                       if (is_array($propertyValue)) {
+                               $hashSource .= serialize($propertyValue);
+                       } elseif (!is_object($propertyValue)) {
+                               $hashSource .= $propertyValue;
+                       } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractEntity) {
+                               $hashSource .= get_class($propertyValue);
+                               $hashSource .= $propertyValue->getUid();
+                       } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
+                               $hashSource .= $this->_getHash($propertyValue);
+                       }
+               }
+               return sha1($hashSource);
+       }
+
 }
 ?>
\ No newline at end of file
index d12350f..5156201 100644 (file)
@@ -74,7 +74,7 @@ class Tx_Extbase_Tests_Unit_DomainObject_AbstractEntityTest extends Tx_Extbase_T
                $domainObject->bar = 'It is raining outside';
                $domainObject->_memorizeCleanState();
 
-               $this->assertFalse($domainObject->_isDirty());
+               $this->assertEquals($domainObject->_isDirty(), FALSE);
        }
 
        /**
@@ -101,7 +101,119 @@ class Tx_Extbase_Tests_Unit_DomainObject_AbstractEntityTest extends Tx_Extbase_T
                $domainObject->bar = 'It is raining outside';
                $domainObject->_memorizeCleanState();
 
-               $this->assertFalse($domainObject->_isDirty());
+               $this->assertEquals($domainObject->_isDirty(), FALSE);
        }
+
+       /**
+        * dataProvider for aChangedValueCanBeDetected
+        */
+       public function previousAndCurrentValue() {
+               $className1 = uniqid('Class_');
+               eval('class ' . $className1 . ' extends Tx_Extbase_DomainObject_AbstractEntity {
+                       public $bar = 42;
+               }');
+
+               // An entity with a simple property
+               $entity1 = new $className1;
+               $entity1->_setProperty('uid', 123);
+
+               // A clone of the entity above
+               $clonedEntity1 = clone $entity1;
+
+               $className2 = uniqid('Class_');
+               eval('class ' . $className2 . ' extends Tx_Extbase_DomainObject_AbstractEntity {
+                       public $bar = 99;
+               }');
+               
+               // A different entity
+               $entity2 = new $className2;
+               $entity2->_setProperty('uid', 321);
+
+               // An entity similar to the second one but with a different uid
+               $entity3 = new $className2;
+               $entity3->_setProperty('uid', 222);
+
+               // An entity identical to the second one but pointing to a different memory space (identical in sense of Domain-Driven Design)
+               $entity4 = new $className2;
+               $entity4->_setProperty('uid', 321);
+
+               // An entity similar to the first entity (same properties and uid, but different class)
+               $entity5 = new $className2;
+               $entity5->bar = 42;
+               $entity5->_setProperty('uid', 123);
+
+               $className3 = uniqid('Class_');
+               eval('class ' . $className3 . ' extends Tx_Extbase_DomainObject_AbstractValueObject {
+                       public $bar = 49;
+                       public $baz = "The quick brown...";
+               }');
+
+               // A simple value object
+               $valueObject1 = new $className3;
+               $valueObject1->_setProperty('uid', 321);
+
+               // A clone of the value object
+               $clonedValueObject1 = clone $valueObject1;
+
+               // A value object with the same property values than the first one but with a different uid (identical to the first value object)
+               $valueObject2 = new $className3;
+               $valueObject2->_setProperty('uid', 111);
+
+               // A value with different property value but the same uid than the first one (not identical to the first value object)
+               $valueObject3 = new $className3;
+               $valueObject->bar = 456;
+               $valueObject3->_setProperty('uid', 111);
+
+               // An empty Object Storage and its clone
+               $emptyObjectStorage = new Tx_Extbase_Persistence_ObjectStorage();
+               $clonedObjectStorage = clone $emptyObjectStorage;
+
+               // An Object Storage in a clean state holding an object, and its clone
+               $objectStorage1 = clone $emptyObjectStorage;
+               $objectStorage1->attach(new stdClass);
+               $objectStorage1->_memorizeCleanState();
+               $clonedObjectStorage1 = clone $objectStorage1;
+
+               // An Object Storage in a dirty state holding an object
+               $objectStorage2 = clone $emptyObjectStorage;
+               $clonedObjectStorage2 = clone $objectStorage2;
+               $objectStorage2->attach(new stdClass);
+               
+               return array(
+                       'Same integer values' => array(42, 42, FALSE),
+                       'Different integer values' => array(42, 666, TRUE),
+                       'Same number but different types' => array(42, '42', TRUE),
+                       'Change from NULL to 0 value' => array(NULL, 0, TRUE),
+                       'Change from 0 to NULL value' => array(0, NULL, TRUE),
+                       'Two different standard class instances' => array(new stdClass, new stdClass, FALSE),
+                       'Change from NULL to standard class instance' => array(NULL, new stdClass, TRUE),
+                       'Change from standard class instance to NULL' => array(new stdClass, NULL, TRUE),
+                       'Two equal entities (same memory pointer)' => array($entity1, $entity1, FALSE),
+                       'Entities with different class, uid, and properties' => array($entity1, $entity2, TRUE),
+                       'Entities with different class but the same uid and properties' => array($entity1, $entity5, TRUE),
+                       'Entities of same class and property values but different uid' => array($entity2, $entity3, TRUE),
+                       'Same entity (same class, uid, and property values)' => array($entity2, $entity4, FALSE),
+                       'An entity and its clone' => array($entity1, $clonedEntity1, FALSE),
+                       'Value objects with the same property values but a different uid' => array($valueObject1, $valueObject2, FALSE),
+                       'Value objects with the same uid but different property values' => array($valueObject2, $valueObject3, FALSE),
+                       'A Value object and its clone' => array($valueObject1, $clonedValueObject1, FALSE),
+                       'An empty ObjectStorage and its clone' => array($clonedObjectStorage, $emptyObjectStorage, FALSE),
+                       'An ObjectStorage and its clone' => array($clonedObjectStorage1, $objectStorage1, FALSE),
+                       'Modified ObjectStorage' => array($clonedObjectStorage2, $objectStorage2, TRUE),
+               );
+       }
+
+       /**
+        * @test
+        * @dataProvider previousAndCurrentValue
+        */
+       public function aChangedValueCanBeDetected($previousValue, $currentValue, $expectedResult) {
+               $className = uniqid('Class_');
+               eval('class ' . $className . ' extends Tx_Extbase_DomainObject_AbstractEntity {}');
+               $mockObject = $this->getMock($this->buildAccessibleProxy($className), array('dummy'), array(), '', FALSE);
+               
+               $this->assertEquals($expectedResult, $mockObject->_call('_propertyValueHasChanged', $previousValue, $currentValue));
+       }
+
 }
 ?>
\ No newline at end of file