[!!!][+FEATURE] Extbase (Persistence): Implemented Single Table Inheritance. Resolves...
authorJochen Rau <j.rau@web.de>
Thu, 15 Apr 2010 20:35:54 +0000 (20:35 +0000)
committerJochen Rau <j.rau@web.de>
Thu, 15 Apr 2010 20:35:54 +0000 (20:35 +0000)
 * Implemented new option subclasses.
 * Extbase doesn't ascend the class hierarchy anymore. You have to specify the table name explicity via "plugin.tx_myext.persistence.classes.[className].mapping.tableName = foo" if it is different from the lowercased class name.
 * Implemented option recordType to map record types of existing tables (e.g. CType "text" in "tt_content").

typo3/sysext/extbase/Classes/Persistence/Backend.php
typo3/sysext/extbase/Classes/Persistence/Mapper/DataMap.php
typo3/sysext/extbase/Classes/Persistence/Mapper/DataMapFactory.php
typo3/sysext/extbase/Classes/Persistence/Mapper/DataMapper.php
typo3/sysext/extbase/Classes/Persistence/Storage/Typo3DbBackend.php

index 09ea626..dd91b09 100644 (file)
@@ -816,6 +816,9 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                if ($dataMap->getModificationDateColumnName() !== NULL) {
                        $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
                }
+               if ($dataMap->getRecordTypeColumnName() !== NULL) {
+                       $row[$dataMap->getRecordTypeColumnName()] = $className; // FIXME add mapping
+               }
                if ($object->_isNew() && !isset($row['pid'])) {
                        $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
                }
index 3d5b7bb..e41e0d8 100644 (file)
@@ -30,7 +30,7 @@
  * @version $ID:$
  */
 class Tx_Extbase_Persistence_Mapper_DataMap {
-
+       
        /**
         * The class name
         *
@@ -46,11 +46,25 @@ class Tx_Extbase_Persistence_Mapper_DataMap {
        protected $tableName;
 
        /**
+        * The record type stored in the "type" field as configured in $TCA
+        *
+        * @var string
+        **/
+       protected $recordType;
+
+       /**
+        * The subclasses of the current class
+        *
+        * @var array
+        **/
+       protected $subclasses = array();
+
+       /**
         * An array of column maps configured in $TCA
         *
         * @var array
         **/
-       protected $columnMaps;
+       protected $columnMaps = array();
                
        /**
         * @var string
@@ -108,13 +122,23 @@ class Tx_Extbase_Persistence_Mapper_DataMap {
        protected $frontendUserGroupColumnName;
 
        /**
+        * @var string
+        **/
+       protected $recordTypeColumnName;
+
+       /**
         * Constructs this DataMap
         *
-        * @param string $className The class name. This determines the table to fetch the configuration for
+        * @param string $className The class name
+        * @param string $tableName The table name
+        * @param string $recordType The record type
+        * @param array $subclasses The subclasses
         */
-       public function __construct($className, $tableName) {
+       public function __construct($className, $tableName, $recordType, array $subclasses = array()) {
                $this->setClassName($className);
                $this->setTableName($tableName);
+               $this->setRecordType($recordType);
+               $this->setSubclasses($subclasses);
        }
 
        /**
@@ -154,6 +178,44 @@ class Tx_Extbase_Persistence_Mapper_DataMap {
        }
        
        /**
+        * Sets the record type
+        *
+        * @param string $recordType The record type
+        * @return void
+        */
+       public function setRecordType($recordType) {
+               $this->recordType = $recordType;
+       }
+
+       /**
+        * Returns the record type
+        *
+        * @return string The record type
+        */
+       public function getRecordType() {
+               return $this->recordType;
+       }
+       
+       /**
+        * Sets the subclasses
+        *
+        * @param array $subclasses An array of subclasses
+        * @return void
+        */
+       public function setSubclasses(array $subclasses) {
+               $this->subclasses = $subclasses;
+       }
+
+       /**
+        * Returns the subclasses
+        *
+        * @return array The subclasses
+        */
+       public function getSubclasses() {
+               return $this->subclasses;
+       }
+       
+       /**
         * Adds a given column map to the data map.
         *
         * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
@@ -393,4 +455,23 @@ class Tx_Extbase_Persistence_Mapper_DataMap {
                return $this->frontendUserGroupColumnName;
        }
 
+       /**
+        * Sets the name of a column holding the record type
+        *
+        * @param string $recordTypeColumnName The field name
+        * @return void
+        */
+       public function setRecordTypeColumnName($recordTypeColumnName) {
+               $this->recordTypeColumnName = $recordTypeColumnName;
+       }
+
+       /**
+        * Sets the name of a column holding the record type
+        *
+        * @return string The field name
+        */
+       public function getRecordTypeColumnName() {
+               return $this->recordTypeColumnName;
+       }
+
 }
\ No newline at end of file
index f92b494..2b044fe 100644 (file)
  * @version $ID:$
  */
 class Tx_Extbase_Persistence_Mapper_DataMapFactory {
-
+       
        /**
         * Builds a data map by adding column maps for all the configured columns in the $TCA.
         * It also resolves the type of values the column is holding and the typo of relation the column
         * represents.
         *
-        * @return void
+        * @param string $className The class name you want to fetch the Data Map for
+        * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
         */
        public function buildDataMap($className) {
-               $tableName = NULL;
+               $recordType = $className;
+               $subclasses = array();
+               $tableName = strtolower($className);
                $columnMapping = array();
-               $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
-               if (is_array($extbaseSettings['persistence']['classes'][$className])) {
-                       $persistenceSettings = $extbaseSettings['persistence']['classes'][$className];
-                       if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
-                               $tableName = $persistenceSettings['mapping']['tableName'];
+               
+               $extbaseFrameworkConfiguration = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+               $classSettings = $extbaseFrameworkConfiguration['persistence']['classes'][$className];
+               if ($classSettings !== NULL) {
+                       if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) {
+                               $subclasses = $classSettings['subclasses'];
                        }
-                       if (is_array($persistenceSettings['mapping']['columns'])) {
-                               $columnMapping = $persistenceSettings['mapping']['columns'];
+                       if (isset($classSettings['mapping']['recordType']) && strlen($classSettings['mapping']['recordType']) > 0) {
+                               $recordType = $classSettings['mapping']['recordType'];
                        }
-               } elseif (class_exists($className)) {
-                       foreach (class_parents($className) as $parentClassName) {
-                               $persistenceSettings = $extbaseSettings['persistence']['classes'][$parentClassName];
-                               if (is_array($persistenceSettings)) {
-                                       if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
-                                               $tableName = $persistenceSettings['mapping']['tableName'];
-                                       }
-                                       if (is_array($persistenceSettings['mapping']['columns'])) {
-                                               $columnMapping = $persistenceSettings['mapping']['columns'];
-                                       }
-                               }
-                               break;
+                       if (isset($classSettings['mapping']['tableName']) && strlen($classSettings['mapping']['tableName']) > 0) {
+                               $tableName = $classSettings['mapping']['tableName'];
+                       }
+                       if (isset($classSettings['mapping']['columns']) && is_array($classSettings['mapping']['columns'])) {
+                               $columnMapping = $classSettings['mapping']['columns'];
                        }
                }
-               if ($tableName === NULL) {
-                       $tableName = strtolower($className);
-               }
-               $dataMap = t3lib_div::makeInstance('Tx_Extbase_Persistence_Mapper_DataMap', $className, $tableName);
+
+               $dataMap = t3lib_div::makeInstance('Tx_Extbase_Persistence_Mapper_DataMap', $className, $tableName, $recordType, $subclasses);
                $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName);
                $columnConfigurations = array();
                foreach ($this->getColumnsDefinition($tableName) as $columnName => $columnDefinition) {
@@ -82,7 +77,7 @@ class Tx_Extbase_Persistence_Mapper_DataMapFactory {
                }
                return $dataMap;
        }
-
+       
        /**
         * Returns the TCA ctrl section of the specified table; or NULL if not set
         *
@@ -127,6 +122,7 @@ class Tx_Extbase_Persistence_Mapper_DataMapFactory {
                if (isset($controlSection['delete'])) $dataMap->setDeletedFlagColumnName($controlSection['delete']);
                if (isset($controlSection['languageField'])) $dataMap->setLanguageIdColumnName($controlSection['languageField']);
                if (isset($controlSection['transOrigPointerField'])) $dataMap->setTranslationOriginColumnName($controlSection['transOrigPointerField']);
+               if (isset($controlSection['type'])) $dataMap->setRecordTypeColumnName($controlSection['type']);
                if (isset($controlSection['enablecolumns']['disabled'])) $dataMap->setDisabledFlagColumnName($controlSection['enablecolumns']['disabled']);
                if (isset($controlSection['enablecolumns']['starttime'])) $dataMap->setStartTimeColumnName($controlSection['enablecolumns']['starttime']);
                if (isset($controlSection['enablecolumns']['endtime'])) $dataMap->setEndTimeColumnName($controlSection['enablecolumns']['endtime']);
index fcdfbf9..1a9017b 100644 (file)
@@ -132,20 +132,39 @@ class Tx_Extbase_Persistence_Mapper_DataMapper implements t3lib_Singleton {
        }
 
        /**
-        * Maps the given rows on objects of the given class
+        * Maps the given rows on objects
         *
-        * @param string $className The name of the target class
+        * @param string $className The name of the class
         * @param array $rows An array of arrays with field_name => value pairs
         * @return array An array of objects of the given class
         */
        public function map($className, array $rows) {
                $objects = array();
                foreach ($rows as $row) {
-                       $objects[] = $this->mapSingleRow($className, $row);
+                       $objects[] = $this->mapSingleRow($this->getTargetType($className, $row), $row);
                }
                return $objects;
        }
-
+       
+       /**
+        * Returns the target type for the given row.
+        *
+        * @param string $className The name of the class
+        * @param array $row A single array with field_name => value pairs
+        * @return string The target type (a class name)
+        */
+       public function getTargetType($className, array $row) {
+               $dataMap = $this->getDataMap($className);
+               if ($dataMap->getRecordTypeColumnName() === NULL) return $className;
+               foreach ($dataMap->getSubclasses() as $subclassName) {
+                       $recordSubtype = $this->getDataMap($subclassName)->getRecordType();
+                       if ($row[$dataMap->getRecordTypeColumnName()] === $recordSubtype) {
+                               return $subclassName;
+                       }
+               }
+               return $className;
+       }
+       
        /**
         * Maps a single row on an object of the given class
         *
index 75d578e..3a76230 100644 (file)
@@ -256,7 +256,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
        /**
         * Parses the query and returns the SQL statement parts.
         *
-        * @param Tx_Extbase_Persistence_QueryInterface $query
+        * @param Tx_Extbase_Persistence_QueryInterface $query The query
         * @return array The SQL statement parts
         */
        public function parseQuery(Tx_Extbase_Persistence_QueryInterface $query, array &$parameters) {
@@ -366,13 +366,45 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
         */
        protected function parseSource(Tx_Extbase_Persistence_QOM_SourceInterface $source, array &$sql) {
                if ($source instanceof Tx_Extbase_Persistence_QOM_SelectorInterface) {
-                       $tableName = $source->getSelectorName();
+                       $className = $source->getNodeTypeName();
+                       $tableName = $this->dataMapper->getDataMap($className)->getTableName();
+                       $this->addRecordTypeConstraint($className, $sql);
                        $sql['fields'][$tableName] = $tableName . '.*';
                        $sql['tables'][$tableName] = $tableName;
                } elseif ($source instanceof Tx_Extbase_Persistence_QOM_JoinInterface) {
                        $this->parseJoin($source, $sql);
                }
        }
+       
+       /**
+        * Adda a constrint to ensure that the record type of the returned tuples is matching the data type of the repository.
+        *
+        * @param string $className The class name
+        * @param array &$sql The query parts
+        * @return void
+        */
+       protected function addRecordTypeConstraint($className, &$sql) {
+               if ($className !== NULL) {
+                       $dataMap = $this->dataMapper->getDataMap($className);
+                       if ($dataMap->getRecordTypeColumnName() !== NULL) {
+                               $recordSubtypes = array();
+                               foreach ($dataMap->getSubclasses() as $subclassName) {
+                                       $recordSubtype = $this->dataMapper->getDataMap($subclassName)->getRecordType();
+                                       if ($recordSubtype !== NULL) {
+                                               $recordSubtypes[] = $recordSubtype;
+                                       } else {
+                                               $recordSubtypes[] = $subclassName;
+                                       }
+                               }
+                               $recordTypes = array_merge(array($dataMap->getRecordType()), $recordSubtypes);
+                               $recordTypeStatements = array();
+                               foreach ($recordTypes as $recordType) {
+                                       $recordTypeStatements[] = $dataMap->getTableName() . '.' . $dataMap->getRecordTypeColumnName() . '=' . $this->databaseHandle->fullQuoteStr($recordType, 'foo');
+                               }
+                               $sql['additionalWhereClause'][] = '(' . implode(' OR ', $recordTypeStatements) . ')';
+                       }
+               }
+       }
 
        /**
         * Transforms a Join into SQL and parameter arrays
@@ -384,6 +416,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
        protected function parseJoin(Tx_Extbase_Persistence_QOM_JoinInterface $join, array &$sql) {
                $leftSource = $join->getLeft();
                $leftClassName = $leftSource->getNodeTypeName();
+               $this->addRecordTypeConstraint($leftClassName, $sql);
                $leftTableName = $leftSource->getSelectorName();
                // $sql['fields'][$leftTableName] = $leftTableName . '.*';
                $rightSource = $join->getRight();
@@ -395,6 +428,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                        $rightTableName = $rightSource->getSelectorName();
                        $sql['fields'][$leftTableName] = $rightTableName . '.*';
                }
+               $this->addRecordTypeConstraint($rightClassName, $sql);
                
                $sql['tables'][$leftTableName] = $leftTableName;
                $sql['unions'][$rightTableName] = 'LEFT JOIN ' . $rightTableName;
@@ -457,7 +491,7 @@ class Tx_Extbase_Persistence_Storage_Typo3DbBackend implements Tx_Extbase_Persis
                $operator = $comparison->getOperator();
                $operand2 = $comparison->getOperand2();
                if (($operator === Tx_Extbase_Persistence_QueryInterface::OPERATOR_EQUAL_TO) && (is_array($operand2) || ($operand2 instanceof ArrayAccess) || ($operand2 instanceof Traversable))) {
-                       // FIXME this else branch enables equals() to behave like in(). This behavior is deprecated and will be removed in future. Use in() instead.
+                       // this else branch enables equals() to behave like in(). This behavior is deprecated and will be removed in future. Use in() instead.
                        $operator = Tx_Extbase_Persistence_QueryInterface::OPERATOR_IN;
                }