Merge "[TASK] Backport changes to Extbase_Error_Message"
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Mapper / DataMapFactory.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25 /**
26 * A factory for a data map to map a single table configured in $TCA on a domain object.
27 *
28 * @package Extbase
29 * @subpackage Persistence\Mapper
30 * @version $ID:$
31 */
32 class Tx_Extbase_Persistence_Mapper_DataMapFactory implements t3lib_Singleton {
33
34 /**
35 * @var Tx_Extbase_Reflection_Service
36 */
37 protected $reflectionService;
38
39 /**
40 * @var Tx_Extbase_Configuration_ConfigurationManagerInterface
41 */
42 protected $configurationManager;
43
44 /**
45 * @var Tx_Extbase_Object_ObjectManagerInterface
46 */
47 protected $objectManager;
48
49
50
51 /**
52 * Injects the reflection service
53 *
54 * @param Tx_Extbase_Reflection_Service $reflectionService
55 * @return void
56 */
57 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
58 $this->reflectionService = $reflectionService;
59 }
60
61 /**
62 * @param Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager
63 * @return void
64 */
65 public function injectConfigurationManager(Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager) {
66 $this->configurationManager = $configurationManager;
67 }
68
69 /**
70 * @param Tx_Extbase_Object_ObjectManagerInterface $objectManager
71 * @return void
72 */
73 public function injectObjectManager(Tx_Extbase_Object_ObjectManagerInterface $objectManager) {
74 $this->objectManager = $objectManager;
75 }
76
77 /**
78 * Builds a data map by adding column maps for all the configured columns in the $TCA.
79 * It also resolves the type of values the column is holding and the typo of relation the column
80 * represents.
81 *
82 * @param string $className The class name you want to fetch the Data Map for
83 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
84 */
85 public function buildDataMap($className) {
86 if (!class_exists($className)) {
87 throw new Tx_Extbase_Persistence_Exception_InvalidClass('Could not find class definition for name "' . $className . '". This could be caused by a mis-spelling of the class name in the class definition.');
88 }
89
90 $recordType = NULL;
91 $subclasses = array();
92 $tableName = strtolower($className);
93 $columnMapping = array();
94
95 $frameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
96 $classSettings = $frameworkConfiguration['persistence']['classes'][$className];
97 if ($classSettings !== NULL) {
98 if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) {
99 $subclasses = $this->resolveSubclassesRecursive(
100 $frameworkConfiguration['persistence']['classes'], $classSettings['subclasses']
101 );
102 }
103 if (isset($classSettings['mapping']['recordType']) && strlen($classSettings['mapping']['recordType']) > 0) {
104 $recordType = $classSettings['mapping']['recordType'];
105 }
106 if (isset($classSettings['mapping']['tableName']) && strlen($classSettings['mapping']['tableName']) > 0) {
107 $tableName = $classSettings['mapping']['tableName'];
108 }
109 $classHierachy = array_merge(array($className), class_parents($className));
110 foreach ($classHierachy as $currentClassName) {
111 if (in_array($currentClassName, array('Tx_Extbase_DomainObject_AbstractEntity', 'Tx_Extbase_DomainObject_AbstractValueObject'))) {
112 break;
113 }
114 $currentClassSettings = $frameworkConfiguration['persistence']['classes'][$currentClassName];
115 if ($currentClassSettings !== NULL) {
116 if (isset($currentClassSettings['mapping']['columns']) && is_array($currentClassSettings['mapping']['columns'])) {
117 $columnMapping = t3lib_div::array_merge_recursive_overrule($columnMapping, $currentClassSettings['mapping']['columns'], 0, FALSE); // FALSE means: do not include empty values form 2nd array
118 }
119 }
120 }
121 }
122
123 $dataMap = $this->objectManager->create('Tx_Extbase_Persistence_Mapper_DataMap', $className, $tableName, $recordType, $subclasses);
124 $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName);
125
126 // $classPropertyNames = $this->reflectionService->getClassPropertyNames($className);
127 $tcaColumnsDefinition = $this->getColumnsDefinition($tableName);
128 $tcaColumnsDefinition = t3lib_div::array_merge_recursive_overrule($tcaColumnsDefinition, $columnMapping); // TODO Is this is too powerful?
129 foreach ($tcaColumnsDefinition as $columnName => $columnDefinition) {
130 if (isset($columnDefinition['mapOnProperty'])) {
131 $propertyName = $columnDefinition['mapOnProperty'];
132 } else {
133 $propertyName = t3lib_div::underscoredToLowerCamelCase($columnName);
134 }
135 // if (in_array($propertyName, $classPropertyNames)) { // TODO Enable check for property existance
136 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
137 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
138 $columnMap = $this->setRelations($columnMap, $columnDefinition['config'], $propertyMetaData);
139 $dataMap->addColumnMap($columnMap);
140 // }
141 }
142 // debug($dataMap);
143 return $dataMap;
144 }
145
146 /**
147 * Resolves all subclasses for the given set of (sub-)classes.
148 * The whole classes configuration is used to determine all subclasses recursively.
149 *
150 * @param array $classes The framework configuration part [persistence][classes].
151 * @param array $subclasses An array of subclasses defined via TypoScript
152 * @return array An numeric array that contains all available subclasses-strings as values.
153 */
154 protected function resolveSubclassesRecursive(array $classesConfiguration, array $subclasses) {
155 $allSubclasses = array();
156
157 foreach ($subclasses as $subclass) {
158 $allSubclasses[] = $subclass;
159 if (isset($classesConfiguration[$subclass]['subclasses']) && is_array($classesConfiguration[$subclass]['subclasses'])) {
160 $childSubclasses = $this->resolveSubclassesRecursive($classesConfiguration, $classesConfiguration[$subclass]['subclasses']);
161 $allSubclasses = array_merge($allSubclasses,$childSubclasses);
162 }
163 }
164
165 return $allSubclasses;
166 }
167
168 /**
169 * Returns the TCA ctrl section of the specified table; or NULL if not set
170 *
171 * @param string $tableName An optional table name to fetch the columns definition from
172 * @return array The TCA columns definition
173 */
174 protected function getControlSection($tableName) {
175 $this->includeTca($tableName);
176 return is_array($GLOBALS['TCA'][$tableName]['ctrl']) ? $GLOBALS['TCA'][$tableName]['ctrl'] : NULL;
177 }
178
179 /**
180 * Returns the TCA columns array of the specified table
181 *
182 * @param string $tableName An optional table name to fetch the columns definition from
183 * @return array The TCA columns definition
184 */
185 protected function getColumnsDefinition($tableName) {
186 $this->includeTca($tableName);
187 return is_array($GLOBALS['TCA'][$tableName]['columns']) ? $GLOBALS['TCA'][$tableName]['columns'] : array();
188 }
189
190 /**
191 * Includes the TCA for the given table
192 *
193 * @param string $tableName An optional table name to fetch the columns definition from
194 * @return void
195 */
196 protected function includeTca($tableName) {
197 if (TYPO3_MODE === 'FE') {
198 $GLOBALS['TSFE']->includeTCA();
199 }
200 t3lib_div::loadTCA($tableName);
201 }
202
203 protected function addMetaDataColumnNames(Tx_Extbase_Persistence_Mapper_DataMap $dataMap, $tableName) {
204 $controlSection = $GLOBALS['TCA'][$tableName]['ctrl'];
205 $dataMap->setPageIdColumnName('pid');
206 if (isset($controlSection['tstamp'])) $dataMap->setModificationDateColumnName($controlSection['tstamp']);
207 if (isset($controlSection['crdate'])) $dataMap->setCreationDateColumnName($controlSection['crdate']);
208 if (isset($controlSection['cruser_id'])) $dataMap->setCreatorColumnName($controlSection['cruser_id']);
209 if (isset($controlSection['delete'])) $dataMap->setDeletedFlagColumnName($controlSection['delete']);
210 if (isset($controlSection['languageField'])) $dataMap->setLanguageIdColumnName($controlSection['languageField']);
211 if (isset($controlSection['transOrigPointerField'])) $dataMap->setTranslationOriginColumnName($controlSection['transOrigPointerField']);
212 if (isset($controlSection['type'])) $dataMap->setRecordTypeColumnName($controlSection['type']);
213 if (isset($controlSection['enablecolumns']['disabled'])) $dataMap->setDisabledFlagColumnName($controlSection['enablecolumns']['disabled']);
214 if (isset($controlSection['enablecolumns']['starttime'])) $dataMap->setStartTimeColumnName($controlSection['enablecolumns']['starttime']);
215 if (isset($controlSection['enablecolumns']['endtime'])) $dataMap->setEndTimeColumnName($controlSection['enablecolumns']['endtime']);
216 if (isset($controlSection['enablecolumns']['fe_group'])) $dataMap->setFrontEndUserGroupColumnName($controlSection['enablecolumns']['fe_group']);
217 return $dataMap;
218 }
219
220 /**
221 * This method tries to determine the type of type of relation to other tables and sets it based on
222 * the $TCA column configuration
223 *
224 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
225 * @param string $columnConfiguration The column configuration from $TCA
226 * @param array $propertyMetaData The property metadata as delivered by the reflection service
227 * @return void
228 */
229 protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration, $propertyMetaData) {
230 if (isset($columnConfiguration)) {
231 if (isset($columnConfiguration['MM']) || isset($columnConfiguration['foreign_selector'])) {
232 $columnMap = $this->setManyToManyRelation($columnMap, $columnConfiguration);
233 } elseif (isset($propertyMetaData['elementType'])) {
234 $columnMap = $this->setOneToManyRelation($columnMap, $columnConfiguration);
235 } elseif (isset($propertyMetaData['type']) && strpos($propertyMetaData['type'], '_') !== FALSE) {
236 $columnMap = $this->setOneToOneRelation($columnMap, $columnConfiguration);
237 } else {
238 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
239 }
240 } else {
241 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
242 }
243 return $columnMap;
244 }
245
246 /**
247 * This method sets the configuration for a 1:1 relation based on
248 * the $TCA column configuration
249 *
250 * @param string $columnMap The column map
251 * @param string $columnConfiguration The column configuration from $TCA
252 * @return void
253 */
254 protected function setOneToOneRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) {
255 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE);
256 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
257 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
258 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
259 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
260 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
261 return $columnMap;
262 }
263
264 /**
265 * This method sets the configuration for a 1:n relation based on
266 * the $TCA column configuration
267 *
268 * @param string $columnMap The column map
269 * @param string $columnConfiguration The column configuration from $TCA
270 * @return void
271 */
272 protected function setOneToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) {
273 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY);
274 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
275 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
276 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
277 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
278 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
279 return $columnMap;
280 }
281
282 /**
283 * This method sets the configuration for a m:n relation based on
284 * the $TCA column configuration
285 *
286 * @param string $columnMap The column map
287 * @param string $columnConfiguration The column configuration from $TCA
288 * @return void
289 */
290 protected function setManyToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) {
291 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
292 if (isset($columnConfiguration['MM'])) {
293 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
294 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
295 $columnMap->setRelationTableName($columnConfiguration['MM']);
296 if (is_array($columnConfiguration['MM_match_fields'])) {
297 $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']);
298 }
299 if (is_array($columnConfiguration['MM_insert_fields'])) {
300 $columnMap->setRelationTableInsertFields($columnConfiguration['MM_insert_fields']);
301 }
302 $columnMap->setRelationTableWhereStatement($columnConfiguration['MM_table_where']);
303 if (!empty($columnConfiguration['MM_opposite_field'])) {
304 $columnMap->setParentKeyFieldName('uid_foreign');
305 $columnMap->setChildKeyFieldName('uid_local');
306 $columnMap->setChildSortByFieldName('sorting_foreign');
307 } else {
308 $columnMap->setParentKeyFieldName('uid_local');
309 $columnMap->setChildKeyFieldName('uid_foreign');
310 $columnMap->setChildSortByFieldName('sorting');
311 }
312 } elseif (isset($columnConfiguration['foreign_selector'])) {
313 $columns = $this->getColumnsDefinition($columnConfiguration['foreign_table']);
314 $childKeyFieldName = $columnConfiguration['foreign_selector'];
315 $columnMap->setChildTableName($columns[$childKeyFieldName]['config']['foreign_table']);
316 $columnMap->setRelationTableName($columnConfiguration['foreign_table']);
317 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
318 $columnMap->setChildKeyFieldName($childKeyFieldName);
319 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
320 } else {
321 throw new Tx_Extbase_Persistence_Exception_UnsupportedRelation('The given information to build a many-to-many-relation was not sufficient. Check your TCA definitions. mm-relations with IRRE must have at least a defined "MM" or "foreign_selector".', 1268817963);
322 }
323 if ($this->getControlSection($columnMap->getRelationTableName()) !== NULL) {
324 $columnMap->setRelationTablePageIdColumnName('pid');
325 }
326 return $columnMap;
327 }
328
329 }