d052ae8ad070f84b38839180be84e6a112f557b0
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Mapper / DataMapFactory.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic\Mapper;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2010-2013 Extbase Team (http://forge.typo3.org/projects/typo3v4-mvc)
8 * Extbase is a backport of TYPO3 Flow. All credits go to the TYPO3 Flow team.
9 * All rights reserved
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 * A copy is found in the textfile GPL.txt and important notices to the license
20 * from the author is found in LICENSE.txt distributed with these scripts.
21 *
22 *
23 * This script is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * This copyright notice MUST APPEAR in all copies of the script!
29 ***************************************************************/
30 /**
31 * A factory for a data map to map a single table configured in $TCA on a domain object.
32 */
33 class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface {
34
35 /**
36 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
37 */
38 protected $reflectionService;
39
40 /**
41 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
42 */
43 protected $configurationManager;
44
45 /**
46 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
47 */
48 protected $objectManager;
49
50 /**
51 * @var \TYPO3\CMS\Core\Cache\CacheManager
52 */
53 protected $cacheManager;
54
55 /**
56 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
57 */
58 protected $dataMapCache;
59
60 /**
61 * Injects the reflection service
62 *
63 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
64 * @return void
65 */
66 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService) {
67 $this->reflectionService = $reflectionService;
68 }
69
70 /**
71 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
72 * @return void
73 */
74 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager) {
75 $this->configurationManager = $configurationManager;
76 }
77
78 /**
79 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
80 * @return void
81 */
82 public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager) {
83 $this->objectManager = $objectManager;
84 }
85
86 /**
87 * @param \TYPO3\CMS\Core\Cache\CacheManager $cacheManager
88 */
89 public function injectCacheManager(\TYPO3\CMS\Core\Cache\CacheManager $cacheManager) {
90 $this->cacheManager = $cacheManager;
91 }
92
93 /**
94 * Lifecycle method
95 *
96 * @return void
97 */
98 public function initializeObject() {
99 $this->dataMapCache = $this->cacheManager->getCache('extbase_datamapfactory_datamap');
100 }
101
102 /**
103 * Builds a data map by adding column maps for all the configured columns in the $TCA.
104 * It also resolves the type of values the column is holding and the typo of relation the column
105 * represents.
106 *
107 * @param string $className The class name you want to fetch the Data Map for
108 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap The data map
109 */
110 public function buildDataMap($className) {
111 $dataMap = $this->dataMapCache->get(str_replace('\\', '%', $className));
112 if ($dataMap === FALSE) {
113 $dataMap = $this->buildDataMapInternal($className);
114 $this->dataMapCache->set(str_replace('\\', '%', $className), $dataMap);
115 }
116 return $dataMap;
117 }
118
119 /**
120 * Builds a data map by adding column maps for all the configured columns in the $TCA.
121 * It also resolves the type of values the column is holding and the typo of relation the column
122 * represents.
123 *
124 * @param string $className The class name you want to fetch the Data Map for
125 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidClassException
126 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap The data map
127 */
128 protected function buildDataMapInternal($className) {
129 if (!class_exists($className)) {
130 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidClassException('Could not find class definition for name "' . $className . '". This could be caused by a mis-spelling of the class name in the class definition.');
131 }
132 $recordType = NULL;
133 $subclasses = array();
134 $tableName = $this->resolveTableName($className);
135 $columnMapping = array();
136 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
137 $classSettings = $frameworkConfiguration['persistence']['classes'][$className];
138 if ($classSettings !== NULL) {
139 if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) {
140 $subclasses = $this->resolveSubclassesRecursive($frameworkConfiguration['persistence']['classes'], $classSettings['subclasses']);
141 }
142 if (isset($classSettings['mapping']['recordType']) && strlen($classSettings['mapping']['recordType']) > 0) {
143 $recordType = $classSettings['mapping']['recordType'];
144 }
145 if (isset($classSettings['mapping']['tableName']) && strlen($classSettings['mapping']['tableName']) > 0) {
146 $tableName = $classSettings['mapping']['tableName'];
147 }
148 $classHierarchy = array_merge(array($className), class_parents($className));
149 foreach ($classHierarchy as $currentClassName) {
150 if (in_array($currentClassName, array('TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity', 'TYPO3\\CMS\\Extbase\\DomainObject\\AbstractValueObject'))) {
151 break;
152 }
153 $currentClassSettings = $frameworkConfiguration['persistence']['classes'][$currentClassName];
154 if ($currentClassSettings !== NULL) {
155 if (isset($currentClassSettings['mapping']['columns']) && is_array($currentClassSettings['mapping']['columns'])) {
156 $columnMapping = \TYPO3\CMS\Core\Utility\GeneralUtility::array_merge_recursive_overrule($columnMapping, $currentClassSettings['mapping']['columns'], 0, FALSE);
157 }
158 }
159 }
160 }
161 /** @var $dataMap \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap */
162 $dataMap = $this->objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Mapper\\DataMap', $className, $tableName, $recordType, $subclasses);
163 $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName);
164 // $classPropertyNames = $this->reflectionService->getClassPropertyNames($className);
165 $tcaColumnsDefinition = $this->getColumnsDefinition($tableName);
166 $tcaColumnsDefinition = \TYPO3\CMS\Core\Utility\GeneralUtility::array_merge_recursive_overrule($tcaColumnsDefinition, $columnMapping);
167 // TODO Is this is too powerful?
168
169 foreach ($tcaColumnsDefinition as $columnName => $columnDefinition) {
170 if (isset($columnDefinition['mapOnProperty'])) {
171 $propertyName = $columnDefinition['mapOnProperty'];
172 } else {
173 $propertyName = \TYPO3\CMS\Core\Utility\GeneralUtility::underscoredToLowerCamelCase($columnName);
174 }
175 // if (in_array($propertyName, $classPropertyNames)) { // TODO Enable check for property existance
176 $columnMap = $this->createColumnMap($columnName, $propertyName);
177 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
178 $columnMap = $this->setRelations($columnMap, $columnDefinition['config'], $propertyMetaData);
179 $columnMap = $this->setFieldEvaluations($columnMap, $columnDefinition['config']);
180 $dataMap->addColumnMap($columnMap);
181 }
182 return $dataMap;
183 }
184
185 /**
186 * Resolve the table name for the given class name
187 *
188 * @param string $className
189 * @return string The table name
190 */
191 protected function resolveTableName($className) {
192 if (strpos($className, '\\') !== FALSE) {
193 $classNameParts = explode('\\', $className, 6);
194 // Skip vendor and product name for core classes
195 if (strpos($className, 'TYPO3\\CMS\\') === 0) {
196 $classPartsToSkip = 2;
197 } else {
198 $classPartsToSkip = 1;
199 }
200 $tableName = 'tx_' . strtolower(implode('_', array_slice($classNameParts, $classPartsToSkip)));
201 } else {
202 $tableName = strtolower($className);
203 }
204 return $tableName;
205 }
206
207 /**
208 * Resolves all subclasses for the given set of (sub-)classes.
209 * The whole classes configuration is used to determine all subclasses recursively.
210 *
211 * @param array $classesConfiguration The framework configuration part [persistence][classes].
212 * @param array $subclasses An array of subclasses defined via TypoScript
213 * @return array An numeric array that contains all available subclasses-strings as values.
214 */
215 protected function resolveSubclassesRecursive(array $classesConfiguration, array $subclasses) {
216 $allSubclasses = array();
217 foreach ($subclasses as $subclass) {
218 $allSubclasses[] = $subclass;
219 if (isset($classesConfiguration[$subclass]['subclasses']) && is_array($classesConfiguration[$subclass]['subclasses'])) {
220 $childSubclasses = $this->resolveSubclassesRecursive($classesConfiguration, $classesConfiguration[$subclass]['subclasses']);
221 $allSubclasses = array_merge($allSubclasses, $childSubclasses);
222 }
223 }
224 return $allSubclasses;
225 }
226
227 /**
228 * Returns the TCA ctrl section of the specified table; or NULL if not set
229 *
230 * @param string $tableName An optional table name to fetch the columns definition from
231 * @return array The TCA columns definition
232 */
233 protected function getControlSection($tableName) {
234 return is_array($GLOBALS['TCA'][$tableName]['ctrl']) ? $GLOBALS['TCA'][$tableName]['ctrl'] : NULL;
235 }
236
237 /**
238 * Returns the TCA columns array of the specified table
239 *
240 * @param string $tableName An optional table name to fetch the columns definition from
241 * @return array The TCA columns definition
242 */
243 protected function getColumnsDefinition($tableName) {
244 return is_array($GLOBALS['TCA'][$tableName]['columns']) ? $GLOBALS['TCA'][$tableName]['columns'] : array();
245 }
246
247 /**
248 * @param DataMap $dataMap
249 * @param string $tableName
250 * @return DataMap
251 */
252 protected function addMetaDataColumnNames(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap $dataMap, $tableName) {
253 $controlSection = $GLOBALS['TCA'][$tableName]['ctrl'];
254 $dataMap->setPageIdColumnName('pid');
255 if (isset($controlSection['tstamp'])) {
256 $dataMap->setModificationDateColumnName($controlSection['tstamp']);
257 }
258 if (isset($controlSection['crdate'])) {
259 $dataMap->setCreationDateColumnName($controlSection['crdate']);
260 }
261 if (isset($controlSection['cruser_id'])) {
262 $dataMap->setCreatorColumnName($controlSection['cruser_id']);
263 }
264 if (isset($controlSection['delete'])) {
265 $dataMap->setDeletedFlagColumnName($controlSection['delete']);
266 }
267 if (isset($controlSection['languageField'])) {
268 $dataMap->setLanguageIdColumnName($controlSection['languageField']);
269 }
270 if (isset($controlSection['transOrigPointerField'])) {
271 $dataMap->setTranslationOriginColumnName($controlSection['transOrigPointerField']);
272 }
273 if (isset($controlSection['type'])) {
274 $dataMap->setRecordTypeColumnName($controlSection['type']);
275 }
276 if (isset($controlSection['rootLevel'])) {
277 $dataMap->setRootLevel($controlSection['rootLevel']);
278 }
279 if (isset($controlSection['is_static'])) {
280 $dataMap->setIsStatic($controlSection['is_static']);
281 }
282 if (isset($controlSection['enablecolumns']['disabled'])) {
283 $dataMap->setDisabledFlagColumnName($controlSection['enablecolumns']['disabled']);
284 }
285 if (isset($controlSection['enablecolumns']['starttime'])) {
286 $dataMap->setStartTimeColumnName($controlSection['enablecolumns']['starttime']);
287 }
288 if (isset($controlSection['enablecolumns']['endtime'])) {
289 $dataMap->setEndTimeColumnName($controlSection['enablecolumns']['endtime']);
290 }
291 if (isset($controlSection['enablecolumns']['fe_group'])) {
292 $dataMap->setFrontEndUserGroupColumnName($controlSection['enablecolumns']['fe_group']);
293 }
294 return $dataMap;
295 }
296
297 /**
298 * This method tries to determine the type of type of relation to other tables and sets it based on
299 * the $TCA column configuration
300 *
301 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
302 * @param string $columnConfiguration The column configuration from $TCA
303 * @param array $propertyMetaData The property metadata as delivered by the reflection service
304 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
305 */
306 protected function setRelations(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration, $propertyMetaData) {
307 if (isset($columnConfiguration)) {
308 if (isset($columnConfiguration['MM'])) {
309 $columnMap = $this->setManyToManyRelation($columnMap, $columnConfiguration);
310 } elseif (isset($propertyMetaData['elementType'])) {
311 $columnMap = $this->setOneToManyRelation($columnMap, $columnConfiguration);
312 } elseif (isset($propertyMetaData['type']) && strpbrk($propertyMetaData['type'], '_\\') !== FALSE) {
313 $columnMap = $this->setOneToOneRelation($columnMap, $columnConfiguration);
314 } else {
315 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_NONE);
316 }
317
318 } else {
319 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_NONE);
320 }
321 return $columnMap;
322 }
323
324 /**
325 * Sets field evaluations based on $TCA column configuration.
326 *
327 * @param ColumnMap $columnMap The column map
328 * @param NULL|array $columnConfiguration The column configuration from $TCA
329 * @return ColumnMap
330 */
331 protected function setFieldEvaluations(ColumnMap $columnMap, array $columnConfiguration = NULL) {
332 if (!empty($columnConfiguration['eval'])) {
333 $fieldEvaluations = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $columnConfiguration['eval'], TRUE);
334 $dateTimeEvaluations = array('date', 'datetime');
335
336 if (count(array_intersect($dateTimeEvaluations, $fieldEvaluations)) > 0 && !empty($columnConfiguration['dbType'])) {
337 $columnMap->setDateTimeStorageFormat($columnConfiguration['dbType']);
338 }
339 }
340
341 return $columnMap;
342 }
343
344 /**
345 * This method sets the configuration for a 1:1 relation based on
346 * the $TCA column configuration
347 *
348 * @param string|\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
349 * @param string $columnConfiguration The column configuration from $TCA
350 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
351 */
352 protected function setOneToOneRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration) {
353 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_ONE);
354 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
355 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
356 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
357 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
358 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
359 if (is_array($columnConfiguration['foreign_match_fields'])) {
360 $columnMap->setRelationTableMatchFields($columnConfiguration['foreign_match_fields']);
361 }
362 return $columnMap;
363 }
364
365 /**
366 * This method sets the configuration for a 1:n relation based on
367 * the $TCA column configuration
368 *
369 * @param string|\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
370 * @param string $columnConfiguration The column configuration from $TCA
371 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
372 */
373 protected function setOneToManyRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration) {
374 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY);
375 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
376 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
377 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
378 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
379 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
380 if (is_array($columnConfiguration['foreign_match_fields'])) {
381 $columnMap->setRelationTableMatchFields($columnConfiguration['foreign_match_fields']);
382 }
383 return $columnMap;
384 }
385
386 /**
387 * This method sets the configuration for a m:n relation based on
388 * the $TCA column configuration
389 *
390 * @param string|\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap The column map
391 * @param string $columnConfiguration The column configuration from $TCA
392 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedRelationException
393 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
394 */
395 protected function setManyToManyRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap, $columnConfiguration) {
396 if (isset($columnConfiguration['MM'])) {
397 $columnMap->setTypeOfRelation(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
398 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
399 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
400 $columnMap->setRelationTableName($columnConfiguration['MM']);
401 if (is_array($columnConfiguration['MM_match_fields'])) {
402 $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']);
403 }
404 if (is_array($columnConfiguration['MM_insert_fields'])) {
405 $columnMap->setRelationTableInsertFields($columnConfiguration['MM_insert_fields']);
406 }
407 $columnMap->setRelationTableWhereStatement($columnConfiguration['MM_table_where']);
408 if (!empty($columnConfiguration['MM_opposite_field'])) {
409 $columnMap->setParentKeyFieldName('uid_foreign');
410 $columnMap->setChildKeyFieldName('uid_local');
411 $columnMap->setChildSortByFieldName('sorting_foreign');
412 } else {
413 $columnMap->setParentKeyFieldName('uid_local');
414 $columnMap->setChildKeyFieldName('uid_foreign');
415 $columnMap->setChildSortByFieldName('sorting');
416 }
417 } else {
418 throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedRelationException('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);
419 }
420 if ($this->getControlSection($columnMap->getRelationTableName()) !== NULL) {
421 $columnMap->setRelationTablePageIdColumnName('pid');
422 }
423 return $columnMap;
424 }
425
426 /**
427 * Creates the ColumnMap object for the given columnName and propertyName
428 *
429 * @param string $columnName
430 * @param string $propertyName
431 *
432 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
433 */
434 protected function createColumnMap($columnName, $propertyName) {
435 return $this->objectManager->get('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Mapper\\ColumnMap', $columnName, $propertyName);
436 }
437
438 }
439
440 ?>