* some more steps towards an implementation of the TCA mapper
authorJochen Rau <j.rau@web.de>
Tue, 3 Feb 2009 11:59:50 +0000 (11:59 +0000)
committerJochen Rau <j.rau@web.de>
Tue, 3 Feb 2009 11:59:50 +0000 (11:59 +0000)
typo3/sysext/extbase/Classes/DomainObject/TX_EXTMVC_DomainObject_AbstractDomainObject.php
typo3/sysext/extbase/Classes/DomainObject/TX_EXTMVC_DomainObject_Entity.php
typo3/sysext/extbase/Classes/DomainObject/TX_EXTMVC_DomainObject_ValueObject.php
typo3/sysext/extbase/Classes/Persistence/Mapper/TX_EXTMVC_Persistence_Mapper_TcaMapper.php
typo3/sysext/extbase/Classes/Persistence/TX_EXTMVC_Persistence_Repository.php
typo3/sysext/extbase/Classes/Persistence/TX_EXTMVC_Persistence_RepositoryInterface.php
typo3/sysext/extbase/Classes/Persistence/TX_EXTMVC_Persistence_Session.php
typo3/sysext/extbase/Documentation/todo.txt [new file with mode: 0644]

index 2263d35..e47223c 100644 (file)
@@ -21,7 +21,6 @@ declare(ENCODING = 'utf-8');
  * The TYPO3 project - inspiring people to share!                         *
  *                                                                        */
 
-require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/Utility/TX_EXTMVC_Utility_Strings.php');
 require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/Persistence/Mapper/TX_EXTMVC_Persistence_Mapper_TcaMapper.php');
 
 /**
@@ -33,44 +32,16 @@ require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/Persistence/Mapper/TX_EX
 abstract class TX_EXTMVC_DomainObject_AbstractDomainObject {
        
        /**
-        * An array of properties filled with database values of columns configured in $TCA.
-        *
-        * @var array
-        */
-       private $cleanProperties = NULL;
-       
-       /**
-        * A configuration array of properties configured as 1:n relations in $TCA.
-        *
-        * @var array
+        * @var string The uid
         */
-       private $oneToManyRelations = array();
-       
+       protected $uid;
+
        /**
-        * A configuration array of properties configured as m:n relations in $TCA.
+        * An array of properties filled with database values of columns configured in $TCA.
         *
         * @var array
         */
-       private $manyToManyRelations = array();
-       
-       private function initCleanProperties() {
-               $properties = get_object_vars($this);
-               $dataMapper = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Mapper_TcaMapper');
-               foreach ($properties as $propertyName => $propertyValue) {
-                       if ($dataMapper->isPersistable($this, $propertyName)) {
-                               $this->cleanProperties[$propertyName] = NULL;
-                       }
-               }
-               $this->cleanProperties['uid'] = NULL;
-       }
-       
-       public function getOneToManyRelations() {
-               return $this->oneToManyRelations;
-       }
-       
-       public function getManyToManyRelations() {
-               return $this->manyToManyRelations;
-       }
+       private $cleanProperties = NULL;
        
        /**
         * This is the magic wakeup() method. It's invoked by the unserialize statement in the reconstitution process
@@ -87,6 +58,24 @@ abstract class TX_EXTMVC_DomainObject_AbstractDomainObject {
                $this->initCleanProperties();
        }
        
+       /**
+        * Getter for uid
+        *
+        * @return string
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function getUid() {
+               return $this->uid;
+       }
+       
+       /**
+        * Reconstitutes a property. This method should only be called at reconstitution time!
+        *
+        * @param string $propertyName 
+        * @param string $value 
+        * @return void
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
        public function _reconstituteProperty($propertyName, $value) {
                if (property_exists($this, $propertyName)) {
                        $this->$propertyName = $value;
@@ -103,6 +92,7 @@ abstract class TX_EXTMVC_DomainObject_AbstractDomainObject {
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
        public function _memorizeCleanState() {
+               $this->initCleanProperties();
                $cleanProperties = array();
                foreach ($this->cleanProperties as $propertyName => $propertyValue) {
                        $cleanProperties[$propertyName] = $this->$propertyName;
@@ -111,18 +101,50 @@ abstract class TX_EXTMVC_DomainObject_AbstractDomainObject {
        }
        
        /**
-        * returns TRUE if the properties configured in $TCA were modified after reconstitution
+        * returns TRUE if the properties were modified after reconstitution
         *
         * @return boolean
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
        public function _isDirty() {
-               if (!is_array($this->cleanProperties)) throw new TX_EXTMVC_Persistence_Exception_CleanStateNotMemorized('The clean state of the object "' . get_class($this) . '" has not been memorized before asking _isDirty().', 1233309106);
+               // if (!is_array($this->cleanProperties)) throw new TX_EXTMVC_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_EXTMVC_Persistence_Exception_TooDirty('The uid "' . $this->uid . '" has been modified, that is simply too much.', 1222871239);
                foreach ($this->cleanProperties as $propertyName => $propertyValue) {
                        if ($this->$propertyName !== $propertyValue) return TRUE;
                }
                return FALSE;
-       }       
+       }
+
+       /**
+        * Returns a hash map of persitable properties and $values
+        *
+        * @return boolean
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function _getProperties() {
+               // if (!is_array($this->cleanProperties)) throw new TX_EXTMVC_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_EXTMVC_Persistence_Exception_TooDirty('The uid "' . $this->uid . '" has been modified, that is simply too much.', 1222871239);
+               $dataMapper = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Mapper_TcaMapper');
+               $properties = get_object_vars($this);
+               $dirtyProperties = array();
+               foreach ($properties as $propertyName => $propertyValue) {
+                       if ($dataMapper->isPersistable(get_class($this), $propertyName)) {
+                               $dirtyProperties[$propertyName] = $propertyValue;
+                       }
+               }
+               return $dirtyProperties;
+       }
+
+       private function initCleanProperties() {
+               $properties = get_object_vars($this);
+               $dataMapper = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Mapper_TcaMapper');
+               foreach ($properties as $propertyName => $propertyValue) {
+                       if ($dataMapper->isPersistable(get_class($this), $propertyName)) {
+                               $this->cleanProperties[$propertyName] = NULL;
+                       }
+               }
+               $this->cleanProperties['uid'] = NULL;
+       }
+       
 }
 ?>
\ No newline at end of file
index 0eabe81..863bfc6 100644 (file)
@@ -29,22 +29,6 @@ require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/DomainObject/TX_EXTMVC_D
  * @version $Id:$
  * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
  */
-class TX_EXTMVC_DomainObject_Entity extends TX_EXTMVC_DomainObject_AbstractDomainObject {
-
-       /**
-        * @var string The uid
-        */
-       protected $uid;
-
-       /**
-        * Getter for uid
-        *
-        * @return string
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       public function getUid() {
-               return $this->uid;
-       }
-       
+class TX_EXTMVC_DomainObject_Entity extends TX_EXTMVC_DomainObject_AbstractDomainObject {      
 }
 ?>
\ No newline at end of file
index 7b4749c..049d303 100644 (file)
@@ -30,6 +30,5 @@ require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/DomainObject/TX_EXTMVC_D
  * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
  */
 abstract class TX_EXTMVC_DomainObject_ValueObject extends TX_EXTMVC_DomainObject_AbstractDomainObject {
-
 }
 ?>
\ No newline at end of file
index 17da9ae..578f78e 100644 (file)
@@ -39,6 +39,13 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
         **/
        protected $cObj;
                
+       /**
+        * The persistence session
+        *
+        * @var 
+        **/
+       protected $session;
+               
        /**
         * Constructs a new mapper
         *
@@ -46,31 +53,20 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
         */
        public function __construct() {
                $this->cObj = t3lib_div::makeInstance('tslib_cObj');
+               $this->session = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Session');
                $GLOBALS['TSFE']->includeTCA();
        }
-               
-       /**
-        * Returns all objects of the given class name
-        *
-        * @return array An array of objects, empty if no objects found
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       public function loadAll($className) {
-               return $this->reconstituteObjects($this->fetch($this->getTableName($className)));
-       }
        
        /**
-        * Finds objects matching 'property=xyz'
+        * Finds objects matching property="xyz"
         *
         * @param string $propertyName The name of the property (will be chekced by a white list)
-        * @param string $arguments The arguments of the magic findBy method
+        * @param string $arguments The WHERE statement
         * @return void
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       public function loadWhere($className, $propertyName, $arguments) {
-               $tableName = $this->getTableName($className);
-               $where = $propertyName . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($arguments[0], $tableName);
-               return $this->reconstituteObjects($className, $this->fetch($tableName, $where));
+       public function findWhere($className, $where = '1=1') {
+               return $this->reconstituteObjects($className, $this->fetch($className, $where));
        }
        
        /**
@@ -84,7 +80,8 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
         * @return void
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       private function fetch($tableName, $where = '1=1', $groupBy = NULL, $orderBy = NULL, $limit = NULL) {
+       private function fetch($className, $where = '1=1', $groupBy = NULL, $orderBy = NULL, $limit = NULL) {
+               $tableName = $this->getTableName($className);
                $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
                        '*', // TODO limit fetched fields
                        $tableName,
@@ -137,16 +134,17 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
                if ($depth > 10) throw new TX_EXTMVC_Persistence_Exception_RecursionTooDeep('The maximum depth of ' . $depth . ' recursions was reached.', 1233352348);
                foreach ($rows as $row) {
                        $object = $this->reconstituteObject($className, $row);
-                       foreach ($object->getOneToManyRelations() as $propertyName => $tcaColumnConfiguration) {
+                       foreach ($this->getOneToManyRelations($className) as $propertyName => $tcaColumnConfiguration) {
                                $relatedRows = $this->fetchOneToMany($object, $tcaColumnConfiguration['foreign_field'], $tcaColumnConfiguration['foreign_table']);
-                               $relatedObjects = $this->reconstituteObjects($relatedRows, $tcaColumnConfiguration['foreign_class'], $depth++);
+                               $relatedObjects = $this->reconstituteObjects($tcaColumnConfiguration['foreign_class'], $relatedRows, ++$depth);
                                $object->_reconstituteProperty($propertyName, $relatedObjects);
                        }
-                       foreach ($object->getManyToManyRelations() as $propertyName => $tcaColumnConfiguration) {
+                       foreach ($this->getManyToManyRelations($className) as $propertyName => $tcaColumnConfiguration) {
                                $relatedRows = $this->fetchManyToMany($object, $tcaColumnConfiguration['foreign_table'], $tcaColumnConfiguration['MM']);
-                               $relatedObjects = $this->reconstituteObjects($relatedRows, $tcaColumnConfiguration['foreign_class'], $depth++);
+                               $relatedObjects = $this->reconstituteObjects($tcaColumnConfiguration['foreign_class'], $relatedRows, ++$depth);
                                $object->_reconstituteProperty($propertyName, $relatedObjects);
                        }
+                       $this->session->registerReconstitutedObject($object);
                        $objects[] = $object;
                }
                return $objects;
@@ -159,36 +157,147 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
         * @param array $properties The names of properties and their values which should be set during the reconstitution
         * @return object The reconstituted object
         * @author Robert Lemke <robert@typo3.org>
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       protected function reconstituteObject($className, array $properties = array()) {
+       protected function reconstituteObject($className, array $properties = array()) {                
                // those objects will be fetched from within the __wakeup() method of the object...
                $GLOBALS['EXTMVC']['reconstituteObject']['properties'] = $properties;
                $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
                unset($GLOBALS['EXTMVC']['reconstituteObject']);
                return $object;
-       }       
+       }
+       
+       public function persistAll($session) {
+               $this->session = $session;
+               $this->persistAggregateRoots();
+
+               foreach ($this->session->getRemovedObjects() as $object) {
+                       $this->delete($object);
+                       $this->session->unregisterRemovedObject($object);
+               }
+
+               $this->save();
+       }
+       
+       /**
+        * Traverse all aggregate roots breadth first.
+        *
+        * @return void
+        * @author Karsten Dambekalns <karsten@typo3.org>
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       protected function persistAggregateRoots() {
+               $aggregateRootClassNames = $this->session->getAggregateRootClassNames();
+               // make sure we have a corresponding node for all new objects on
+               // first level
+               foreach ($aggregateRootClassNames as $className) {
+                       $addedObjects = $this->session->getAddedObjects($className);
+                       foreach ($addedObjects as $object) {
+                               $this->persistObject($object);
+                               $this->session->unregisterAddedObject($object);
+                       }
+               }
+
+               // // now traverse into the objects
+               // foreach ($aggregateRootClassNames as $object) {
+               //      $this->persistObject($object);
+               // }
 
+       }
+       
        /**
-        * Inserts an object in the database.
+        * Persists an object to the database.
         *
         * @return void
+        * @author Karsten Dambekalns <karsten@typo3.org>
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       public function insert(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+       public function persistObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               $queue = array();
                $row = array(
                        'pid' => 0, // FIXME
                        'tstamp' => time(),
-                       'crdate' => time(),
-                       // FIXME static fields
-                       'name' => $object->getName(),
-                       'description' => $object->getDescription()
-                       // 'logo' => $object->getLogo(),
                        );
+               $properties = $object->_getProperties();
+               foreach ($properties as $propertyName => $propertyValue) {
+                       if ($this->isPersistable(get_class($object), $propertyName)) {
+                               if ($this->isRelation(get_class($object), $propertyName)) {
+                                       if (!$this->session->isReconstitutedObject($object) || $this->session->isDirtyObject($object)) {
+                                               $this->persistArray($object, $propertyName, $propertyValue, $queue);
+                                               $row[TX_EXTMVC_Utility_Strings::camelCaseToLowerCaseUnderscored($propertyName)] = count($properties[$propertyName]);
+                                       } else {
+                                               $queue = array_merge($queue, array_values($propertyValue));
+                                       }
+                               } elseif (is_array($propertyValue)) {
+                                       $this->persistArray($object, $propertyName, $propertyValue, $queue);
+                               } elseif ($propertyValue instanceof TX_EXTMVC_DomainObject_AbstractDomainObject) {
+                                       if (!$this->session->isReconstitutedObject($object)) {
+                                               $this->persistObject($propertyValue);
+                                       }
+                                       $queue[] = $propertyValue;
+                               } else {
+                                       // TODO Property Mapper
+                                       $row[TX_EXTMVC_Utility_Strings::camelCaseToLowerCaseUnderscored($propertyName)] = $propertyValue;
+                               }
+                       }
+               }
+               
+               $tableName = $this->getTableName(get_class($object));
                $res = $GLOBALS['TYPO3_DB']->exec_INSERTquery(
-                       $this->getTableName($this->getClassName($object)),
+                       $tableName,
                        $row
                        );
+               
+               $object->_reconstituteProperty('uid', $GLOBALS['TYPO3_DB']->sql_insert_id());
+               $this->session->unregisterObject($object);
+               $this->session->registerReconstitutedObject($object);
+               // var_dump($object);
+               
+               // here we loop over the objects. their nodes are already at the
+               // right place and have the right name. fancy, eh?
+               foreach ($queue as $object) {
+                       $this->persistObject($object);
+               }
+       }
+       
+       /**
+        * Store an array as a node of type flow3:arrayPropertyProxy, with each
+        * array element becoming a property named like the key and the value.
+        *
+        * Every element not being an object or array will become a property on the
+        * node, arrays will be handled recursively.
+        *
+        * Note: Objects contained in the array will have a node created, properties
+        * On those nodes must be set elsewhere!
+        *
+        * @param array $array The array for which to create a node
+        * @param \F3\PHPCR\NodeInterface $parentNode The node to add the property proxy to
+        * @param string $nodeName The name to use for the object, must be a legal name as per JSR-283
+        * @param array &$queue Found entities are accumulated here.
+        * @author Karsten Dambekalns <karsten@typo3.org>
+        */
+       protected function persistArray(TX_EXTMVC_DomainObject_AbstractDomainObject $parentObject, $propertyName, array $array, array &$queue) {
+               foreach ($array as $key => $element) {
+                       if ($element instanceof TX_EXTMVC_DomainObject_AbstractDomainObject) {
+                               if (!$this->session->isReconstitutedObject($element) || $this->session->isDirtyObject($element)) {
+                                       $this->persistObject($element);
+                               }
+                       } elseif (is_array($element)) {
+                               $this->persistArray($parentObject, $propertyName, $element, $queue);
+                       } else {
+                               $queue[] = $element;
+                       }
+                       // TODO persist arrays with plain values
+
+               }
+       }
+       
+       /**
+        * Deletes all removed objects from the database.
+        *
+        * @return void
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       protected function processRemovedObject($object) {
        }
        
        /**
@@ -197,8 +306,35 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
         * @return void
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       public function update(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
-
+       public function update(TX_EXTMVC_DomainObject_AbstractDomainObject $object, $depth = 0) {
+               if ($depth > 10) throw new TX_EXTMVC_Persistence_Exception_RecursionTooDeep('The maximum depth of ' . $depth . ' recursions was reached.', 1233352348);
+               $row = array(
+                       'tstamp' => time(),
+                       );
+               $properties = $object->_getProperties();
+               $columns = $this->getColumns($this->getClassName($object));
+               $relations = $this->getRelations($this->getClassName($object));
+               foreach ($relations as $propertyName => $tcaColumnConfiguration) {
+                       foreach ($properties[$propertyName] as $object) {
+                               // TODO implement reverse update chain
+                               if (TRUE || $object->_isDirty()) {
+                                       $this->update($object, ++$depth);
+                               }
+                       }
+                       $row[TX_EXTMVC_Utility_Strings::camelCaseToLowerCaseUnderscored($propertyName)] = count($properties[$propertyName]);
+                       unset($properties[$propertyName]);
+               }
+               foreach ($properties as $propertyName => $propertyValue) {
+                       $row[TX_EXTMVC_Utility_Strings::camelCaseToLowerCaseUnderscored($propertyName)] = $propertyValue;
+               }
+               $uid = $object->getUid();
+               // debug($uid);
+               // debug($row);
+               // $res = $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
+               //      $this->getTableName($this->getClassName($object)),
+               //      'uid=' . $object->getUid(),
+               //      $row
+               //      );
        }
        
        /**
@@ -227,9 +363,10 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
                }
        }
        
-       public function getColumns(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
-               t3lib_div::loadTCA($this->getTableName($this->getClassName($object)));
-               return $GLOBALS['TCA'][$this->getTableName($this->getClassName($object))]['columns'];
+       protected function getColumns($className) {
+               $tableName = $this->getTableName($className);
+               t3lib_div::loadTCA($tableName);
+               return $GLOBALS['TCA'][$tableName]['columns'];
        }
                
        protected function getClassName(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
@@ -237,22 +374,74 @@ class TX_EXTMVC_Persistence_Mapper_TcaMapper implements t3lib_singleton {
        }
        
        protected function getTableName($className) {
+               // TODO implement table name aliases
                return strtolower($className);
        }
        
-       protected function getDeletedColumnName($tableName) {
+       protected function getDeletedColumnName($className) {
+               $this->getTableName($className);
                return $GLOBALS['TCA'][$tableName]['ctrl']['delete'];
        }
        
-       protected function getHiddenColumnName($tableName) {
-               return $GLOBALS['TCA'][$tableNAme]['ctrl']['enablecolumns']['disabled'];
+       protected function getHiddenColumnName($className) {;
+               $this->getTableName($className);
+               return $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['disabled'];
+       }
+       
+       protected function getRelations($className) {
+               return t3lib_div::array_merge_recursive_overrule($this->getOneToManyRelations($className), $this->getManyToManyRelations($className));
        }
        
-       public function isPersistable(TX_EXTMVC_DomainObject_AbstractDomainObject $object, $propertyName) {
-               $columns = $this->getColumns($object);
+       protected function isRelation($className, $propertyName) {
+               $columns = $this->getColumns($className);               
+               if (array_key_exists('foreign_table', $columns[TX_EXTMVC_Utility_Strings::camelCaseToLowerCaseUnderscored($propertyName)]['config'])) return TRUE;
+               return FALSE;
+       }
+       
+       protected function getOneToManyRelations($className) {
+               $columns = $this->getColumns($className);
+               $oneToManyRelations = array();
                foreach ($columns as $columnName => $columnConfiguration) {
-                       if (TX_EXTMVC_Utility_Strings::camelCaseToLowerCaseUnderscored($columnName) == $propertyName) return TRUE;
+                       $propertyName = TX_EXTMVC_Utility_Strings::underscoredToLowerCamelCase($columnName);
+                       if (array_key_exists('foreign_table', $columnConfiguration['config'])) {
+                               // TODO take IRRE into account
+                               if (!array_key_exists('MM', $columnConfiguration['config'])) {
+                                       // TODO implement a $TCA object 
+                                       $oneToManyRelations[$propertyName] = array(
+                                               'foreign_class' => $columnConfiguration['config']['foreign_class'],
+                                               'foreign_table' => $columnConfiguration['config']['foreign_table'],
+                                               'foreign_field' => $columnConfiguration['config']['foreign_field'],
+                                               'foreign_table_field' => $columnConfiguration['config']['foreign_table_field']
+                                               );
+                               }
+                       }                               
                }
+               return $oneToManyRelations;
+       }
+       
+       protected function getManyToManyRelations($className) {
+               $columns = $this->getColumns($className);
+               $relations = array();
+               foreach ($columns as $columnName => $columnConfiguration) {
+                       $propertyName = TX_EXTMVC_Utility_Strings::underscoredToLowerCamelCase($columnName);
+                       if (array_key_exists('foreign_table', $columnConfiguration['config'])) {
+                               // TODO take IRRE into account
+                               if (array_key_exists('MM', $columnConfiguration['config'])) {
+                                       // TODO implement a $TCA object 
+                                       $relations[$propertyName] = array(
+                                               'foreign_class' => $columnConfiguration['config']['foreign_class'],
+                                               'foreign_table' => $columnConfiguration['config']['foreign_table'],
+                                               'MM' => $columnConfiguration['config']['MM']
+                                               );
+                               }
+                       }                               
+               }
+               return $relations;
+       }
+       
+       public function isPersistable($className, $propertyName) {
+               $columns = $this->getColumns($className);
+               if (array_key_exists(TX_EXTMVC_Utility_Strings::camelCaseToLowerCaseUnderscored($propertyName), $columns)) return TRUE;
                return FALSE;
        }
        
index cb90982..86f51c1 100644 (file)
@@ -87,15 +87,14 @@ class TX_EXTMVC_Persistence_Repository implements TX_EXTMVC_Persistence_Reposito
                $this->objects = new TX_EXTMVC_Persistence_ObjectStorage();
                $this->cObj = t3lib_div::makeInstance('tslib_cObj');
                $repositoryClassName = get_class($this);
-               $this->dataMapper = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Mapper_TcaMapper');
-               // the session object is a singleton
-               $this->session = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Session');
-               $this->session->registerRepository($repositoryClassName);
                if (substr($repositoryClassName, -10) == 'Repository' && substr($repositoryClassName, -11, 1) != '_') {
                        $this->aggregateRootClassName = substr($repositoryClassName, 0, -10);
+               } else {
+                       // TODO throw new Exception
                }
-               // TODO check if the table exists in the database
-               $this->tableName = strtolower($this->aggregateRootClassName);
+               $this->dataMapper = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Mapper_TcaMapper'); // singleton
+               $this->session = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Session'); // singleton
+               $this->session->registerAggregateRootClassName($this->aggregateRootClassName);
                // TODO auto resolve findBy properties
                $this->allowedFindByProperties = array('name');
        }
@@ -109,6 +108,7 @@ class TX_EXTMVC_Persistence_Repository implements TX_EXTMVC_Persistence_Reposito
         */
        public function setAggregateRootClassName($aggregateRootClassName) {
                $this->aggregateRootClassName = $aggregateRootClassName;
+               $this->session->registerAggregateRootClassName($this->aggregateRootClassName);
        }
 
        /**
@@ -121,27 +121,6 @@ class TX_EXTMVC_Persistence_Repository implements TX_EXTMVC_Persistence_Reposito
                return $this->aggregateRootClassName;
        }
        
-       /**
-        * Sets the database table name for the aggregare root
-        *
-        * @param string $tableName The table name for the aggregate root
-        * @return void
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       public function setTableName($tableName) {
-               $this->tableName = $tableName;
-       }
-
-       /**
-        * Returns the database table name for the aggregare root
-        *
-        * @return string The table name for the aggregate root
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       public function getTableName() {
-               return $this->tableName;
-       }
-       
        /**
         * Adds an object to this repository
         *
@@ -180,7 +159,7 @@ class TX_EXTMVC_Persistence_Repository implements TX_EXTMVC_Persistence_Reposito
                if (substr($methodName, 0, 6) === 'findBy') {
                        $propertyName = TX_EXTMVC_Utility_Strings::lowercaseFirst(substr($methodName,6));
                        if (in_array($propertyName, $this->allowedFindByProperties)) {
-                               return $this->findByProperty($propertyName, $arguments);
+                               return $this->findByProperty($propertyName, $arguments[0]);
                        }
                }
                throw new TX_EXTMVC_Persistence_Exception_UnsupportedMethod('The method "' . $methodName . '" is not supported by the repository.', 1233180480);
@@ -193,190 +172,21 @@ class TX_EXTMVC_Persistence_Repository implements TX_EXTMVC_Persistence_Reposito
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
        public function findAll() {
-               return $this->reconstituteObjects($this->fetch($this->getTableName()));
+               return $this->dataMapper->findWhere($this->aggregateRootClassName);
        }
        
        /**
         * Finds objects matching 'property=xyz'
         *
-        * @param string $propertyName The name of the property (will be chekced by a white list)
+        * @param string $propertyName The name of the property (will be checked by a white list)
         * @param string $arguments The arguments of the magic findBy method
         * @return void
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       private function findByProperty($propertyName, $arguments) {
-               $where = $propertyName . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($arguments[0], $this->tableName);
-               return $this->reconstituteObjects($this->fetch($this->getTableName(), $where));
+       private function findByProperty($propertyName, $value) {
+               $where = $propertyName . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, 'foo');
+               return $this->dataMapper->findWhere($this->aggregateRootClassName, $where);
        }
-       
-       /**
-        * Fetches a rows from the database by given SQL statement snippets
-        *
-        * @param string $from FROM statement
-        * @param string $where WHERE statement
-        * @param string $groupBy GROUP BY statement
-        * @param string $orderBy ORDER BY statement
-        * @param string $limit LIMIT statement
-        * @return void
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       private function fetch($tableName, $where = '1=1', $groupBy = NULL, $orderBy = NULL, $limit = NULL) {
-               $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
-                       '*', // TODO limit fetched fields
-                       $tableName,
-                       $where . $this->cObj->enableFields($tableName) . $this->cObj->enableFields($tableName),
-                       $groupBy,
-                       $orderBy,
-                       $limit
-                       );
-               // TODO language overlay; workspace overlay
-               return $rows ? $rows : array();
-       }       
-       
-       /**
-        * Fetches a rows from the database by given SQL statement snippets
-        *
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       private function fetchOneToMany($parentObject, $parentField, $tableName, $where = '', $groupBy = NULL, $orderBy = NULL, $limit = NULL) {
-               $where .= ' ' . $parentField . '=' . intval($parentObject->getUid());
-               return $this->fetch($tableName, $where, $groupBy, $orderBy, $limit);
-       }       
-       
-       /**
-        * Fetches a rows from the database by given SQL statement snippets
-        *
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       private function fetchManyToMany($parentObject, $foreignTableName, $relationTableName, $where = '1=1', $groupBy = NULL, $orderBy = NULL, $limit = NULL) {
-               $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
-                       $foreignTableName . '.*, ' . $relationTableName . '.*',
-                       $foreignTableName . ' LEFT JOIN ' . $relationTableName . ' ON (' . $foreignTableName . '.uid=' . $relationTableName . '.uid_foreign)',
-                       $where . ' AND ' . $relationTableName . '.uid_local=' . intval($parentObject->getUid()) . $this->cObj->enableFields($foreignTableName) . $this->cObj->enableFields($foreignTableName),
-                       $groupBy,
-                       $orderBy,
-                       $limit
-                       );
-               // TODO language overlay; workspace overlay
-               return $rows ? $rows : array();         
-       }
-       
-       /**
-        * Dispatches the reconstitution of a domain object to an appropriate method
-        *
-        * @param array $rows The rows array fetched from the database
-        * @throws TX_EXTMVC_Persistence_Exception_RecursionTooDeep
-        * @return array An array of reconstituted domain objects
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       protected function reconstituteObjects(array $rows, $objectClassName = NULL, $depth = 0) {
-               if ($depth > 10) throw new TX_EXTMVC_Persistence_Exception_RecursionTooDeep('The maximum depth of ' . $depth . ' recursions was reached.', 1233352348);
-               if ($objectClassName === NULL) $objectClassName = $this->aggregateRootClassName;
-               $reconstituteMethodName = 'reconstitute' . array_pop(explode('_', $objectClassName));
-               $objects = array();
-               if (method_exists($this, $reconstituteMethodName)) {
-                       foreach ($rows as $row) {
-                               $objects[] = $this->$reconstituteMethodName($row);                              
-                       }
-               } else {
-                       foreach ($rows as $row) {
-                               $object = $this->reconstituteObject($objectClassName, $row);
-                               foreach ($object->getOneToManyRelations() as $propertyName => $tcaColumnConfiguration) {
-                                       $relatedRows = $this->fetchOneToMany($object, $tcaColumnConfiguration['foreign_field'], $tcaColumnConfiguration['foreign_table']);
-                                       $relatedObjects = $this->reconstituteObjects($relatedRows, $tcaColumnConfiguration['foreign_class'], $depth++);
-                                       $object->_reconstituteProperty($propertyName, $relatedObjects);
-                               }
-                               foreach ($object->getManyToManyRelations() as $propertyName => $tcaColumnConfiguration) {
-                                       $relatedRows = $this->fetchManyToMany($object, $tcaColumnConfiguration['foreign_table'], $tcaColumnConfiguration['MM']);
-                                       $relatedObjects = $this->reconstituteObjects($relatedRows, $tcaColumnConfiguration['foreign_class'], $depth++);
-                                       $object->_reconstituteProperty($propertyName, $relatedObjects);
-                               }
-                               $objects[] = $object;
-                               $this->session->registerReconstitutedObject($object);
-                       }
-               }
-               return $objects;
-       }
-       
-       /**
-        * Reconstitutes the specified object and fills it with the given properties.
-        *
-        * @param string $objectName Name of the object to reconstitute
-        * @param array $properties The names of properties and their values which should be set during the reconstitution
-        * @return object The reconstituted object
-        * @author Robert Lemke <robert@typo3.org>
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       protected function reconstituteObject($objectClassName, array $properties = array()) {
-               // those objects will be fetched from within the __wakeup() method of the object...
-               $GLOBALS['EXTMVC']['reconstituteObject']['properties'] = $properties;
-               $object = unserialize('O:' . strlen($objectClassName) . ':"' . $objectClassName . '":0:{};');
-               unset($GLOBALS['EXTMVC']['reconstituteObject']);
-               return $object;
-       }
-       
-       /**
-        * Persists changes (added, removed or changed objects) to the database
-        *
-        * @return void
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       public function persistAll() {
-               $this->deleteRemoved();
-               $this->insertAdded();
-               $this->updateDirty();
-       }
-       
-       /**
-        * Deletes all removed objects from the database.
-        *
-        * @return void
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       protected function deleteRemoved() {
-               $removedObjects = $this->session->getRemovedObjects($this->getAggregateRootClassName());
-
-               // FIXME remove debug code
-               // debug($removedObjects, 'removed objects');
-
-               foreach ($removedObjects as $object) {
-                       $this->dataMapper->delete($object);
-               }
-       }
-       
-       /**
-        * Inserts all added objects in the database.
-        *
-        * @return void
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       protected function insertAdded() {
-               $addedObjects = $this->session->getAddedObjects($this->getAggregateRootClassName());
-
-               // FIXME remove debug code
-               // debug($addedObjects, 'added objects');
                
-               foreach ($addedObjects as $object) {
-                       $this->dataMapper->insert($object);
-               }
-       }
-       
-       /**
-        * Updates all dirty objects.
-        *
-        * @return void
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
-        */
-       protected function updateDirty() {
-               $dirtyObjects = $this->session->getDirtyObjects($this->getAggregateRootClassName());
-
-               // FIXME remove debug code
-               // debug($dirtyObjects, 'dirty objects');
-
-               foreach ($dirtyObjects as $object) {
-                       $this->dataMapper->update($dirtyObjects);
-               }
-       }
-       
 }
 ?>
\ No newline at end of file
index 4c354b3..ab7cd2e 100644 (file)
@@ -63,12 +63,5 @@ interface TX_EXTMVC_Persistence_RepositoryInterface {
         */
        public function remove($object);
 
-       /**
-        * Persists changes (added, removed or changed objects) to the database.
-        *
-        * @return void
-        */
-       public function persistAll();
-
 }
 ?>
\ No newline at end of file
index 4f46f64..e8bb640 100644 (file)
@@ -35,32 +35,28 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
 // TODO Implement against SessionInterface
                
        /**
-        * Objects added to the repository but not yet persisted
+        * Objects added to the repository but not yet persisted in the persistence backend
         *
         * @var TX_EXTMVC_Persistence_ObjectStorage
         */
        protected $addedObjects;
 
        /**
-        * Objects removed but not yet persisted
+        * Objects removed but not yet persisted in the persistence backend
         *
         * @var TX_EXTMVC_Persistence_ObjectStorage
         */
        protected $removedObjects;
 
        /**
-        * Reconstituted objects
-        *
         * @var TX_EXTMVC_Persistence_ObjectStorage
         */
        protected $reconstitutedObjects;
 
        /**
-        * Repositories
-        *
         * @var array
         */
-       protected $repositoryClassNames = array();
+       protected $aggregateRootClassNames = array();
 
        /**
         * Constructs a new Session
@@ -84,6 +80,17 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                $this->removedObjects->detach($object);
                $this->addedObjects->attach($object);
        }
+       
+       /**
+        * Unregisters an added object
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object
+        * @return void
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function unregisterAddedObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               $this->addedObjects->detach($object);
+       }
 
        /**
         * Returns all objects which have been registered as added objects
@@ -100,6 +107,17 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                }
                return $addedObjects;
        }
+       
+       /**
+        * Returns TRUE if the given object is registered as added
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object 
+        * @return bool TRUE if the given object is registered as added; otherwise FALSE
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function isAddedObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               return $this->addedObjects->contains($object);
+       }
 
        /**
         * Registers a removed object
@@ -115,6 +133,17 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                        $this->removedObjects->attach($object);
                }
        }
+       
+       /**
+        * Unregisters a removed object
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object
+        * @return void
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function unregisterRemovedObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               $this->removedObjects->detach($object);
+       }
 
        /**
         * Returns all objects which have been registered as removed objects
@@ -131,6 +160,17 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                }
                return $removedObjects;
        }
+       
+       /**
+        * Returns TRUE if the given object is registered as removed
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object 
+        * @return bool TRUE if the given object is registered as removed; otherwise FALSE
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function isRemovedObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               return $this->removedObjects->contains($object);
+       }
 
        /**
         * Registers a reconstituted object
@@ -143,6 +183,17 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                $this->reconstitutedObjects->attach($object);
                $object->_memorizeCleanState();
        }
+       
+       /**
+        * Unregisters a reconstituted object
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object
+        * @return void
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function unregisterReconstitutedObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               $this->reconstitutedObjects->detach($object);
+       }
 
        /**
         * Returns all objects which have been registered as reconstituted objects
@@ -160,6 +211,17 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                return $reconstitutedObjects;
        }
        
+       /**
+        * Returns TRUE if the given object is registered as reconstituted
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object 
+        * @return bool TRUE if the given object is registered as reconstituted; otherwise FALSE
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function isReconstitutedObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               return $this->reconstitutedObjects->contains($object);
+       }
+
        /**
         * Returns all objects marked as dirty (changed after reconstitution)
         *
@@ -176,7 +238,31 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                        }
                }
                return $dirtyObjects;
-       }       
+       }
+       
+       /**
+        * Returns TRUE if the given object is dirty
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object 
+        * @return bool TRUE if the given object is dirty; otherwise FALSE
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function isDirtyObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               return $object->_isDirty();
+       }
+       
+       /**
+        * Unregisters an object from all states
+        *
+        * @param TX_EXTMVC_DomainObject_AbstractDomainObject $object
+        * @return void
+        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        */
+       public function unregisterObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object) {
+               $this->unregisterAddedObject($object);
+               $this->unregisterRemovedObject($object);
+               $this->unregisterReconstitutedObject($object);
+       }
        
        /**
         * Clears all ObjectStorages
@@ -188,47 +274,40 @@ class TX_EXTMVC_Persistence_Session implements t3lib_singleton {
                $this->addedObjects->removeAll();
                $this->removedObjects->removeAll();
                $this->reconstitutedObjects->removeAll();
-               $this->repositoryClassNames = array();
+               $this->aggregateRootClassNames = array();
        }
        
        /**
-        * Registers a repository to be managed by the session
+        * Registers an aggregate root
         *
-        * @param string $repositoryClassName The repository to be registered
+        * @param string $className The class to be registered
         * @return void
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       public function registerRepository($repositoryClassName) {
-               $this->repositoryClassNames[] = $repositoryClassName;
+       public function registerAggregateRootClassName($className) {
+               $this->aggregateRootClassNames[] = $className;
        }
        
        /**
-        * Unegisters a repository to be managed by the session
+        * Returns all aggregate root classes
         *
-        * @param string $repository The repository to be unregistered
-        * @return void
+        * @return array An array holding the registered aggregate root classes
         * @author Jochen Rau <jochen.rau@typoplanet.de>
         */
-       public function unregisterRepository($repositoryClassName) {
-               // TODO Implement unregisterRepository()
+       public function getAggregateRootClassNames() {
+               return $this->aggregateRootClassNames;
        }
        
        /**
-        * Returns all repository class names
+        * Commits the current persistence session.
         *
-        * @return array An array holding the class names
-        * @author Jochen Rau <jochen.rau@typoplanet.de>
+        * @return void
+        * @author Karsten Dambekalns <karsten@typo3.org>
         */
-       public function getRepositoryClassNames() {
-               return $this->repositoryClassNames;
-       }
-       
        public function commit() {
-               foreach ($this->getRepositoryClassNames() as $repositoryClassName) {
-                       $repository = t3lib_div::makeInstance($repositoryClassName);
-                       $repository->persistAll();
-               }
+               $dataMapper = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Mapper_TcaMapper'); // singleton;
+               $dataMapper->persistAll($this);
        }
-       
+               
 }
 ?>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Documentation/todo.txt b/typo3/sysext/extbase/Documentation/todo.txt
new file mode 100644 (file)
index 0000000..e3c337b
--- /dev/null
@@ -0,0 +1,10 @@
+The SQL statements are issued in the following order
+
+    * all entity insertions, in the same order the corresponding objects were added using $repository->add($object)
+    * all entity updates
+    * all collection deletions
+    * all collection element deletions, updates and insertions
+    * all collection insertions
+    * all entity deletions, in the same order the corresponding objects were removed using $repository->remove($object)
+       (Exception: entity instances using application-assigned identifiers are inserted when they are saved.)
+