Merge branch 'issue3875'
authorJochen Rau <j.rau@web.de>
Sat, 7 Nov 2009 14:04:29 +0000 (14:04 +0000)
committerJochen Rau <j.rau@web.de>
Sat, 7 Nov 2009 14:04:29 +0000 (14:04 +0000)
24 files changed:
typo3/sysext/extbase/Classes/Dispatcher.php
typo3/sysext/extbase/Classes/Domain/Model/FrontendUser.php
typo3/sysext/extbase/Classes/DomainObject/AbstractDomainObject.php
typo3/sysext/extbase/Classes/DomainObject/AbstractEntity.php
typo3/sysext/extbase/Classes/DomainObject/DomainObjectInterface.php
typo3/sysext/extbase/Classes/Persistence/Backend.php
typo3/sysext/extbase/Classes/Persistence/Exception/UnexpectedTypeException.php [new file with mode: 0644]
typo3/sysext/extbase/Classes/Persistence/LazyLoadingProxy.php
typo3/sysext/extbase/Classes/Persistence/LazyObjectStorage.php
typo3/sysext/extbase/Classes/Persistence/Mapper/ColumnMap.php
typo3/sysext/extbase/Classes/Persistence/Mapper/DataMap.php
typo3/sysext/extbase/Classes/Persistence/Mapper/DataMapper.php
typo3/sysext/extbase/Classes/Persistence/ObjectStorage.php
typo3/sysext/extbase/Classes/Persistence/Query.php
typo3/sysext/extbase/Classes/Persistence/Storage/Typo3DbBackend.php
typo3/sysext/extbase/Classes/Persistence/Typo3QuerySettings.php
typo3/sysext/extbase/Classes/Persistence/Typo3QuerySettingsInterface.php
typo3/sysext/extbase/Classes/Property/Mapper.php
typo3/sysext/extbase/Classes/Reflection/ClassSchema.php
typo3/sysext/extbase/Classes/Reflection/Service.php
typo3/sysext/extbase/Classes/Utility/Extension.php
typo3/sysext/extbase/Tests/DomainObject/AbstractEntity_testcase.php
typo3/sysext/extbase/Tests/Persistence/Repository_testcase.php
typo3/sysext/extbase/ext_typoscript_setup.txt

index c3456de..20a7f8c 100644 (file)
@@ -95,7 +95,9 @@ class Tx_Extbase_Dispatcher {
                //$extutil = new Tx_Extbase_Utility_Extension;
                //$extutil->createAutoloadRegistryForExtension('extbase', t3lib_extMgm::extPath('extbase'));
                //$extutil->createAutoloadRegistryForExtension('fluid', t3lib_extMgm::extPath('fluid'));
-
+               
+               $GLOBALS['TT']->push('Extbase is called.','');
+               $GLOBALS['TT']->push('Extbase gets initialized.','');
                if (!is_array($configuration)) {
                        t3lib_div::sysLog('Extbase was not able to dispatch the request. No configuration.', 'extbase', t3lib_div::SYSLOG_SEVERITY_ERROR);
                        return $content;
@@ -116,7 +118,9 @@ class Tx_Extbase_Dispatcher {
                $requestHashService->verifyRequest($request);
 
                $persistenceManager = self::getPersistenceManager();
+               $GLOBALS['TT']->pull();
 
+               $GLOBALS['TT']->push('Extbase dispatches request.','');
                $dispatchLoopCount = 0;
                while (!$request->isDispatched()) {
                        if ($dispatchLoopCount++ > 99) throw new Tx_Extbase_MVC_Exception_InfiniteLoop('Could not ultimately dispatch the request after '  . $dispatchLoopCount . ' iterations.', 1217839467);
@@ -126,16 +130,20 @@ class Tx_Extbase_Dispatcher {
                        } catch (Tx_Extbase_MVC_Exception_StopAction $ignoredException) {
                        }
                }
+               $GLOBALS['TT']->pull();
 
+               $GLOBALS['TT']->push('Extbase persists all changes.','');
                $flashMessages = t3lib_div::makeInstance('Tx_Extbase_MVC_Controller_FlashMessages'); // singleton
-               $flashMessages->persist();
-
+               $flashMessages->persist();              
                $persistenceManager->persistAll();
+               $GLOBALS['TT']->pull();
+               
                self::$reflectionService->shutdown();
                if (count($response->getAdditionalHeaderData()) > 0) {
                        $GLOBALS['TSFE']->additionalHeaderData[$request->getControllerExtensionName()] = implode("\n", $response->getAdditionalHeaderData());
                }
                $response->sendHeaders();
+               $GLOBALS['TT']->pull();
                return $response->getContent();
        }
 
@@ -238,9 +246,12 @@ class Tx_Extbase_Dispatcher {
         */
        public static function getPersistenceManager() {
                if (self::$persistenceManager === NULL) {
+                       $identityMap = t3lib_div::makeInstance('Tx_Extbase_Persistence_IdentityMap');
+                       
                        $dataMapper = t3lib_div::makeInstance('Tx_Extbase_Persistence_Mapper_DataMapper'); // singleton
+                       $dataMapper->injectIdentityMap($identityMap);
                        $dataMapper->injectReflectionService(self::$reflectionService);
-
+                       
                        $storageBackend = t3lib_div::makeInstance('Tx_Extbase_Persistence_Storage_Typo3DbBackend', $GLOBALS['TYPO3_DB']); // singleton
                        $storageBackend->injectDataMapper($dataMapper);
 
@@ -250,7 +261,8 @@ class Tx_Extbase_Dispatcher {
 
                        $persistenceBackend = t3lib_div::makeInstance('Tx_Extbase_Persistence_Backend', $persistenceSession, $storageBackend); // singleton
                        $persistenceBackend->injectDataMapper($dataMapper);
-                       $persistenceBackend->injectIdentityMap(t3lib_div::makeInstance('Tx_Extbase_Persistence_IdentityMap'));
+                       $persistenceBackend->injectIdentityMap($identityMap);
+                       $persistenceBackend->injectReflectionService(self::$reflectionService);
                        $persistenceBackend->injectQueryFactory(t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory'));
                        $persistenceBackend->injectQOMFactory($qomFactory);
                        $persistenceBackend->injectValueFactory(t3lib_div::makeInstance('Tx_Extbase_Persistence_ValueFactory'));
index 5ec9938..b2c4a32 100644 (file)
@@ -502,7 +502,7 @@ class Tx_Extbase_Domain_Model_FrontendUser extends Tx_Extbase_DomainObject_Abstr
         * @return void
         * @api
         */
-       public function setLastLogin($lastLogin) {
+       public function setLastLogin(DateTime $lastLogin) {
                $this->lastLogin = $lastLogin;
        }
 
index ab9bdf6..3f04032 100644 (file)
@@ -34,7 +34,7 @@
 abstract class Tx_Extbase_DomainObject_AbstractDomainObject implements Tx_Extbase_DomainObject_DomainObjectInterface {
 
        /**
-        * @var string The uid
+        * @var int The uid
         */
        protected $uid;
 
@@ -145,16 +145,7 @@ abstract class Tx_Extbase_DomainObject_AbstractDomainObject implements Tx_Extbas
         */
        public function _memorizeCleanState() {
        }
-
-       /**
-        * Returns a hash map of dirty properties and $values. This is always the empty array for ValueObjects, because ValueObjects never change.
-        *
-        * @return array
-        */
-       public function _getDirtyProperties() {
-               return array();
-       }
-
+       
        /**
         * Returns TRUE if the properties were modified after reconstitution. However, value objects can be never updated.
         *
index 09e3c43..66748b9 100644 (file)
@@ -35,7 +35,7 @@ abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainO
        /**
         * @var An array holding the clean property values. Set right after reconstitution of the object
         */
-       private $_cleanProperties = NULL;
+       private $_cleanProperties = array();
 
        /**
         * Register an object's clean state, e.g. after it has been reconstituted
@@ -68,9 +68,6 @@ abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainO
         */
        public function _memorizePropertyCleanState($propertyName) {
                $propertyValue = $this->$propertyName;
-               if (!is_array($this->_cleanProperties)) {
-                       $this->_cleanProperties = array();
-               }
                if (is_object($propertyValue)) {
                        $this->_cleanProperties[$propertyName] = clone($propertyValue);
 
@@ -94,41 +91,32 @@ abstract class Tx_Extbase_DomainObject_AbstractEntity extends Tx_Extbase_DomainO
         * @return array
         */
        public function _getCleanProperties() {
-               if (!is_array($this->_cleanProperties)) throw new Tx_Extbase_Persistence_Exception_CleanStateNotMemorized('The clean state of the object "' . get_class($this) . '" has not been memorized before calling _isDirty().', 1233309106);
                return $this->_cleanProperties;
        }
 
        /**
-        * Returns a hash map of dirty properties and $values
+        * 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.
         *
-        * @return array
+        * @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 _getDirtyProperties() {
-               if (!is_array($this->_cleanProperties)) throw new Tx_Extbase_Persistence_Exception_CleanStateNotMemorized('The clean state of the object "' . get_class($this) . '" has not been memorized before asking _isDirty().', 1233309106);
-               if ($this->uid !== NULL && $this->uid != $this->_cleanProperties['uid']) throw new Tx_Extbase_Persistence_Exception_TooDirty('The uid "' . $this->uid . '" has been modified, that is simply too much.', 1222871239);
-               $dirtyProperties = array();
-               foreach ($this->_cleanProperties as $propertyName => $propertyValue) {
-                       if (is_object($this->$propertyName)) {
-                               // In case it is an object, we do a simple comparison (!=) as we want cloned objects to return the same values.
-                               if ($this->$propertyName != $propertyValue) {
-                                       $dirtyProperties[$propertyName] = $this->$propertyName;
-                               }
-                       } else {
-                               if ($this->$propertyName !== $propertyValue) {
-                                       $dirtyProperties[$propertyName] = $this->$propertyName;
-                               }
-                       }
+       public function _getCleanProperty($propertyName) {
+               if (is_array($this->_cleanProperties)) {
+                       return isset($this->_cleanProperties[$propertyName]) ? $this->_cleanProperties[$propertyName] : NULL;
+               } else {
+                       return NULL;
                }
-               return $dirtyProperties;
        }
-
+       
        /**
         * Returns TRUE if the properties were modified after reconstitution
         *
         * @return boolean
         */
        public function _isDirty($propertyName = NULL) {
-               if (!is_array($this->_cleanProperties)) throw new Tx_Extbase_Persistence_Exception_CleanStateNotMemorized('The clean state of the object "' . get_class($this) . '" has not been memorized before asking _isDirty().', 1233309106);
+               if (empty($this->_cleanProperties)) return TRUE;
+               // if (!is_array($this->_cleanProperties)) throw new Tx_Extbase_Persistence_Exception_CleanStateNotMemorized('The clean state of the object "' . get_class($this) . '" has not been memorized before asking _isDirty().', 1233309106);
                if ($this->uid !== NULL && $this->uid != $this->_cleanProperties['uid']) throw new Tx_Extbase_Persistence_Exception_TooDirty('The uid "' . $this->uid . '" has been modified, that is simply too much.', 1222871239);
                $result = FALSE;
                if ($propertyName !== NULL) {
index c3d0b85..13d468c 100644 (file)
@@ -81,12 +81,5 @@ interface Tx_Extbase_DomainObject_DomainObjectInterface {
         */
        public function _getProperties();
 
-       /**
-        * Returns a hash map of dirty properties and $values
-        *
-        * @return boolean
-        */
-       public function _getDirtyProperties();
-
 }
 ?>
\ No newline at end of file
index 3e4e4ab..3bdacc8 100644 (file)
@@ -51,6 +51,11 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        protected $identityMap;
 
        /**
+        * @var Tx_Extbase_Reflection_Service
+        */
+       protected $reflectionService;
+
+       /**
         * @var Tx_Extbase_Persistence_QueryFactoryInterface
         */
        protected $queryFactory;
@@ -83,6 +88,11 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        protected $referenceIndex;
 
        /**
+        * @var array
+        **/
+       protected $extbaseSettings;
+
+       /**
         * Constructs the backend
         *
         * @param Tx_Extbase_Persistence_Session $session The persistence session used to persist data
@@ -90,7 +100,10 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        public function __construct(Tx_Extbase_Persistence_Session $session, Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend) {
                $this->session = $session;
                $this->storageBackend = $storageBackend;
-               $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
+               $this->extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
+               }
                $this->aggregateRootObjects = new Tx_Extbase_Persistence_ObjectStorage();
        }
 
@@ -115,6 +128,16 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        }
 
        /**
+        * Injects the Reflection Service
+        *
+        * @param Tx_Extbase_Reflection_Service
+        * @return void
+        */
+       public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
+               $this->reflectionService = $reflectionService;
+       }
+
+       /**
         * Injects the QueryFactory
         *
         * @param Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory
@@ -296,6 +319,13 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         */
        protected function persistObjects() {
                foreach ($this->aggregateRootObjects as $object) {
+                       // if (!$this->identityMap->hasObject($object)) { // TODO Must be enabled to allow other identity properties than $uid
+                       if ($object->_isNew()) {
+                               $this->insertObject($object);
+                       }
+               }
+               
+               foreach ($this->aggregateRootObjects as $object) {
                        $this->persistObject($object);
                }
        }
@@ -308,98 +338,180 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param string $parentPropertyName The name of the property the object is stored in
         * @return void
         */
-       protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL) {
+       protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
                $row = array();
-               $queuedObjects = array();
+               $queue = array();
                $className = get_class($object);
                $dataMap = $this->dataMapper->getDataMap($className);
-
-               if ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
-                       $this->mapAlreadyPersistedValueObject($object);
-               }
-
+               $classSchema = $this->reflectionService->getClassSchema($className);
+               
                $properties = $object->_getProperties();
-               // Fill up $row[$columnName] array with changed values which need to be stored
                foreach ($properties as $propertyName => $propertyValue) {
-                       if (!$dataMap->isPersistableProperty($propertyName) || ($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy)) {
+                       if (!$dataMap->isPersistableProperty($propertyName)) continue;
+                       if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
                                continue;
                        }
 
                        $columnMap = $dataMap->getColumnMap($propertyName);
-                       if ($object->_isNew() || $object->_isDirty($propertyName)) {
-                               if ($columnMap->isRelation()) {
-                                       $this->persistRelations($object, $propertyName, $propertyValue, $columnMap, $queuedObjects, $row);
+                       $propertyMetaData = $classSchema->getProperty($propertyName);
+                       $propertyType = $propertyMetaData['type'];
+                       // FIXME enable property-type check
+                       // $this->checkPropertyType($propertyType, $propertyValue);
+                       if ($propertyType === 'Tx_Extbase_Persistence_ObjectStorage') { 
+                               if ($object->_isDirty($propertyName)) {
+                                       $row[$columnMap->getColumnName()] = $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue);
                                } else {
-                                       // We have not a relation, this means it is a scalar (like a string or interger value) or an object
+                                       foreach ($propertyValue as $containedObject) {
+                                               $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, $object, $propertyName);
+                                                       $queue[] = $propertyValue;
+                                               } else {
+                                                       $this->persistValueObject($propertyValue, $object, $propertyName);
+                                               }
+                                       }
                                        $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
                                }
+                       } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || $object->_isNew() || $object->_isDirty($propertyName)) {
+                               $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
                        }
-               } // end property iteration for loop
-
-               // The state of the Object has to be stored in a local variable because $object->_isNew() will return FALSE after
-               // the object was inserted. We need the initial state here.
-               $objectIsNew = $object->_isNew();
-               if ($objectIsNew === TRUE) {
-                       $this->insertObject($object, $parentObject, $parentPropertyName, $row);
-               } elseif ($object->_isDirty()) {
-                       $this->updateObject($object, $parentObject, $parentPropertyName, $row);
+               }
+               
+               if (count($row) > 0) {
+                       $this->updateObject($object, $row);
+               }
+               
+               if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
+                       $object->_memorizeCleanState();
                }
 
-               $objectHasToBeUpdated = $this->processQueuedChildObjects($object, $queuedObjects, $row);
-               if ($objectHasToBeUpdated === TRUE) {
-                       // TODO Check if this can be merged with the first update
-                       $this->updateObject($object, $parentObject, $parentPropertyName, $row);
+               foreach ($queue as $object) {
+                       $this->persistObject($object);
                }
+               
+       }
 
-               // SK: I need to check the code below more thoroughly
-               if ($parentObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface && !empty($parentPropertyName)) {
-                       $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
-                       $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
-                       if (($parentColumnMap->getTypeOfRelation()  === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY)) {
-                               $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName);
-                       }
+       /**
+        * 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;
                }
 
-               // TODO: We should only register the object if it was really updated.
-               $this->identityMap->registerObject($object, $object->getUid());
-               $object->_memorizeCleanState();
+               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);
+               }
        }
+       
+       /**
+        * Persists the given value object.
+        *
+        * @return void
+        */
+       protected function persistValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 1) {
+               $result = $this->getUidOfAlreadyPersistedValueObject($object);
+               if ($result !== FALSE) {
+                       $object->_setProperty('uid', (int)$result);
+                       if($this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName)->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
+                               $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
+                       }
+               } else {
+                       $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->isLazyValue($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()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
+                       }
+                       $this->insertObject($object, $parentObject, $parentPropertyName, $sortingPosition, $row);
+               }
+       }
+       
+       /**
+        * Tests, if the given Value Object already exists in the storage backend. If so, it maps the uid
+        * to the given object.
+        *
+        * @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 relation. 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 deleted immediately, too.
+        * 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 (an array of Domain Objects, ObjectStorage holding Domain Objects or a Domain Object itself)
+        * @param mixed $propertyValue The property value 
         * @return void
         */
-       protected function persistRelations(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName, $propertyValue, Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, &$queuedObjects, &$row) {
-                       $columnName = $columnMap->getColumnName();
-                       if (($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) || ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY)) {
-                               if (is_array($propertyValue) || $propertyValue instanceof ArrayAccess) {
-                                       foreach ($propertyValue as $relatedObject) {
-                                               $queuedObjects[$propertyName][] = $relatedObject;
-                                       }
-                                       foreach ($this->getRemovedChildObjects($object, $propertyName) as $removedObject) {
-                                               // TODO The removed object should only be deleted automatically if it is not managed by a Repository
-                                               if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
-                                                       $this->deleteObject($removedObject, $object, $propertyName);
-                                               } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
-                                                       $this->deleteRelationFromRelationtable($removedObject, $object, $propertyName);
-                                               }
-                                       }
-                                       $row[$columnName] = count($propertyValue); // Will be overwritten if the related objects are referenced by a comma separated list
-                               }
-                       } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
-                               // TODO Handle Value Objects different
-                               if ($propertyValue->_isNew() || $propertyValue->_isDirty()) {
-                                       $this->persistObject($propertyValue);
+       protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, &$queue) {
+               $className = get_class($parentObject);
+               $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
+               $columnName = $columnMap->getColumnName();              
+               $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
+               
+               foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
+                       if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
+                               $this->removeObject($removedObject);
+                       } else {
+                               $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
+                       }
+               }
+
+               $childPidArray = array();
+               $sortingPosition = 1;
+               foreach ($objectStorage as $object) {
+                       if ($object->_isNew()) {
+                               if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
+                                       $this->insertObject($object, $parentObject, $propertyName, $sortingPosition);
+                                       $queue[] = $object;
+                               } else {
+                                       $this->persistValueObject($object, $parentObject, $propertyName, $sortingPosition);
                                }
-                               $row[$columnName] = $propertyValue->getUid();
                        }
+                       $childPidArray[] = $object->getUid(); // FIXME This won't work for partly loaded storages
+                       $sortingPosition++;
+               }
+               
+               if ($columnMap->getParentKeyFieldName() === NULL) {
+                       $newParentPropertyValue = implode(',', $childPidArray);
+               } else {
+                       $newParentPropertyValue = count($objectStorage); // TODO check for limited queries
+               }
+               
+               return $newParentPropertyValue;
        }
-
+       
        /**
         * Returns the removed objects determined by a comparison of the clean property value
         * with the actual property value.
@@ -410,110 +522,101 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         */
        protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
                $removedObjects = array();
-               if (!$object->_isNew()) {
-                       $cleanProperties = $object->_getCleanProperties();
-                       $cleanPropertyValue = $cleanProperties[$propertyName];
-                       $propertyValue = $object->_getProperty($propertyName);
-                       if ($cleanPropertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
-                               $cleanPropertyValue = $cleanPropertyValue->toArray();
-                       }
-                       if ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
-                               $propertyValue = $propertyValue->toArray();
-                       }
-                       foreach ($cleanPropertyValue as $item) {
-                               if (!in_array($item, $propertyValue)) {
-                                       $removedObjects[] = $item;
-                               }
+               $cleanPropertyValue = $object->_getCleanProperty($propertyName);
+               $propertyValue = $object->_getProperty($propertyName);
+               if ($cleanPropertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
+                       $cleanPropertyValue = $cleanPropertyValue->toArray();
+               }
+               if ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
+                       $propertyValue = $propertyValue->toArray();
+               }
+               foreach ($cleanPropertyValue as $hash => $item) {
+                       if (!array_key_exists($hash, $propertyValue)) {
+                               $removedObjects[] = $item;
                        }
                }
                return $removedObjects;
        }
-
+       
        /**
-        * This function processes the queued child objects to be persisted. The queue is build while looping over the
-        * collection of Domain Objects stored in a object property.
+        * Updates the fields defining the relation between the object and the parent object.
         *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object holding the collection
-        * @param array $queuedObjects The queued child objects
-        * @param array $row The row to be inseted or updated in the database. Passed as reference.
-        * @return boolean TRUE if the object holding the collection has to be updated; otherwise FALSE
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object 
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject 
+        * @param string $parentPropertyName 
+        * @return void
         */
-       protected function processQueuedChildObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $queuedChildObjects, array &$row) {
-               $objectHasToBeUpdated = FALSE;
-               $className = get_class($object);
-               $dataMap = $this->dataMapper->getDataMap($className);
-               foreach ($queuedChildObjects as $propertyName => $childObjects) {
-                       $columnMap = $dataMap->getColumnMap($propertyName);
-                       $childPidArray = array();
-                       foreach($childObjects as $childObject) {
-                               $this->persistObject($childObject, $object, $propertyName);
-                               if ($childObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
-                                       $childPidArray[] = (int)$childObject->getUid();
+       protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
+               $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
+               $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
+               if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
+                       $row = array();
+                       $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
+                       if ($parentKeyFieldName !== NULL) {
+                               $row[$parentKeyFieldName] = '';
+                               $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
+                               if ($parentTableFieldName !== NULL) {
+                                       $row[$parentTableFieldName] = '';
                                }
                        }
-                       if ($columnMap->getParentKeyFieldName() === NULL) { // TRUE: We have to generate a comma separated list stored in the field
-                               if (count($childPidArray) > 0) {
-                                       $row[$propertyName] = implode(',', $childPidArray);
-                               } else {
-                                       $row[$propertyName] = NULL;
-                               }
-                               $objectHasToBeUpdated = TRUE;
+                       if (count($row) > 0) {
+                               $this->updateObject($object, $row);
                        }
-               }
-               return $objectHasToBeUpdated;
-       }
-
-       /**
-        * Tests, if the given Value Object already exists in the storage backend. If so, it maps the uid
-        * to the given object.
-        *
-        * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
-        */
-       protected function mapAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
-               $dataMap = $this->dataMapper->getDataMap(get_class($object));
-               $properties = $object->_getProperties();
-               $result = $this->storageBackend->hasValueObject($properties, $dataMap);
-               if ($result !== FALSE) {
-                       $object->_setProperty('uid', $result);
-               }
+               } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
+                       $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
+               }               
        }
-
+       
        /**
         * Inserts an object in the storage
         *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
-        * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
-        * @param string|NULL $parentPropertyName The name of the property
-        * @param array $row The $row
+        * @param array $row The tuple to be inserted
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The parent object (if any)
+        * @param string $parentPropertyName The name of the property
         */
-       protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
-               $className = get_class($object);
-               $dataMap = $this->dataMapper->getDataMap($className);
-               $tableName = $dataMap->getTableName();
-               $this->addCommonFieldsToRow($object, $parentObject, $parentPropertyName, $row);
-               $uid = $this->storageBackend->addRow(
-                       $tableName,
-                       $row
-                       );
-               $object->_setProperty('uid', $uid);
-               $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+       protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject = NULL, $propertyName = NULL, $sortingPosition = NULL, array $row = array()) {
+               $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
+               $this->addCommonFieldsToRow($object, $row);
+               if ($parentObject !== NULL) {
+                       $parentColumnMap = $this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
+                       if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $parentColumnMap->getParentKeyFieldName() !== NULL) {
+                               $row[$parentColumnMap->getParentKeyFieldName()] = $parentObject->getUid();
+                       }
+               }
+               if ($object->_isNew()) {
+                       $uid = $this->storageBackend->addRow(
+                               $tableName,
+                               $row
+                               );
+                       $object->_setProperty('uid', (int)$uid);
+               }
+               if ($parentObject !== NULL) {
+                       if($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
+                               $this->insertRelationInRelationtable($object, $parentObject, $propertyName, $sortingPosition);
+                       }
+               }
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               }
+               $this->identityMap->registerObject($object, $uid);
        }
-
+       
        /**
         * Inserts mm-relation into a relation table
         *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
-        * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
+        * @param string $propertyName The name of the parent object's property where the related objects are stored in
         * @return void
         */
-       protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
+       protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
                $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
-               $columnMap = $dataMap->getColumnMap($parentPropertyName);
+               $columnMap = $dataMap->getColumnMap($propertyName);
                $row = array(
                        $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
-                       $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
-                       'sorting' => 9999 // TODO sorting of mm table items
+                       $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
+                       $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
                        );
                $relationTableName = $columnMap->getRelationTableName();
                // FIXME Reenable support for tablenames
@@ -558,18 +661,21 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param string|NULL $parentPropertyName The name of the property
         * @param array $row The $row
         */
-       protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
-               $className = get_class($object);
-               $dataMap = $this->dataMapper->getDataMap($className);
-               $tableName = $dataMap->getTableName();
-               $this->addCommonFieldsToRow($object, $parentObject, $parentPropertyName, $row);
+       protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
+               $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
+               $this->addCommonFieldsToRow($object, $row);
                $uid = $object->getUid();
                $row['uid'] = $uid;
                $res = $this->storageBackend->updateRow(
                        $tableName,
                        $row
                        );
-               $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               }
+               if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
+                       $object->_memorizeCleanState();
+               }       
                return $res;
        }
 
@@ -580,7 +686,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param array $properties The properties of the object
         * @return array A single row to be inserted in the database
         */
-       protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
+       protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
                $className = get_class($object);
                $dataMap = $this->dataMapper->getDataMap($className);
                if ($dataMap->hasCreationDateColumn() && $object->_isNew()) {
@@ -589,48 +695,9 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                if ($dataMap->hasTimestampColumn()) {
                        $row[$dataMap->getTimestampColumnName()] = $GLOBALS['EXEC_TIME'];
                }
-
                if ($object->_isNew() && $dataMap->hasPidColumn() && !isset($row['pid'])) {
                        $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
                }
-
-               if ($parentObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface && !empty($parentPropertyName)) {
-                       $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
-                       $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
-                       // FIXME This is a hacky solution
-                       if ($parentColumnMap->getTypeOfRelation() !== Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
-                               $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
-                               if ($parentKeyFieldName !== NULL) {
-                                       $row[$parentKeyFieldName] = $parentObject->getUid();
-                               }
-                               $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
-                               if ($parentTableFieldName !== NULL) {
-                                       $row[$parentTableFieldName] = $parentDataMap->getTableName();
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Determine the storage page ID for a given NEW record
-        *
-        * This does the following:
-        * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
-        * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
-        *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
-        * @return int the storage Page ID where the object should be stored
-        */
-       protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
-               $className = get_class($object);
-               $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
-
-               if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
-                       return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
-               } else {
-                       $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
-                       return (int) $storagePidList[0];
-               }
        }
 
        /**
@@ -640,14 +707,14 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         */
        protected function processDeletedObjects() {
                foreach ($this->deletedObjects as $object) {
-                       $this->deleteObject($object);
+                       $this->removeObject($object);
                        $this->identityMap->unregisterObject($object);
                }
                $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
        }
 
        /**
-        * Deletes an object, it's 1:n related objects, and the m:n relations in relation tables (but not the m:n related objects!)
+        * Deletes an object
         *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
         * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
@@ -655,8 +722,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
         * @return void
         */
-       protected function deleteObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, $markAsDeleted = TRUE) {
-               // TODO Implement recursive deletions
+       protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
                $dataMap = $this->dataMapper->getDataMap(get_class($object));
                $tableName = $dataMap->getTableName();
                if (($markAsDeleted === TRUE) && $dataMap->hasDeletedColumn()) {
@@ -674,7 +740,37 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                                array('uid' => $object->getUid())
                                );
                }
-               $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               $this->removeRelatedObjects($object);
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
+               }               
+       }
+       
+       /**
+        * Remove related objects
+        *
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
+        * @return void
+        */
+       protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
+               $className = get_class($object);
+               $dataMap = $this->dataMapper->getDataMap($className);
+               $classSchema = $this->reflectionService->getClassSchema($className);
+                               
+               $properties = $object->_getProperties();
+               foreach ($properties as $propertyName => $propertyValue) {
+                       $columnMap = $dataMap->getColumnMap($propertyName);
+                       $propertyMetaData = $classSchema->getProperty($propertyName);
+                       if ($propertyMetaData['cascade'] === 'remove') {
+                               if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
+                                       foreach ($propertyValue as $containedObject) {
+                                               $this->removeObject($containedObject);
+                                       }
+                               } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {                            
+                                       $this->removeObject($propertyValue);
+                               }
+                       }
+               }
        }
 
        /**
@@ -689,6 +785,28 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $dataMap = $this->dataMapper->getDataMap($className);
                return $dataMap->isPersistableProperty($propertyName);
        }
+       
+       /**
+        * Determine the storage page ID for a given NEW record
+        *
+        * This does the following:
+        * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
+        * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
+        *
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
+        * @return int the storage Page ID where the object should be stored
+        */
+       protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
+               $className = get_class($object);
+               $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+
+               if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
+                       return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
+               } else {
+                       $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
+                       return (int) $storagePidList[0];
+               }
+       }
 
 }
 
diff --git a/typo3/sysext/extbase/Classes/Persistence/Exception/UnexpectedTypeException.php b/typo3/sysext/extbase/Classes/Persistence/Exception/UnexpectedTypeException.php
new file mode 100644 (file)
index 0000000..4e591e6
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
+*  All rights reserved
+*
+*  This class is a backport of the corresponding class of FLOW3. 
+*  All credits go to the v5 team.
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+/**
+ * An "Unexpected Type" exception.
+ *
+ * @package Extbase
+ * @subpackage Persistence\Exception
+ * @version $ID:$
+ */
+class Tx_Extbase_Persistence_Exception_UnexpectedTypeException extends Tx_Extbase_Persistence_Exception {
+}
+
+?>
\ No newline at end of file
index 86bb42d..b36dac7 100644 (file)
 class Tx_Extbase_Persistence_LazyLoadingProxy implements Iterator, Tx_Extbase_Persistence_LoadingStrategyInterface {
 
        /**
-        * @var Tx_Extbase_Persistence_QueryFactoryInterface
-        */
-       protected $queryFactory;
-
-       /**
         * The object this property is contained in.
         *
         * @var object
@@ -62,25 +57,16 @@ class Tx_Extbase_Persistence_LazyLoadingProxy implements Iterator, Tx_Extbase_Pe
        private $fieldValue;
 
        /**
-        *
-        * @var Tx_Extbase_Persistence_Mapper_ColumnMap
-        */
-       private $columnMap;
-
-       /**
         * Constructs this proxy instance.
         *
         * @param object $parentObject The object instance this proxy is part of
         * @param string $propertyName The name of the proxied property in it's parent
         * @param mixed $fieldValue The raw field value.
-        * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
         */
-       public function __construct($parentObject, $propertyName, $fieldValue, Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
-               $this->queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
+       public function __construct($parentObject, $propertyName, $fieldValue) {
                $this->parentObject = $parentObject;
                $this->propertyName = $propertyName;
                $this->fieldValue = $fieldValue;
-               $this->columnMap = $columnMap;
        }
 
        /**
@@ -94,14 +80,11 @@ class Tx_Extbase_Persistence_LazyLoadingProxy implements Iterator, Tx_Extbase_Pe
                // it's parent... the result would be weird.
                if ($this->parentObject->_getProperty($this->propertyName) instanceof Tx_Extbase_Persistence_LazyLoadingProxy) {
                        $dataMapper = Tx_Extbase_Dispatcher::getPersistenceManager()->getBackend()->getDataMapper();
-                       $objects = $dataMapper->fetchRelatedObjects($this->parentObject, $this->propertyName, $this->fieldValue, $this->columnMap);
-                       $realInstance = new Tx_Extbase_Persistence_ObjectStorage();
-                       foreach ($objects as $object) {
-                               $realInstance->attach($object);
-                       }
-                       $this->parentObject->_setProperty($this->propertyName, $realInstance);
+                       $objects = $dataMapper->fetchRelated($this->parentObject, $this->propertyName, $this->fieldValue, FALSE);
+                       $propertyValue = $dataMapper->mapResultToPropertyValue($this->parentObject, $this->propertyName, $objects);
+                       $this->parentObject->_setProperty($this->propertyName, $propertyValue);
                        $this->parentObject->_memorizeCleanState($this->propertyName);
-                       return $realInstance;
+                       return $propertyValue;
                } else {
                        return $this->parentObject->_getProperty($this->propertyName);
                }
index 5ec0bbc..3164ec1 100644 (file)
 class Tx_Extbase_Persistence_LazyObjectStorage extends Tx_Extbase_Persistence_ObjectStorage implements Tx_Extbase_Persistence_LoadingStrategyInterface {
 
        /**
-        * @var Tx_Extbase_Persistence_QueryFactoryInterface
-        */
-       protected $queryFactory;
-
-       /**
         * The object this property is contained in.
         *
         * @var object
         */
-       private $parentObject;
+       protected $parentObject;
 
        /**
         * The name of the property represented by this proxy.
         *
         * @var string
         */
-       private $propertyName;
+       protected $propertyName;
 
        /**
         * The raw field value.
         *
         * @var mixed
         */
-       private $fieldValue;
+       protected $fieldValue;
 
        /**
         *
-        * @var Tx_Extbase_Persistence_Mapper_ColumnMap
+        * @var bool
         */
-       private $columnMap;
+       protected $isInitialized = FALSE;
 
        /**
         * Constructs this proxy instance.
@@ -73,15 +68,11 @@ class Tx_Extbase_Persistence_LazyObjectStorage extends Tx_Extbase_Persistence_Ob
         * @param object $parentObject The object instance this proxy is part of
         * @param string $propertyName The name of the proxied property in it's parent
         * @param mixed $fieldValue The raw field value.
-        * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
         */
-       public function __construct($parentObject, $propertyName, $fieldValue, Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
-               $this->queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
+       public function __construct($parentObject, $propertyName, $fieldValue) {
                $this->parentObject = $parentObject;
                $this->propertyName = $propertyName;
                $this->fieldValue = $fieldValue;
-               $this->columnMap = $columnMap;
-               $this->storage = NULL; // TODO
        }
        
        /**
@@ -90,18 +81,16 @@ class Tx_Extbase_Persistence_LazyObjectStorage extends Tx_Extbase_Persistence_Ob
         * @return void
         */
        protected function initializeStorage() {
-               if (is_null($this->storage)) {
+               if (!$this->isInitialized) {
                        $dataMapper = Tx_Extbase_Dispatcher::getPersistenceManager()->getBackend()->getDataMapper();
-                       $objects = $dataMapper->fetchRelatedObjects($this->parentObject, $this->propertyName, $this->fieldValue, $this->columnMap);
+                       $objects = $dataMapper->fetchRelated($this->parentObject, $this->propertyName, $this->fieldValue, FALSE);
                        $storage = array();
                        foreach ($objects as $object) {
                                $storage[spl_object_hash($object)] = $object;
                        }
                        $this->storage = $storage;
                        $this->parentObject->_memorizeCleanState($this->propertyName);
-               }
-               if (!is_array($this->storage)) {
-                       throw new Tx_Extbase_Persistence_Exception('The storage could not be initialized.', 1252393014); // TODO
+                       $this->isInitialized = TRUE;
                }
        }
        
@@ -111,22 +100,13 @@ class Tx_Extbase_Persistence_LazyObjectStorage extends Tx_Extbase_Persistence_Ob
         * @return void
         */
        public function count() {
+               $dataMapper = Tx_Extbase_Dispatcher::getPersistenceManager()->getBackend()->getDataMapper();
+               $columnMap = $dataMapper->getDataMap(get_class($this->parentObject))->getColumnMap($this->propertyName);
                $numberOfElements = NULL;
-               if ($this->columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
-                       $parentKeyFieldName = $this->columnMap->getParentKeyFieldName();
-                       if (!empty($parentKeyFieldName)) {
-                               $numberOfElements = $this->fieldValue; // The field value in TYPO3 normally contains the number of related elements
-                       } else {
-                               $this->initializeStorage();
-                               $numberOfElements = count($this->storage);
-                               // FIXME Count on comma separated lists does not respect hidden objects
-                               // if (empty($this->fieldValue)) {
-                               //      $numberOfElements = 0;
-                               // }
-                               // $numberOfElements = count(explode(',', $this->fieldValue));
-                       }
-               } elseif ($this->columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
-                       $numberOfElements = $this->fieldValue;                  
+               if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
+                       $parentKeyFieldName = $columnMap->getParentKeyFieldName();
+                       $dataMapper = Tx_Extbase_Dispatcher::getPersistenceManager()->getBackend()->getDataMapper();
+                       $numberOfElements = $dataMapper->countRelated($this->parentObject, $this->propertyName, $this->fieldValue);
                } else {
                        $this->initializeStorage();
                        $numberOfElements = count($this->storage);                      
index 100a098..1c8ec3d 100644 (file)
@@ -212,11 +212,9 @@ class Tx_Extbase_Persistence_Mapper_ColumnMap {
                switch ($loadingStrategy) {
                        case self::STRATEGY_LAZY_PROXY;
                        case self::STRATEGY_LAZY_STORAGE;
+                       case self::STRATEGY_EAGER;
                                $this->loadingStrategy = $loadingStrategy;
                                break;
-                       default:
-                               $this->loadingStrategy = self::STRATEGY_EAGER;
-                               break;
                }
        }
 
index b886656..88b0687 100644 (file)
@@ -210,7 +210,7 @@ class Tx_Extbase_Persistence_Mapper_DataMap {
         * This method tries to determine the type of type of relation to other tables and sets it based on
         * the $TCA column configuration
         *
-        * @param string $columnMap The column map
+        * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
         * @param string $columnConfiguration The column configuration from $TCA
         * @return void
         */
index 9af7559..e1e2d6d 100644 (file)
@@ -194,103 +194,86 @@ class Tx_Extbase_Persistence_Mapper_DataMapper implements t3lib_Singleton {
                                }
                                break;
                                case (Tx_Extbase_Persistence_PropertyType::REFERENCE):
-                                       if (!is_null($row->getValue($columnName))) {
-                                               $propertyValue = $this->mapRelatedObjects($object, $propertyName, $row, $columnMap);
-                                       } else {
+                                       if (is_null($row->getValue($columnName))) {
                                                $propertyValue = NULL;
+                                       } else {
+                                               $fieldValue = $row->getValue($columnMap->getColumnName());
+                                               $result = $this->fetchRelated($object, $propertyName, $fieldValue);
+                                               $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $result);
                                        }
                                        break;
-                                       // FIXME we have an object to handle... -> exception
                                default:
-                                       // SK: We should throw an exception as this point as there was an undefined propertyType we can not handle.
-                                       if ($row->hasValue($columnName)) {
-                                               $property = $row->getValue($columnName);
-                                               if (is_object($property)) {
-                                                       $propertyValue = $this->mapObject($property);
-                                                       // SK: THIS case can not happen I think. At least $this->mapObject() is not available.
-                                               } else {
-                                                       // SK: This case does not make sense either. $this-mapSingleRow has a different signature
-                                                       $propertyValue = $this->mapSingleRow($className, $property);
-                                               }
-                                       }
+                                       // FIXME throw exception
                                        break;
                        }
-
                        $object->_setProperty($propertyName, $propertyValue);
                }
        }
 
        /**
-        * Maps related objects to an ObjectStorage
+        * Fetches a collection of objects related to a property of a parent object
         *
-        * @param object $parentObject The parent object for the mapping result
-        * @param string $propertyName The target property name for the mapping result
-        * @param Tx_Extbase_Persistence_RowInterface $row The actual database row
-        * @param int $loadingStrategy The loading strategy; one of Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_*
-        * @return array|Tx_Extbase_Persistence_ObjectStorage|Tx_Extbase_Persistence_LazyLoadingProxy|another implementation of a loading strategy
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
+        * @param string $propertyName The name of the proxied property in it's parent
+        * @param mixed $fieldValue The raw field value.
+        * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
+        * @return mixed The result
         */
-       protected function mapRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, Tx_Extbase_Persistence_RowInterface $row, Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
-               $dataMap = $this->getDataMap(get_class($parentObject));
-               $columnMap = $dataMap->getColumnMap($propertyName);
-               $targetClassSchema = $this->reflectionService->getClassSchema(get_class($parentObject));
-               $propertyMetaData = $targetClassSchema->getProperty($propertyName);
-               $fieldValue = $row->getValue($columnMap->getColumnName());
-               if ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_LAZY_PROXY) {
-                       $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue, $columnMap);
-               } else {
-                       $queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
-                       if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
-                               $query = $queryFactory->create($columnMap->getChildClassName());
-                               // TODO: This is an ugly hack, just ignoring the storage page state from here. Actually, the query settings would have to be passed into the DataMapper, so we can respect
-                               // enableFields and storage page settings.
-                               $query->getQuerySettings()->setRespectStoragePage(FALSE);
-                               $result = current($query->matching($query->withUid((int)$fieldValue))->execute());
-                       } elseif (($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) || ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY)) {
-                               if ($propertyMetaData['lazy'] === TRUE || $columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_LAZY_STORAGE) {
-                                       $result = new Tx_Extbase_Persistence_LazyObjectStorage($parentObject, $propertyName, $fieldValue, $columnMap);
-                               } else {
-                                       $objects = $this->fetchRelatedObjects($parentObject, $propertyName, $fieldValue, $columnMap);
-                                       if ($propertyMetaData['type'] === 'ArrayObject') {
-                                               $result = new ArrayObject($objects);
-                                       } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage' || $propertyMetaData['type'] === 'Tx_Extbase_Persistence_LazyObjectStorage') {
-                                               $result = new Tx_Extbase_Persistence_ObjectStorage();
-                                               foreach ($objects as $object) {
-                                                       $result->attach($object);
-                                               }
-                                       } else {
-                                               $result = $objects;
-                                       }
-                               }
+       public function fetchRelated(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue, $enableLazyLoading = TRUE) {
+               $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
+               $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
+               if ($enableLazyLoading === TRUE && ($propertyMetaData['lazy'] || ($columnMap->getLoadingStrategy() !== Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_EAGER))) {
+                       if (($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') || ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_LAZY_STORAGE)) {
+                               $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyObjectStorage', $parentObject, $propertyName, $fieldValue);                               
+                       } else {
+                               $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue);
                        }
+               } else {
+                       $result = $this->fetchRelatedEager($parentObject, $propertyName, $fieldValue);
                }
                return $result;
        }
-
+       
        /**
-        * Fetches a collection of objects related to a property of a parent object
+        * Fetches the related objects from the storage backend.
         *
         * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
         * @param string $propertyName The name of the proxied property in it's parent
         * @param mixed $fieldValue The raw field value.
-        * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
-        * @return Tx_Extbase_Persistence_ObjectStorage An Object Storage containing the related objects
+        * @return void
         */
-       public function fetchRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue, Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
+       protected function fetchRelatedEager(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue) {
+               $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
+               return $query->execute();
+       }
+       
+       /**
+        * Builds and returns the prepared query, ready to be executed.
+        *
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject 
+        * @param string $propertyName 
+        * @param string $fieldValue 
+        * @return void
+        */
+       protected function getPreparedQuery(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue) {
+               $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
                $queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
-               $objects = NULL;
-               $childSortByFieldName = $columnMap->getChildSortByFieldName();
+               $query = $queryFactory->create($columnMap->getChildClassName());
+               // TODO: This is an ugly hack, just ignoring the storage page state from here. Actually, the query settings would have to be passed into the DataMapper, so we can respect
+               // enableFields and storage page settings.
+               $query->getQuerySettings()->setRespectStoragePage(FALSE);
                $parentKeyFieldName = $columnMap->getParentKeyFieldName();
-               if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
-                       $query = $queryFactory->create($columnMap->getChildClassName());
+               $childSortByFieldName = $columnMap->getChildSortByFieldName();
+               if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
+                       $result = $query->matching($query->withUid(intval($fieldValue)));
+               } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
                        if (!empty($childSortByFieldName)) {
                                $query->setOrderings(array($childSortByFieldName => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING));
                        }
-                       $parentKeyFieldName = $columnMap->getParentKeyFieldName();
                        if (isset($parentKeyFieldName)) {
-                               $objects = $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()))->execute();
+                               $result = $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()));
                        } else {
-                               $uidArray = t3lib_div::intExplode(',', $fieldValue);
-                               $objects = $query->matching($query->equals('uid', $uidArray))->execute();
+                               $result = $query->matching($query->equals('uid', t3lib_div::intExplode(',', $fieldValue)));                                     
                        }
                } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
                        $relationTableName = $columnMap->getRelationTableName();
@@ -305,19 +288,62 @@ class Tx_Extbase_Persistence_Mapper_DataMapper implements t3lib_Singleton {
                                Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER,
                                $joinCondition
                                );
-                       $query = $queryFactory->create($columnMap->getChildClassName());
                        $query->setSource($source);
                        if (!empty($childSortByFieldName)) {
                                $query->setOrderings(array($childSortByFieldName => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING));
                        }
-                       // TODO: This is an ugly hack, just ignoring the storage page state from here. Actually, the query settings would have to be passed into the DataMapper, so we can respect
-                       // enableFields and storage page settings.
-                       $query->getQuerySettings()->setRespectStoragePage(FALSE);
-                       $objects = $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()))->execute();
+                       $result = $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()));
                } else {
                        throw new Tx_Extbase_Persistence_Exception('Could not determine type of relation.', 1252502725);
                }
-               return $objects;
+               return $query;
+       }
+
+       /**
+        * Returns the given result as property value of the specified property type.
+        *
+        * @param mixed $result The result could be an object or an ObjectStorage 
+        * @param array $propertyMetaData The property meta data
+        * @return void
+        */
+       public function mapResultToPropertyValue(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $result) {
+               $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
+               if ($result instanceof Tx_Extbase_Persistence_LoadingStrategyInterface) {
+                       $propertyValue = $result;
+               } else {
+                       if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'Tx_Extbase_Persistence_ObjectStorage')) && strpos($propertyMetaData['elementType'], '_') !== FALSE) {
+                               $objects = array();
+                               foreach ($result as $value) {
+                                       $objects[] = $value;
+                               }
+
+                               if ($propertyMetaData['type'] === 'ArrayObject') {
+                                       $propertyValue = new ArrayObject($objects);
+                               } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') {
+                                       $propertyValue = new Tx_Extbase_Persistence_ObjectStorage();
+                                       foreach ($objects as $object) {
+                                               $propertyValue->attach($object);
+                                       }
+                               } else {
+                                       $propertyValue = $objects;
+                               }
+                       } elseif (strpos($propertyMetaData['type'], '_') !== FALSE) {
+                               $propertyValue = current($result);
+                       }
+               }
+               return $propertyValue;
+       }
+       
+       /**
+        * Counts the number of related objects assigned to a property of a parent object
+        *
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
+        * @param string $propertyName The name of the proxied property in it's parent
+        * @param mixed $fieldValue The raw field value.
+        */
+       public function countRelated(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue) {
+               $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
+               return $query->count();
        }
 
        /**
index 05625b0..be8fea4 100644 (file)
@@ -40,6 +40,12 @@ class Tx_Extbase_Persistence_ObjectStorage implements Iterator, Countable, Array
        protected $storage = array();
 
        /**
+        *
+        * @var bool
+        */
+       protected $isInitialized = TRUE;
+
+       /**
         * This is a template function to be overwritten by a concrete implementation. It enables you to implement
         * a lazy load implementation. 
         *
@@ -49,6 +55,15 @@ class Tx_Extbase_Persistence_ObjectStorage implements Iterator, Countable, Array
        }
        
        /**
+        * Returns the state of the initialization
+        *
+        * @return void
+        */
+       public function isInitialized() {
+               return $this->isInitialized;
+       }
+       
+       /**
         * Resets the array pointer of the storage
         *
         * @return void
index f6e9958..1a41826 100644 (file)
@@ -182,7 +182,11 @@ class Tx_Extbase_Persistence_Query implements Tx_Extbase_Persistence_QueryInterf
         */
        public function execute() {
                $result = $this->getPreparedQueryObjectModel()->execute();
-               return $this->dataMapper->map($this->className, $result->getRows());
+               if ($this->getQuerySettings()->getReturnRawQueryResult() === TRUE) {
+                       return $result;
+               } else {
+                       return $this->dataMapper->map($this->className, $result->getRows());
+               }
        }
        
        /**
index 21815dd..83b7122 100644 (file)
@@ -59,6 +59,20 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
        protected $pageSelectObject;
 
        /**
+        * The TypoScript Configuration of the page
+        *
+        * @var array
+        */
+       protected $pageTSConfig;
+
+       /**
+        * Caches information about tables (esp. the existing column names)
+        *
+        * @var array
+        */
+       protected $tableInformationCache = array();
+
+       /**
         * Constructs this Storage Backend instance
         *
         * @param t3lib_db $databaseHandle The database handle
@@ -100,13 +114,14 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
 
                $sqlString = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')';
                $this->replacePlaceholders($sqlString, $parameters);
+               // debug($sqlString,2);
                $this->databaseHandle->sql_query($sqlString);
                $this->checkSqlErrors();
                $uid = $this->databaseHandle->sql_insert_id();
                if (!$isRelation) {
                        $this->clearPageCache($tableName, $uid);
                }
-               return $uid;
+               return (int)$uid;
        }
 
        /**
@@ -131,7 +146,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
 
                $sqlString = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $fields) . ' WHERE uid=?';
                $this->replacePlaceholders($sqlString, $parameters);
-
+               // debug($sqlString,2);
                $returnValue = $this->databaseHandle->sql_query($sqlString);
                $this->checkSqlErrors();
                if (!$isRelation) {
@@ -167,6 +182,28 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
        }
 
        /**
+        * Fetches row data from the database
+        *
+        * @param string $identifier The Identifier of the row to fetch
+        * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The Data Map
+        * @return array|FALSE
+        */
+       public function getRowByIdentifier($identifier, Tx_Extbase_Persistence_Mapper_DataMap $dataMap) {
+               $tableName = $dataMap->getTableName();
+               $statement = 'SELECT * FROM ' . $tableName . ' WHERE uid=?';
+               $this->replacePlaceholders($statement, array($identifier));
+               // debug($statement,-2);
+               $res = $this->databaseHandle->sql_query($statement);
+               $this->checkSqlErrors();
+               $row = $this->databaseHandle->sql_fetch_assoc($res);
+               if ($row !== FALSE) {
+                       return $row;
+               } else {
+                       return FALSE;
+               }
+       }
+
+       /**
         * Returns an array with tuples matching the query.
         *
         * @param Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query
@@ -200,7 +237,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         */
        public function countRows(Tx_Extbase_Persistence_QOM_QueryObjectModelInterface $query) {
                $constraint = $query->getConstraint();
-               if($constraint instanceof Tx_Extbase_Persistence_QOM_StatementInterface) throw new Tx_Extbase_Persistence_Storage_Exception_BadConstraint('Could not execute count on queries with aconstraint of type Tx_Extbase_Persistence_QOM_StatementInterface', 1256661045);
+               if($constraint instanceof Tx_Extbase_Persistence_QOM_StatementInterface) throw new Tx_Extbase_Persistence_Storage_Exception_BadConstraint('Could not execute count on queries with a constraint of type Tx_Extbase_Persistence_QOM_StatementInterface', 1256661045);
                $parameters = array();
                $statementParts = $this->parseQuery($query, $parameters);
                $statementParts['fields'] = array('COUNT(*)');
@@ -277,13 +314,14 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
        /**
         * Checks if a Value Object equal to the given Object exists in the data base
         *
-        * @param array $properties The properties of the Value Object
-        * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The Data Map
+        * @param Tx_Extbase_DomainObject_AbstractValueObject $object The Value Object
         * @return array The matching uid
         */
-       public function hasValueObject(array $properties, Tx_Extbase_Persistence_Mapper_DataMap $dataMap) {
+       public function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
                $fields = array();
                $parameters = array();
+               $dataMap = $this->dataMapper->getDataMap(get_class($object));
+               $properties = $object->_getProperties();
                foreach ($properties as $propertyName => $propertyValue) {
                        // FIXME We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method
                        if ($dataMap->isPersistableProperty($propertyName) && ($propertyName !== 'uid')  && ($propertyName !== 'pid') && ($propertyName !== 'isClone')) {
@@ -303,11 +341,12 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                        $statement .= ' AND ' . implode(' AND ', $sql['additionalWhereClause']);
                }
                $this->replacePlaceholders($statement, $parameters);
+               // debug($statement,-2);
                $res = $this->databaseHandle->sql_query($statement);
                $this->checkSqlErrors();
                $row = $this->databaseHandle->sql_fetch_assoc($res);
                if ($row !== FALSE) {
-                       return $row['uid'];
+                       return (int)$row['uid'];
                } else {
                        return FALSE;
                }
@@ -546,6 +585,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         * @return string The query part with replaced placeholders
         */
        protected function replacePlaceholders(&$sqlString, array $parameters) {
+               // TODO profile this method again
                if (substr_count($sqlString, '?') !== count($parameters)) throw new Tx_Extbase_Persistence_Exception('The number of question marks to replace must be equal to the number of parameters.', 1242816074);
                $offset = 0;
                foreach ($parameters as $parameter) {
@@ -598,8 +638,10 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         * @return void
         */
        protected function addPageIdStatement($tableName, array &$sql) {
-               $columns = $this->databaseHandle->admin_get_fields($tableName);         
-               if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('pid', $columns)) {
+               if (empty($this->tableInformationCache[$tableName]['columnNames'])) {
+                       $this->tableInformationCache[$tableName]['columnNames'] = $this->databaseHandle->admin_get_fields($tableName);
+               }
+               if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('pid', $this->tableInformationCache[$tableName]['columnNames'])) {
                        $extbaseFrameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
                        $sql['additionalWhereClause'][] = $tableName . '.pid IN (' . implode(', ', t3lib_div::intExplode(',', $extbaseFrameworkConfiguration['persistence']['storagePid'])) . ')';
                }
@@ -759,7 +801,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                        // if disabled, return
                        return;
                }
-
+               
                $pageIdsToClear = array();
                $storagePage = NULL;
 
@@ -776,14 +818,15 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                        $pageIdsToClear[] = $storagePage;
                }
 
-
                if ($storagePage === NULL) {
                        return;
                }
 
-               $pageTSConfig = t3lib_BEfunc::getPagesTSconfig($storagePage);
-               if (isset($pageTSConfig['TCEMAIN.']['clearCacheCmd']))  {
-                       $clearCacheCommands = t3lib_div::trimExplode(',',strtolower($pageTSConfig['TCEMAIN.']['clearCacheCmd']),1);
+               if (empty($this->pageTSConfig)) {
+                       $this->pageTSConfig = t3lib_BEfunc::getPagesTSconfig($storagePage);
+               }
+               if (isset($this->pageTSConfig['TCEMAIN.']['clearCacheCmd']))    {
+                       $clearCacheCommands = t3lib_div::trimExplode(',',strtolower($this->pageTSConfig['TCEMAIN.']['clearCacheCmd']),1);
                        $clearCacheCommands = array_unique($clearCacheCommands);
                        foreach ($clearCacheCommands as $clearCacheCommand)     {
                                if (t3lib_div::testInt($clearCacheCommand))     {
@@ -791,7 +834,8 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                                }
                        }
                }
-
+               
+               // TODO check if we can hand this over to the Dispatcher to clear the page only once
                Tx_Extbase_Utility_Cache::clearPageCache($pageIdsToClear);
        }
 }
index 126061c..d211f07 100644 (file)
@@ -49,6 +49,12 @@ class Tx_Extbase_Persistence_Typo3QuerySettings implements Tx_Extbase_Persistenc
        protected $respectEnableFields = TRUE;
 
        /**
+        * Flag if the the query result should be returned as raw QueryResult.
+        * @var boolean
+        */
+       protected $returnRawQueryResult = FALSE;
+
+       /**
         * Sets the flag if the storage page should be respected for the query.
         *
         * @param $respectStoragePage If TRUE the storage page ID will be determined and the statement will be extended accordingly.
@@ -89,6 +95,25 @@ class Tx_Extbase_Persistence_Typo3QuerySettings implements Tx_Extbase_Persistenc
        public function getRespectEnableFields() {
                return $this->respectEnableFields;
        }
+       
+       /**
+        * Sets the state, if the QueryResult should be returned unmapped.
+        *
+        * @var boolean $returnRawQueryResult TRUE, if the QueryResult should be returned unmapped; otherwise FALSE.
+        * @return void
+        */
+       public function setReturnRawQueryResult($returnRawQueryResult) {
+               $this->returnRawQueryResult = $returnRawQueryResult;
+       }
+       
+       /**
+        * Returns the state, if the QueryResult should be returned unmapped.
+        *
+        * @return boolean TRUE, if the QueryResult should be returned unmapped; otherwise FALSE.
+        */
+       public function getReturnRawQueryResult() {
+               return $this->returnRawQueryResult;
+       }       
 
 }
 ?>
\ No newline at end of file
index dd6ad69..79cfa24 100644 (file)
@@ -54,5 +54,19 @@ interface Tx_Extbase_Persistence_Typo3QuerySettingsInterface extends Tx_Extbase_
         */
        public function setRespectEnableFields($respectEnableFields);
 
+       /**
+        * Sets the state, if the QueryResult should be returned unmapped.
+        *
+        * @return boolean TRUE, if the QueryResult should be returned unmapped; otherwise FALSE.
+        */
+       public function setReturnRawQueryResult($returnRawQueryResult);
+       
+       /**
+        * Returns the state, if the QueryResult should be returned unmapped.
+        *
+        * @return boolean TRUE, if the QueryResult should be returned unmapped; otherwise FALSE.
+        */
+       public function getReturnRawQueryResult();
+       
 }
 ?>
\ No newline at end of file
index 6872de0..b2543fd 100644 (file)
@@ -197,7 +197,7 @@ class Tx_Extbase_Property_Mapper {
                                if ($targetClassSchema !== NULL && $targetClassSchema->hasProperty($propertyName)) {
                                        $propertyMetaData = $targetClassSchema->getProperty($propertyName);
 
-                                       if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'Tx_Extbase_Persistence_ObjectStorage', 'Tx_Extbase_Persistence_LazyObjectStorage')) && strpos($propertyMetaData['elementType'], '_') !== FALSE) {
+                                       if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'Tx_Extbase_Persistence_ObjectStorage')) && strpos($propertyMetaData['elementType'], '_') !== FALSE) {
                                                $objects = array();
                                                foreach ($propertyValue as $value) {
                                                        $objects[] = $this->transformToObject($value, $propertyMetaData['elementType'], $propertyName);
@@ -206,7 +206,7 @@ class Tx_Extbase_Property_Mapper {
                                                        // make sure we hand out what is expected
                                                if ($propertyMetaData['type'] === 'ArrayObject') {
                                                        $propertyValue = new ArrayObject($objects);
-                                               } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage' || $propertyMetaData['type'] === 'Tx_Extbase_Persistence_LazyObjectStorage') {
+                                               } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') {
                                                        $propertyValue = new Tx_Extbase_Persistence_ObjectStorage();
                                                        foreach ($objects as $object) {
                                                                $propertyValue->attach($object);
index 09078c1..b78ec0f 100644 (file)
@@ -111,11 +111,12 @@ class Tx_Extbase_Reflection_ClassSchema {
         * @param string $name Name of the property
         * @param string $type Type of the property (see ALLOWED_TYPES_PATTERN)
         * @param boolean $lazy Whether the property should be lazy-loaded when reconstituting
+        * @param string $cascade Strategy to cascade the object graph.
         * @return void
         * @author Robert Lemke <robert@typo3.org>
         * @author Karsten Dambekalns <karsten@typo3.org>
         */
-       public function addProperty($name, $type, $lazy = FALSE) {
+       public function addProperty($name, $type, $lazy = FALSE, $cascade = '') {
                $matches = array();
                if (preg_match(self::ALLOWED_TYPES_PATTERN, $type, $matches)) {
                        $type = ($matches['type'] === 'int') ? 'integer' : $matches['type'];
@@ -128,7 +129,8 @@ class Tx_Extbase_Reflection_ClassSchema {
                        $this->properties[$name] = array(
                                'type' => $type,
                                'elementType' => $elementType,
-                               'lazy' => $lazy
+                               'lazy' => $lazy,
+                               'cascade' => $cascade
                        );
                } else {
                        throw new Tx_Extbase_Reflection_Exception_InvalidPropertyType('Invalid property type encountered: ' . $type, 1220387528);
@@ -143,7 +145,7 @@ class Tx_Extbase_Reflection_ClassSchema {
         * @author Karsten Dambekalns <karsten@typo3.org>
         */
        public function getProperty($propertyName) {
-               return $this->properties[$propertyName];
+               return is_array($this->properties[$propertyName]) ? $this->properties[$propertyName] : array();
        }
 
        /**
index 170f6cf..9c99cb2 100644 (file)
@@ -262,7 +262,6 @@ class Tx_Extbase_Reflection_Service implements t3lib_Singleton {
         * @param string $className Name of the class containing the property
         * @param string $propertyName Name of the tagged property
         * @param string $tag Tag to return the values of
-               if (!isset($this->propertyTagsValues[$className])) return array();
         * @return array An array of values or an empty array if the tag was not found
         * @author Robert Lemke <robert@typo3.org>
         * @api
@@ -389,7 +388,8 @@ class Tx_Extbase_Reflection_Service implements t3lib_Singleton {
 
                foreach ($this->getClassPropertyNames($className) as $propertyName) {
                        if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
-                               $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'));
+                               $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
+                               $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
                        }
                        if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
                                $classSchema->setUUIDPropertyName($propertyName);
@@ -459,7 +459,6 @@ class Tx_Extbase_Reflection_Service implements t3lib_Singleton {
         * Tries to load the reflection data from this service's cache.
         *
         * @return void
-
         */
        protected function loadFromCache() {
                $cacheKey = $this->getCacheKey();
@@ -475,7 +474,6 @@ class Tx_Extbase_Reflection_Service implements t3lib_Singleton {
         * Exports the internal reflection data into the ReflectionData cache.
         *
         * @return void
-
         */
        protected function saveToCache() {
                if (!is_object($this->cache)) {
index b3952a7..510500c 100644 (file)
@@ -119,7 +119,6 @@ tt_content.list.20.' . $pluginSignature . ' = ' . ($contentObjectType === 'USER'
        }
        persistence {
                storagePid =
-               enableAutomaticCacheClearing = 1
                classes {
                }
        }
index b542f37..ba91456 100644 (file)
@@ -59,9 +59,6 @@ class Tx_Extbase_DomainObject_AbstractEntity_testcase extends Tx_Extbase_BaseTes
                $domainObject->_memorizePropertyCleanState('bar');
                $domainObject->bar = 'Now it is sunny.';
                $this->assertTrue($domainObject->_isDirty());
-
-               // We can check here that _getDirtyProperties returns the correct values as well.
-               $this->assertSame(array('bar' => 'Now it is sunny.'), $domainObject->_getDirtyProperties());
        }
 
        /**
@@ -85,23 +82,6 @@ class Tx_Extbase_DomainObject_AbstractEntity_testcase extends Tx_Extbase_BaseTes
        /**
         * @test
         */
-       public function getDirtyPropertiesReturnsNoPropertiesIfObjectWithObjectPropertiesIsClean() {
-               $domainObjectName = uniqid('DomainObject_');
-               eval('class ' . $domainObjectName . ' extends Tx_Extbase_DomainObject_AbstractEntity {
-                       public $foo;
-                       public $bar;
-               }');
-               $domainObject = new $domainObjectName();
-               $domainObject->foo = new DateTime();
-               $domainObject->bar = 'It is raining outside';
-               $domainObject->_memorizePropertyCleanState('foo');
-               $domainObject->_memorizePropertyCleanState('bar');
-               $this->assertEquals(array(), $domainObject->_getDirtyProperties());
-       }
-
-       /**
-        * @test
-        */
        public function objectIsNotDirtyAfterCallingMemorizeCleanStateWithOtherDomainObjectsAsProperties() {
                $domainObjectName = uniqid('DomainObject_');
                eval('class ' . $domainObjectName . ' extends Tx_Extbase_DomainObject_AbstractEntity {
index 1bf7dd3..d877fe5 100644 (file)
@@ -39,7 +39,6 @@ class Tx_Extbase_Persistence_Repository_testcase extends Tx_Extbase_BaseTestCase
                        public function _setProperty($propertyName, $propertyValue) {}
                        public function _getProperty($propertyName) {}
                        public function _getProperties() {}
-                       public function _getDirtyProperties() {}
                        public function getUid() { return 123; }
                }');
        }
index 8f31ee5..ed189a6 100644 (file)
@@ -1,5 +1,7 @@
 config.tx_extbase {
        persistence{
+               enableAutomaticCacheClearing = 1
+               updateReferenceIndex = 0
                classes {
                        Tx_Extbase_Domain_Model_FrontendUser {
                                mapping {