[+BUGFIX] Extbase (Persistence): Fixed updating of related objects. Thanks to Björn...
authorJochen Rau <j.rau@web.de>
Tue, 25 Jan 2011 18:48:24 +0000 (18:48 +0000)
committerJochen Rau <j.rau@web.de>
Tue, 25 Jan 2011 18:48:24 +0000 (18:48 +0000)
This is the second attempt after having reverted the first one in r3920. The patch includes some clean-up. Important additional changes:
* The objects within an ObjectStorage are now added to the queue. This is necessary to persist changes made to those contained objects without having changed the composition of objects.
* Moved the _memorizeCleanState() from the AbstractEntity to the AbstractDomainObject. In theory ValueObjects are immutable, but we need this as sometimes ValueObjects are stored in two steps (1. to get a UID, 2. to persist its properties). Maybe we have to recheck this for 1.4.
* Fixed some possible problems with detecting an ObjectStorage (namely SplObjectStorage->getUid()).

typo3/sysext/extbase/Classes/DomainObject/AbstractDomainObject.php
typo3/sysext/extbase/Classes/DomainObject/AbstractEntity.php
typo3/sysext/extbase/Classes/Persistence/Backend.php

index ff0e3c6..37d16a9 100644 (file)
@@ -60,6 +60,11 @@ abstract class Tx_Extbase_DomainObject_AbstractDomainObject implements Tx_Extbas
        private $_isClone = FALSE;
 
        /**
+        * @var An array holding the clean property values. Set right after reconstitution of the object
+        */
+       private $_cleanProperties;
+
+       /**
         * This is the magic __wakeup() method. It's invoked by the unserialize statement in the reconstitution process
         * of the object. If you want to implement your own __wakeup() method in your Domain Object you have to call
         * parent::__wakeup() first!
@@ -144,7 +149,7 @@ abstract class Tx_Extbase_DomainObject_AbstractDomainObject implements Tx_Extbas
        public function _getProperties() {
                $properties = get_object_vars($this);
                foreach ($properties as $propertyName => $propertyValue) {
-                       if ($propertyName{0} === '_') {
+                       if (substr($propertyName, 0, 1) === '_') {
                                unset($properties[$propertyName]);
                        }
                }
@@ -172,23 +177,122 @@ abstract class Tx_Extbase_DomainObject_AbstractDomainObject implements Tx_Extbas
 
        /**
         * Register an object's clean state, e.g. after it has been reconstituted
-        * from the database
+        * from the database.
         *
+        * @param string $propertyName The name of the property to be memorized. If omitted all persistable properties are memorized.
         * @return void
         */
-       public function _memorizeCleanState() {
+       public function _memorizeCleanState($propertyName = NULL) {
+               if ($propertyName !== NULL) {
+                       $this->_memorizePropertyCleanState($propertyName);
+               } else {
+                       $this->_cleanProperties = array();
+                       $properties = get_object_vars($this);
+                       foreach ($properties as $propertyName => $propertyValue) {
+                               if (substr($propertyName, 0, 1) === '_') continue; // Do not memorize "internal" properties
+                               $this->_memorizePropertyCleanState($propertyName);
+                       }
+               }
+       }
+
+       /**
+        * Register an properties's clean state, e.g. after it has been reconstituted
+        * from the database.
+        *
+        * @param string $propertyName The name of the property to be memorized. If omittet all persistable properties are memorized.
+        * @return void
+        */
+       public function _memorizePropertyCleanState($propertyName) {
+               $propertyValue = $this->$propertyName;
+               if (!is_array($this->_cleanProperties)) {
+                       $this->_cleanProperties = array();
+               }
+               if (is_object($propertyValue)) {
+                       $this->_cleanProperties[$propertyName] = clone($propertyValue);
+
+                       // We need to make sure the clone and the original object
+                       // are identical when compared with == (see _isDirty()).
+                       // After the cloning, the Domain Object will have the property
+                       // "isClone" set to TRUE, so we manually have to set it to FALSE
+                       // again. Possible fix: Somehow get rid of the "isClone" property,
+                       // which is currently needed in Fluid.
+                       if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
+                               $this->_cleanProperties[$propertyName]->_setClone(FALSE);
+                       }
+               } else {
+                       $this->_cleanProperties[$propertyName] = $propertyValue;
+               }
+       }
+
+       /**
+        * Returns a hash map of clean properties and $values.
+        *
+        * @return array
+        */
+       public function _getCleanProperties() {
+               return $this->_cleanProperties;
+       }
+
+       /**
+        * Returns the clean value of the given property. The returned value will be NULL if the clean state was not memorized before, or
+        * if the clean value is NULL.
+        *
+        * @param string $propertyName The name of the property to be memorized. If omittet all persistable properties are memorized.
+        * @return mixed The clean property value or NULL
+        */
+       public function _getCleanProperty($propertyName) {
+               if (is_array($this->_cleanProperties)) {
+                       return isset($this->_cleanProperties[$propertyName]) ? $this->_cleanProperties[$propertyName] : NULL;
+               } else {
+                       return NULL;
+               }
        }
        
        /**
-        * Returns TRUE if the properties were modified after reconstitution. However, value objects can be never updated.
+        * Returns TRUE if the properties were modified after reconstitution
         *
+        * @param string $propertyName An optional name of a property to be checked if its value is dirty
         * @return boolean
         */
        public function _isDirty($propertyName = NULL) {
+               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;
+                       }
+               } else {
+                       if ($this->isPropertyDirty($this->_getCleanProperty($propertyName), $this->$propertyName) === TRUE) return TRUE;
+               }
                return FALSE;
        }
 
        /**
+        * Checks the $value against the $cleanState.
+        *
+        * @param mixed $previousValue
+        * @param mixed $currentValue
+        * @return boolan
+        */
+       protected function isPropertyDirty($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_DomainObject_DomainObjectInterface) {
+                               $result = !is_object($previousValue) || (get_class($previousValue) !== get_class($currentValue)) || ($currentValue->getUid() !== $previousValue->getUid());
+                       } elseif ($currentValue instanceof Tx_Extbase_Persistence_ObjectMonitoringInterface) {
+                               $result = !is_object($previousValue) || $currentValue->_isDirty() || (get_class($previousValue) !== get_class($currentValue));
+                       } else {
+                               // For all other objects we do only a simple comparison (!=) as we want cloned objects to return the same values.
+                               $result = ($previousValue != $currentValue);
+                       }
+               } else {
+                       $result = ($previousValue !== $currentValue);
+               }
+               return $result;
+       }
+       
+       /**
         * Returns TRUE if the object has been clonesd, cloned, FALSE otherwise.
         *
         * @return boolean TRUE if the object has been cloned
index 04909bf..29f694c 100644 (file)
  * @version $ID:$
  */
 abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainObject_AbstractDomainObject {
-
-       /**
-        * @var An array holding the clean property values. Set right after reconstitution of the object
-        */
-       private $_cleanProperties;
-
-       /**
-        * Register an object's clean state, e.g. after it has been reconstituted
-        * from the database.
-        *
-        * @param string $propertyName The name of the property to be memorized. If omitted all persistable properties are memorized.
-        * @return void
-        */
-       public function _memorizeCleanState($propertyName = NULL) {
-               if ($propertyName !== NULL) {
-                       $this->_memorizePropertyCleanState($propertyName);
-               } else {
-                       $this->_cleanProperties = array();
-                       $properties = get_object_vars($this);
-                       foreach ($properties as $propertyName => $propertyValue) {
-                               if ($propertyName[0] === '_') continue; // Do not memorize "internal" properties
-                               $this->_memorizePropertyCleanState($propertyName);
-                       }
-               }
-       }
-
-       /**
-        * Register an properties's clean state, e.g. after it has been reconstituted
-        * from the database.
-        *
-        * @param string $propertyName The name of the property to be memorized. If omittet all persistable properties are memorized.
-        * @return void
-        */
-       public function _memorizePropertyCleanState($propertyName) {
-               $propertyValue = $this->$propertyName;
-               if (!is_array($this->_cleanProperties)) {
-                       $this->_cleanProperties = array();
-               }
-               if (is_object($propertyValue)) {
-                       $this->_cleanProperties[$propertyName] = clone($propertyValue);
-
-                       // We need to make sure the clone and the original object
-                       // are identical when compared with == (see _isDirty()).
-                       // After the cloning, the Domain Object will have the property
-                       // "isClone" set to TRUE, so we manually have to set it to FALSE
-                       // again. Possible fix: Somehow get rid of the "isClone" property,
-                       // which is currently needed in Fluid.
-                       if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
-                               $this->_cleanProperties[$propertyName]->_setClone(FALSE);
-                       }
-               } else {
-                       $this->_cleanProperties[$propertyName] = $propertyValue;
-               }
-       }
-
-       /**
-        * Returns a hash map of clean properties and $values.
-        *
-        * @return array
-        */
-       public function _getCleanProperties() {
-               return $this->_cleanProperties;
-       }
-
-       /**
-        * Returns the clean value of the given property. The returned value will be NULL if the clean state was not memorized before, or
-        * if the clean value is NULL.
-        *
-        * @param string $propertyName The name of the property to be memorized. If omittet all persistable properties are memorized.
-        * @return mixed The clean property value or NULL
-        */
-       public function _getCleanProperty($propertyName) {
-               if (is_array($this->_cleanProperties)) {
-                       return isset($this->_cleanProperties[$propertyName]) ? $this->_cleanProperties[$propertyName] : NULL;
-               } else {
-                       return NULL;
-               }
-       }
-       
-       /**
-        * Returns TRUE if the properties were modified after reconstitution
-        *
-        * @param string $propertyName An optional name of a property to be checked if its value is dirty
-        * @return boolean
-        */
-       public function _isDirty($propertyName = NULL) {
-               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;
-                       }
-               } else {
-                       if ($this->isPropertyDirty($this->_getCleanProperty($propertyName), $this->$propertyName) === TRUE) return TRUE;
-               }
-               return FALSE;
-       }
-
-       /**
-        * Checks the $value against the $cleanState.
-        *
-        * @param mixed $previousValue
-        * @param mixed $currentValue
-        * @return boolan
-        */
-       protected function isPropertyDirty($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));
-                       } else {
-                               // For all other objects we do only a simple comparison (!=) as we want cloned objects to return the same values.
-                               $result = ($previousValue != $currentValue);
-                       }
-               } else {
-                       $result = ($previousValue !== $currentValue);
-               }
-               return $result;
-       }
-       
 }
 ?>
\ No newline at end of file
index 32e4d84..f806b88 100644 (file)
@@ -360,96 +360,51 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        }
 
        /**
-        * Persists an object (instert, update) and its related objects (instert, update, delete).
+        * Persists the given object.
         *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
-        * @param string $parentPropertyName The name of the property the object is stored in
         * @return void
         */
        protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
                if (isset($this->visitedDuringPersistence[$object])) {
-                       return $this->visitedDuringPersistence[$object];
-               } else {
-                       $this->visitedDuringPersistence[$object] = $object->getUid();
+                       return;
                }
-
                $row = array();
                $queue = array();
-               $className = get_class($object);
-               $dataMap = $this->dataMapper->getDataMap($className);
-               $classSchema = $this->reflectionService->getClassSchema($className);
-
+               $dataMap = $this->dataMapper->getDataMap(get_class($object));
                $properties = $object->_getProperties();
                foreach ($properties as $propertyName => $propertyValue) {
                        if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
-
                        $columnMap = $dataMap->getColumnMap($propertyName);
-                       $propertyMetaData = $classSchema->getProperty($propertyName);
-                       $propertyType = $propertyMetaData['type'];
-                       // FIXME enable property-type check
-                       // $this->checkPropertyType($propertyType, $propertyValue);
-                       if (($propertyValue !== NULL) && ($propertyType === 'SplObjectStorage' || $propertyType === 'Tx_Extbase_Persistence_ObjectStorage')) {
-                               if ($object->_isNew() || $object->_isDirty($propertyName)) {
-                                       $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue, $row);
-                                       foreach ($propertyValue as $containedObject) {
-                                               if ($containedObject instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
-                                                       $queue[] = $containedObject;
-                                               }
+                       if ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
+                               if ($object->_isNew() || $propertyValue->_isDirty()) {
+                                       $this->persistObjectStorage($propertyValue, $object, $propertyName, $row);
+                               }
+                               foreach ($propertyValue as $containedObject) {
+                                       if ($containedObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
+                                               $queue[] = $containedObject;
                                        }
                                }
                        } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
                                if ($object->_isDirty($propertyName)) {
                                        if ($propertyValue->_isNew()) {
-                                               if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractEntity) {
-                                                       $this->insertObject($propertyValue);
-                                               } else {
-                                                       $this->persistValueObject($propertyValue);
-                                               }
+                                               $this->insertObject($propertyValue);
                                        }
                                        $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
-                                       $queue[] = $propertyValue;
                                }
-                       } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || $object->_isNew() || $object->_isDirty($propertyName)) {
+                               $queue[] = $propertyValue;
+                       } elseif ($object->_isNew() || $object->_isDirty($propertyName)) {
                                $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
                        }
                }
-
                if (count($row) > 0) {
                        $this->updateObject($object, $row);
-               }
-
-               if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
                        $object->_memorizeCleanState();
                }
-
+               $this->visitedDuringPersistence[$object] = $object->getUid();
                foreach ($queue as $queuedObject) {
                        $this->persistObject($queuedObject);
                }
-
-       }
-
-       /**
-        * Checks a value given against the expected type. If not matching, an
-        * UnexpectedTypeException is thrown. NULL is always considered valid.
-        *
-        * @param string $expectedType The expected type
-        * @param mixed $value The value to check
-        * @return void
-        * @throws Tx_Extbase_Persistence_Exception_UnexpectedType
-        */
-       protected function checkPropertyType($expectedType, $value) {
-               if ($value === NULL) {
-                       return;
-               }
-
-               if (is_object($value)) {
-                       if (!($value instanceof $expectedType)) {
-                               throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);
-                       }
-               } elseif ($expectedType !== gettype($value)) {
-                       throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . gettype($value), 1244465558);
-               }
        }
 
        /**
@@ -458,9 +413,9 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param mixed $propertyValue The property value
         * @return bool
         */
-       public function propertyValueIsLazyLoaded($propertyValue) {
+       protected function propertyValueIsLazyLoaded($propertyValue) {
                if ($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) return TRUE;
-               if (is_object($propertyValue) && get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') {
+               if ($propertyValue instanceof Tx_Extbase_Persistence_LazyObjectStorage) {
                        if ($propertyValue->isInitialized() === FALSE) {
                                return TRUE;
                        }
@@ -469,55 +424,17 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        }
 
        /**
-        * Persists the given value object.
-        *
-        * @return void
-        */
-       protected function persistValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object, $sortingPosition = 1) {
-               $result = $this->getUidOfAlreadyPersistedValueObject($object);
-               if ($result !== FALSE) {
-                       $object->_setProperty('uid', (int)$result);
-               } elseif ($object->_isNew()) {
-                       $row = array();
-                       $className = get_class($object);
-                       $dataMap = $this->dataMapper->getDataMap($className);
-                       $classSchema = $this->reflectionService->getClassSchema($className);
-
-                       $properties = $object->_getProperties();
-                       foreach ($properties as $propertyName => $propertyValue) {
-                               if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
-
-                               $columnMap = $dataMap->getColumnMap($propertyName);
-                               $propertyMetaData = $classSchema->getProperty($propertyName);
-                               $propertyType = $propertyMetaData['type'];
-                               // FIXME enable property-type check
-                               // $this->checkPropertyType($propertyType, $propertyValue);
-                               $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
-                       }
-                       $this->insertObject($object, $row);
-               }
-       }
-
-       /**
-        * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
-        *
-        * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
-        */
-       protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
-               return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
-       }
-
-       /**
         * Persists a an object storage. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
         * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
         * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
         *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
-        * @param string $propertyName The name of the property the related objects are stored in
-        * @param mixed $propertyValue The property value
+        * @param Tx_Extbase_Persistence_ObjectStorage $objectStorage The object storage to be persisted.
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object. One of the properties holds the object storage.
+        * @param string $propertyName The name of the property holding the object storage.
+        * @param array $row The row array of the parent object to be persisted. It's passed by reference and gets filled with either a comma separated list of uids (csv) or the number of contained objects. 
         * @return void
         */
-       protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$queue, array &$row) {
+       protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$row) {
                $className = get_class($parentObject);
                $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
                $columnName = $columnMap->getColumnName();
@@ -539,11 +456,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $sortingPosition = 1;
                foreach ($objectStorage as $object) {
                        if ($object->_isNew()) {
-                               if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
-                                       $this->insertObject($object);
-                               } else {
-                                       $this->persistValueObject($object, $sortingPosition);
-                               }
+                               $this->insertObject($object);
                        }
                        $currentUids[] = $object->getUid();
                        $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
@@ -671,8 +584,17 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
         * @return void
         */
-       protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $row = array()) {
+       protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
+               if ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
+                       $result = $this->getUidOfAlreadyPersistedValueObject($object);
+                       if ($result !== FALSE) {
+                               $object->_setProperty('uid', (int)$result);
+                               return;
+                       }
+               }
+
                $dataMap = $this->dataMapper->getDataMap(get_class($object));
+               $row = array();
                $this->addCommonFieldsToRow($object, $row);
                if($dataMap->getLanguageIdColumnName() !== NULL) {
                        $row[$dataMap->getLanguageIdColumnName()] = -1;
@@ -690,6 +612,15 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        }
 
        /**
+        * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
+        *
+        * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
+        */
+       protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
+               return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
+       }
+
+       /**
         * Inserts mm-relation into a relation table
         *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object