[FEATURE][Performance] Add caches for persistence layer
[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 * @var t3lib_cache_Manager
51 */
52 protected $cacheManager;
53
54 /**
55 * @var t3lib_cache_frontend_VariableFrontend
56 */
57 protected $dataMapCache;
58
59
60
61 /**
62 * Injects the reflection service
63 *
64 * @param Tx_Extbase_Reflection_Service $reflectionService
65 * @return void
66 */
67 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
68 $this->reflectionService = $reflectionService;
69 }
70
71 /**
72 * @param Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager
73 * @return void
74 */
75 public function injectConfigurationManager(Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager) {
76 $this->configurationManager = $configurationManager;
77 }
78
79 /**
80 * @param Tx_Extbase_Object_ObjectManagerInterface $objectManager
81 * @return void
82 */
83 public function injectObjectManager(Tx_Extbase_Object_ObjectManagerInterface $objectManager) {
84 $this->objectManager = $objectManager;
85 }
86
87 /**
88 * @param t3lib_cache_Manager $cacheManager
89 */
90 public function injectCacheManager(t3lib_cache_Manager $cacheManager) {
91 $this->cacheManager = $cacheManager;
92 }
93
94 /**
95 * Lifecycle method
96 *
97 * @return void
98 */
99 public function initializeObject() {
100 $this->dataMapCache = $this->cacheManager->getCache('extbase_datamapfactory_datamap');
101 }
102
103 /**
104 * Builds a data map by adding column maps for all the configured columns in the $TCA.
105 * It also resolves the type of values the column is holding and the typo of relation the column
106 * represents.
107 *
108 * @param string $className The class name you want to fetch the Data Map for
109 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
110 */
111 public function buildDataMap($className) {
112 $dataMap = $this->dataMapCache->get($className);
113 if ($dataMap === FALSE) {
114 $dataMap = $this->buildDataMapInternal($className);
115 $this->dataMapCache->set($className, $dataMap);
116 }
117 return $dataMap;
118 }
119
120 /**
121 * Builds a data map by adding column maps for all the configured columns in the $TCA.
122 * It also resolves the type of values the column is holding and the typo of relation the column
123 * represents.
124 *
125 * @param string $className The class name you want to fetch the Data Map for
126 * @throws Tx_Extbase_Persistence_Exception_InvalidClass
127 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
128 */
129 protected function buildDataMapInternal($className) {
130 if (!class_exists($className)) {
131 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.');
132 }
133
134 $recordType = NULL;
135 $subclasses = array();
136 $tableName = strtolower($className);
137 $columnMapping = array();
138
139 $frameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
140 $classSettings = $frameworkConfiguration['persistence']['classes'][$className];
141 if ($classSettings !== NULL) {
142 if (isset($classSettings['subclasses']) && is_array($classSettings['subclasses'])) {
143 $subclasses = $this->resolveSubclassesRecursive(
144 $frameworkConfiguration['persistence']['classes'], $classSettings['subclasses']
145 );
146 }
147 if (isset($classSettings['mapping']['recordType']) && strlen($classSettings['mapping']['recordType']) > 0) {
148 $recordType = $classSettings['mapping']['recordType'];
149 }
150 if (isset($classSettings['mapping']['tableName']) && strlen($classSettings['mapping']['tableName']) > 0) {
151 $tableName = $classSettings['mapping']['tableName'];
152 }
153 $classHierarchy = array_merge(array($className), class_parents($className));
154 foreach ($classHierarchy as $currentClassName) {
155 if (in_array($currentClassName, array('Tx_Extbase_DomainObject_AbstractEntity', 'Tx_Extbase_DomainObject_AbstractValueObject'))) {
156 break;
157 }
158 $currentClassSettings = $frameworkConfiguration['persistence']['classes'][$currentClassName];
159 if ($currentClassSettings !== NULL) {
160 if (isset($currentClassSettings['mapping']['columns']) && is_array($currentClassSettings['mapping']['columns'])) {
161 $columnMapping = t3lib_div::array_merge_recursive_overrule($columnMapping, $currentClassSettings['mapping']['columns'], 0, FALSE); // FALSE means: do not include empty values form 2nd array
162 }
163 }
164 }
165 }
166
167 $dataMap = $this->objectManager->create('Tx_Extbase_Persistence_Mapper_DataMap', $className, $tableName, $recordType, $subclasses);
168 $dataMap = $this->addMetaDataColumnNames($dataMap, $tableName);
169
170 // $classPropertyNames = $this->reflectionService->getClassPropertyNames($className);
171 $tcaColumnsDefinition = $this->getColumnsDefinition($tableName);
172 $tcaColumnsDefinition = t3lib_div::array_merge_recursive_overrule($tcaColumnsDefinition, $columnMapping); // TODO Is this is too powerful?
173 foreach ($tcaColumnsDefinition as $columnName => $columnDefinition) {
174 if (isset($columnDefinition['mapOnProperty'])) {
175 $propertyName = $columnDefinition['mapOnProperty'];
176 } else {
177 $propertyName = t3lib_div::underscoredToLowerCamelCase($columnName);
178 }
179 // if (in_array($propertyName, $classPropertyNames)) { // TODO Enable check for property existance
180 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
181 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
182 $columnMap = $this->setRelations($columnMap, $columnDefinition['config'], $propertyMetaData);
183 $dataMap->addColumnMap($columnMap);
184 // }
185 }
186 // debug($dataMap);
187 return $dataMap;
188 }
189
190 /**
191 * Resolves all subclasses for the given set of (sub-)classes.
192 * The whole classes configuration is used to determine all subclasses recursively.
193 *
194 * @param array $classes The framework configuration part [persistence][classes].
195 * @param array $subclasses An array of subclasses defined via TypoScript
196 * @return array An numeric array that contains all available subclasses-strings as values.
197 */
198 protected function resolveSubclassesRecursive(array $classesConfiguration, array $subclasses) {
199 $allSubclasses = array();
200
201 foreach ($subclasses as $subclass) {
202 $allSubclasses[] = $subclass;
203 if (isset($classesConfiguration[$subclass]['subclasses']) && is_array($classesConfiguration[$subclass]['subclasses'])) {
204 $childSubclasses = $this->resolveSubclassesRecursive($classesConfiguration, $classesConfiguration[$subclass]['subclasses']);
205 $allSubclasses = array_merge($allSubclasses,$childSubclasses);
206 }
207 }
208
209 return $allSubclasses;
210 }
211
212 /**
213 * Returns the TCA ctrl section of the specified table; or NULL if not set
214 *
215 * @param string $tableName An optional table name to fetch the columns definition from
216 * @return array The TCA columns definition
217 */
218 protected function getControlSection($tableName) {
219 $this->includeTca($tableName);
220 return is_array($GLOBALS['TCA'][$tableName]['ctrl']) ? $GLOBALS['TCA'][$tableName]['ctrl'] : NULL;
221 }
222
223 /**
224 * Returns the TCA columns array of the specified table
225 *
226 * @param string $tableName An optional table name to fetch the columns definition from
227 * @return array The TCA columns definition
228 */
229 protected function getColumnsDefinition($tableName) {
230 $this->includeTca($tableName);
231 return is_array($GLOBALS['TCA'][$tableName]['columns']) ? $GLOBALS['TCA'][$tableName]['columns'] : array();
232 }
233
234 /**
235 * Includes the TCA for the given table
236 *
237 * @param string $tableName An optional table name to fetch the columns definition from
238 * @return void
239 */
240 protected function includeTca($tableName) {
241 if (TYPO3_MODE === 'FE') {
242 $GLOBALS['TSFE']->includeTCA();
243 }
244 t3lib_div::loadTCA($tableName);
245 }
246
247 protected function addMetaDataColumnNames(Tx_Extbase_Persistence_Mapper_DataMap $dataMap, $tableName) {
248 $controlSection = $GLOBALS['TCA'][$tableName]['ctrl'];
249 $dataMap->setPageIdColumnName('pid');
250 if (isset($controlSection['tstamp'])) $dataMap->setModificationDateColumnName($controlSection['tstamp']);
251 if (isset($controlSection['crdate'])) $dataMap->setCreationDateColumnName($controlSection['crdate']);
252 if (isset($controlSection['cruser_id'])) $dataMap->setCreatorColumnName($controlSection['cruser_id']);
253 if (isset($controlSection['delete'])) $dataMap->setDeletedFlagColumnName($controlSection['delete']);
254 if (isset($controlSection['languageField'])) $dataMap->setLanguageIdColumnName($controlSection['languageField']);
255 if (isset($controlSection['transOrigPointerField'])) $dataMap->setTranslationOriginColumnName($controlSection['transOrigPointerField']);
256 if (isset($controlSection['type'])) $dataMap->setRecordTypeColumnName($controlSection['type']);
257 if (isset($controlSection['enablecolumns']['disabled'])) $dataMap->setDisabledFlagColumnName($controlSection['enablecolumns']['disabled']);
258 if (isset($controlSection['enablecolumns']['starttime'])) $dataMap->setStartTimeColumnName($controlSection['enablecolumns']['starttime']);
259 if (isset($controlSection['enablecolumns']['endtime'])) $dataMap->setEndTimeColumnName($controlSection['enablecolumns']['endtime']);
260 if (isset($controlSection['enablecolumns']['fe_group'])) $dataMap->setFrontEndUserGroupColumnName($controlSection['enablecolumns']['fe_group']);
261 return $dataMap;
262 }
263
264 /**
265 * This method tries to determine the type of type of relation to other tables and sets it based on
266 * the $TCA column configuration
267 *
268 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
269 * @param string $columnConfiguration The column configuration from $TCA
270 * @param array $propertyMetaData The property metadata as delivered by the reflection service
271 * @return void
272 */
273 protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration, $propertyMetaData) {
274 if (isset($columnConfiguration)) {
275 if (isset($columnConfiguration['MM']) || isset($columnConfiguration['foreign_selector'])) {
276 $columnMap = $this->setManyToManyRelation($columnMap, $columnConfiguration);
277 } elseif (isset($propertyMetaData['elementType'])) {
278 $columnMap = $this->setOneToManyRelation($columnMap, $columnConfiguration);
279 } elseif (isset($propertyMetaData['type']) && strpos($propertyMetaData['type'], '_') !== FALSE) {
280 $columnMap = $this->setOneToOneRelation($columnMap, $columnConfiguration);
281 } else {
282 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
283 }
284 } else {
285 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
286 }
287 return $columnMap;
288 }
289
290 /**
291 * This method sets the configuration for a 1:1 relation based on
292 * the $TCA column configuration
293 *
294 * @param string $columnMap The column map
295 * @param string $columnConfiguration The column configuration from $TCA
296 * @return void
297 */
298 protected function setOneToOneRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) {
299 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE);
300 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
301 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
302 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
303 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
304 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
305 return $columnMap;
306 }
307
308 /**
309 * This method sets the configuration for a 1:n relation based on
310 * the $TCA column configuration
311 *
312 * @param string $columnMap The column map
313 * @param string $columnConfiguration The column configuration from $TCA
314 * @return void
315 */
316 protected function setOneToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) {
317 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY);
318 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
319 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
320 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
321 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
322 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
323 return $columnMap;
324 }
325
326 /**
327 * This method sets the configuration for a m:n relation based on
328 * the $TCA column configuration
329 *
330 * @param string $columnMap The column map
331 * @param string $columnConfiguration The column configuration from $TCA
332 * @return void
333 */
334 protected function setManyToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap, $columnConfiguration) {
335 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
336 if (isset($columnConfiguration['MM'])) {
337 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
338 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
339 $columnMap->setRelationTableName($columnConfiguration['MM']);
340 if (is_array($columnConfiguration['MM_match_fields'])) {
341 $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']);
342 }
343 if (is_array($columnConfiguration['MM_insert_fields'])) {
344 $columnMap->setRelationTableInsertFields($columnConfiguration['MM_insert_fields']);
345 }
346 $columnMap->setRelationTableWhereStatement($columnConfiguration['MM_table_where']);
347 if (!empty($columnConfiguration['MM_opposite_field'])) {
348 $columnMap->setParentKeyFieldName('uid_foreign');
349 $columnMap->setChildKeyFieldName('uid_local');
350 $columnMap->setChildSortByFieldName('sorting_foreign');
351 } else {
352 $columnMap->setParentKeyFieldName('uid_local');
353 $columnMap->setChildKeyFieldName('uid_foreign');
354 $columnMap->setChildSortByFieldName('sorting');
355 }
356 } elseif (isset($columnConfiguration['foreign_selector'])) {
357 $columns = $this->getColumnsDefinition($columnConfiguration['foreign_table']);
358 $childKeyFieldName = $columnConfiguration['foreign_selector'];
359 $columnMap->setChildTableName($columns[$childKeyFieldName]['config']['foreign_table']);
360 $columnMap->setRelationTableName($columnConfiguration['foreign_table']);
361 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
362 $columnMap->setChildKeyFieldName($childKeyFieldName);
363 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
364 } else {
365 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);
366 }
367 if ($this->getControlSection($columnMap->getRelationTableName()) !== NULL) {
368 $columnMap->setRelationTablePageIdColumnName('pid');
369 }
370 return $columnMap;
371 }
372
373 }