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