[~TASK] Extbase: Refactored Dispatcher. Moved initialization of cache and reflection...
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Mapper / DataMap.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 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_DataMap {
33 /**
34 * The domain class name
35 *
36 * @var string
37 **/
38 protected $className;
39
40 /**
41 * The table name corresponding to the domain class configured in $TCA
42 *
43 * @var string
44 **/
45 protected $tableName;
46
47 /**
48 * An array of column maps configured in $TCA
49 *
50 * @var array
51 **/
52 protected $columnMaps;
53
54 /**
55 * Constructs this DataMap
56 *
57 * @param string $className The class name. This determines the table to fetch the configuration for
58 */
59 // TODO Refactor to factory pattern (DataMapFactory) and value object (DataMap)
60 public function __construct($className, $tableName = '', array $mapping = array()) {
61 $this->setClassName($className);
62 if (empty($tableName)) {
63 $this->setTableName(strtolower($className));
64 } else {
65 $this->setTableName($tableName);
66 }
67 $this->initialize($mapping);
68 }
69
70 /**
71 * Sets the name of the class the colum map represents
72 *
73 * @return void
74 */
75 public function setClassName($className) {
76 $this->className = $className;
77 }
78
79 /**
80 * Returns the name of the class the column map represents
81 *
82 * @return string The class name
83 */
84 public function getClassName() {
85 return $this->className;
86 }
87
88 /**
89 * Sets the name of the table the colum map represents
90 *
91 * @return void
92 */
93 public function setTableName($tableName) {
94 $this->tableName = $tableName;
95 }
96
97 /**
98 * Returns the name of the table the column map represents
99 *
100 * @return string The table name
101 */
102 public function getTableName() {
103 return $this->tableName;
104 }
105
106 /**
107 * Initializes the data map by adding column maps for all the configured columns in the $TCA.
108 * It also resolves the type of values the column is holding and the typo of relation the column
109 * represents.
110 *
111 * @return void
112 */
113 protected function initialize(array $mapping) {
114 if (TYPO3_MODE === 'FE') {
115 $GLOBALS['TSFE']->includeTCA();
116 }
117 t3lib_div::loadTCA($this->getTableName());
118 $columns = $GLOBALS['TCA'][$this->getTableName()]['columns'];
119 $this->addCommonColumns();
120 if (is_array($columns)) {
121 foreach ($columns as $columnName => $columnDefinition) {
122 $columnConfiguration = $columnDefinition['config'];
123 if (!empty($mapping[$columnName]['mapOnProperty'])) {
124 $propertyName = $mapping[$columnName]['mapOnProperty'];
125 } else {
126 $propertyName = Tx_Extbase_Utility_Extension::convertUnderscoredToLowerCamelCase($columnName);
127 }
128 if (isset($mapping[$columnName]['foreignClass']) && !isset($columnConfiguration['foreign_class'])) {
129 $columnConfiguration['foreign_class'] = $mapping[$columnName]['foreignClass'];
130 }
131 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
132 $this->setPropertyType($columnMap, $columnConfiguration);
133 $this->setRelations($columnMap, $columnConfiguration);
134 $this->addColumnMap($columnMap);
135 }
136 }
137 }
138
139 /**
140 * Adds available common columns (e.g. tstamp or crdate) to the data map. It takes the configured column names
141 * into account.
142 *
143 * @return void
144 */
145 protected function addCommonColumns() {
146 // TODO Decide whether we should add pid and uid columns by default
147 $this->addColumn('uid', NULL, Tx_Extbase_Persistence_PropertyType::LONG);
148 if ($this->hasPidColumn()) {
149 $this->addColumn('pid', NULL, Tx_Extbase_Persistence_PropertyType::LONG);
150 }
151 if ($this->hasTimestampColumn()) {
152 $this->addColumn($this->getTimestampColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::DATE);
153 }
154 if ($this->hasCreationDateColumn()) {
155 $this->addColumn($this->getCreationDateColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::DATE);
156 }
157 if ($this->hasCreatorUidColumn()) {
158 $this->addColumn($this->getCreatorUidColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::LONG);
159 }
160 if ($this->hasDeletedColumn()) {
161 $this->addColumn($this->getDeletedColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::BOOLEAN);
162 }
163 if ($this->hasHiddenColumn()) {
164 $this->addColumn($this->getHiddenColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::BOOLEAN);
165 }
166 }
167
168 /**
169 * This method tries to determine the type of value the column hold by inspectiong the $TCA column configuration
170 * and sets it.
171 *
172 * @param string $columnMap The column map
173 * @param string $columnConfiguration The column configuration from $TCA
174 * @return void
175 */
176 protected function setPropertyType(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
177 $evalConfiguration = t3lib_div::trimExplode(',', $columnConfiguration['eval']);
178 if (in_array('date', $evalConfiguration) || in_array('datetime', $evalConfiguration)) {
179 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DATE);
180 } elseif ($columnConfiguration['type'] === 'check' && empty($columnConfiguration['items'])) {
181 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::BOOLEAN);
182 } elseif (in_array('int', $evalConfiguration)) {
183 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::LONG);
184 } elseif (in_array('double2', $evalConfiguration)) {
185 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DOUBLE);
186 } else {
187 if (isset($columnConfiguration['foreign_table'])) {
188 if (isset($columnConfiguration['loadingStrategy'])) {
189 $columnMap->setLoadingStrategy($columnConfiguration['loadingStrategy']);
190 } else {
191 $columnMap->setLoadingStrategy(Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_EAGER);
192 }
193 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::REFERENCE);
194 } else {
195 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::STRING);
196 }
197 }
198 }
199
200 /**
201 * This method tries to determine the type of type of relation to other tables and sets it based on
202 * the $TCA column configuration
203 *
204 * @param string $columnMap The column map
205 * @param string $columnConfiguration The column configuration from $TCA
206 * @return void
207 */
208 protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
209 if (isset($columnConfiguration) && $columnConfiguration['type'] !== 'passthrough') {
210 if (isset($columnConfiguration['foreign_table']) && !isset($columnConfiguration['MM']) && !isset($columnConfiguration['foreign_label'])) {
211 if ($columnConfiguration['maxitems'] == 1) {
212 $this->setOneToOneRelation($columnMap, $columnConfiguration);
213 } else {
214 $this->setOneToManyRelation($columnMap, $columnConfiguration);
215 }
216 } elseif ($columnConfiguration['type'] === 'inline' && isset($columnConfiguration['foreign_table']) && isset($columnConfiguration['foreign_label'])) {
217 $this->setManyToManyRelation($columnMap, $columnConfiguration);
218 } elseif ($columnConfiguration['type'] !== 'inline' && isset($columnConfiguration['MM'])) {
219 // TODO support for MM_insert_fields and MM_match_fields
220 $this->setManyToManyRelation($columnMap, $columnConfiguration);
221 } else {
222 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
223 }
224 }
225 }
226
227 /**
228 * This method sets the configuration for a 1:1 relation based on
229 * the $TCA column configuration
230 *
231 * @param string $columnMap The column map
232 * @param string $columnConfiguration The column configuration from $TCA
233 * @return void
234 */
235 protected function setOneToOneRelation(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
236 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE);
237 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
238 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
239 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
240 $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']);
241 $columnMap->setDeleteChildObjectsState($columnConfiguration['deleteRelationsWithParent']);
242 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
243 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
244 }
245
246 /**
247 * This method sets the configuration for a 1:n 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 setOneToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
255 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY);
256 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
257 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
258 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
259 $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']);
260 $columnMap->setDeleteChildObjectsState($columnConfiguration['deleteRelationsWithParent']);
261 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
262 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
263 }
264
265 /**
266 * This method sets the configuration for a m:n relation based on
267 * the $TCA column configuration
268 *
269 * @param string $columnMap The column map
270 * @param string $columnConfiguration The column configuration from $TCA
271 * @return void
272 */
273 protected function setManyToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
274 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
275 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
276 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
277 $columnMap->setRelationTableName($columnConfiguration['MM']);
278 if (is_array($columnConfiguration['MM_match_fields'])) {
279 $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']);
280 }
281 $columnMap->setRelationTableWhereStatement($columnConfiguration['MM_table_where']);
282 // TODO We currently do not support multi table relationships
283 if ($columnConfiguration['MM_opposite_field']) {
284 $columnMap->setParentKeyFieldName('uid_foreign');
285 $columnMap->setChildKeyFieldName('uid_local');
286 $columnMap->setChildSortByFieldName('sorting_foreign');
287 } else {
288 $columnMap->setParentKeyFieldName('uid_local');
289 $columnMap->setChildKeyFieldName('uid_foreign');
290 $columnMap->setChildSortByFieldName('sorting');
291 }
292 }
293
294 /**
295 * This function determines the child class name. It can either be defined as foreign_class in the column configuration (TCA)
296 * or it must be defined in the extbase framework configuration (reverse mapping from tableName to className).
297 *
298 * @param $columnConfiguration The column configuration (from TCA)
299 * @return string The class name of the related child object
300 */
301 protected function determineChildClassName($columnConfiguration) {
302 $foreignClassName = '';
303 if (is_string($columnConfiguration['foreign_class']) && (strlen($columnConfiguration['foreign_class']) > 0)) {
304 $foreignClassName = $columnConfiguration['foreign_class'];
305 } else {
306 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
307 // TODO Apply a cache to increase performance (profile first)
308 if (is_array($extbaseSettings['persistence']['classes'])) {
309 foreach ($extbaseSettings['persistence']['classes'] as $className => $classConfiguration) {
310 if ($classConfiguration['mapping']['tableName'] === $columnConfiguration['foreign_table']) {
311 $foreignClassName = $className;
312 break;
313 }
314 }
315 }
316 }
317 // TODO Throw exception if no appropriate class name was found
318 return $foreignClassName;
319 }
320
321 /**
322 * Sets the column maps.
323 *
324 * @param array $columnMaps The column maps stored in a flat array.
325 * @return void
326 */
327 public function setColumnMaps(array $columnMaps) {
328 $this->columnMaps = $columnMaps;
329 }
330
331 /**
332 * Adds a given column map to the data map.
333 *
334 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
335 * @return void
336 */
337 public function addColumnMap(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
338 $this->columnMaps[$columnMap->getPropertyName()] = $columnMap;
339 }
340
341 /**
342 * Builds a column map out of the given column name, type of value (optional), and type of
343 * relation (optional) and adds it to the data map.
344 *
345 * @param string $columnName The column name
346 * @param string $propertyName The property name
347 * @param string $propertyType The type of value (default: string)
348 * @param string $typeOfRelation The type of relation (default: none)
349 * @return Tx_Extbase_Persistence_Mapper_DataMap Returns itself for a fluent interface
350 */
351 public function addColumn($columnName, $propertyName = '', $propertyType = Tx_Extbase_Persistence_PropertyType::STRING, $typeOfRelation = Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE) {
352 if (empty($propertyName)) {
353 $propertyName = Tx_Extbase_Utility_Extension::convertUnderscoredToLowerCamelCase($columnName);
354 }
355
356 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
357 $columnMap->setPropertyType($propertyType);
358 $columnMap->setTypeOfRelation($typeOfRelation);
359 $this->addColumnMap($columnMap);
360 return $this;
361 }
362
363 /**
364 * Returns all column maps
365 *
366 * @return array The column maps
367 */
368 public function getColumnMaps() {
369 return $this->columnMaps;
370 }
371
372 /**
373 * Returns the column map corresponding to the given property name.
374 *
375 * @param string $propertyName
376 * @return Tx_Extbase_Persistence_Mapper_ColumnMap|NULL The column map or NULL if no corresponding column map was found.
377 */
378 public function getColumnMap($propertyName) {
379 return $this->columnMaps[$propertyName];
380 }
381
382 /**
383 * Returns TRUE if the property is persistable (configured in $TCA)
384 *
385 * @param string $propertyName The property name
386 * @return boolean TRUE if the property is persistable (configured in $TCA)
387 */
388 public function isPersistableProperty($propertyName) {
389 return isset($this->columnMaps[$propertyName]);
390 }
391
392 /**
393 * Check if versioning is enabled .
394 *
395 * @return boolean
396 */
397 public function isVersionable() {
398 return ($GLOBALS['TCA'] [$this->tableName] ['ctrl'] ['versioningWS'] === '1');
399 }
400
401 /**
402 * Returns TRUE if the table has a pid column holding the id of the page the record is virtually stored on.
403 * Currently we don't support tables without a pid column.
404 *
405 * @return boolean The result
406 */
407 public function hasPidColumn() {
408 // TODO Should we implement a check for having a pid column?
409 return TRUE;
410 }
411
412 /**
413 * Returns the name of a column holding the timestamp the record was modified
414 *
415 * @return string The field name
416 */
417 public function getTimestampColumnName() {
418 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp'];
419 }
420
421 /**
422 * Returns TRUE if the table has a column holding the timestamp the record was modified
423 *
424 * @return boolean The result
425 */
426 public function hasTimestampColumn() {
427 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp']);
428 }
429
430 /**
431 * Returns the name of a column holding the creation date timestamp
432 *
433 * @return string The field name
434 */
435 public function getCreationDateColumnName() {
436 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate'];
437 }
438
439 /**
440 * Returns TRUE if the table has olumn holding the creation date timestamp
441 *
442 * @return boolean The result
443 */
444 public function hasCreationDateColumn() {
445 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate']);
446 }
447
448 /**
449 * Returns the name of a column holding the uid of the back-end user who created this record
450 *
451 * @return string The field name
452 */
453 public function getCreatorUidColumnName() {
454 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id'];
455 }
456
457 /**
458 * Returns TRUE if the table has a column holding the uid of the back-end user who created this record
459 *
460 * @return boolean The result
461 */
462 public function hasCreatorUidColumn() {
463 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id']);
464 }
465
466 /**
467 * Returns the name of a column indicating the 'deleted' state of the row
468 *
469 * @return string The field name
470 */
471 public function getDeletedColumnName() {
472 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete'];
473 }
474
475 /**
476 * Returns TRUE if the table has a column indicating the 'deleted' state of the row
477 *
478 * @return boolean The result
479 */
480 public function hasDeletedColumn() {
481 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete']);
482 }
483
484 /**
485 * Returns the name of a column indicating the 'hidden' state of the row
486 *
487 * @return string The field name
488 */
489 public function getHiddenColumnName() {
490 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled'];
491 }
492
493 /**
494 * Returns TRUE if the table has a column indicating the 'hidden' state of the row
495 *
496 * @return boolean The result
497 */
498 public function hasHiddenColumn() {
499 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled']);
500 }
501
502 /**
503 * Returns the name of a column holding the timestamp the record should not displayed before
504 *
505 * @return string The field name
506 */
507 public function getStartTimeColumnName() {
508 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime'];
509 }
510
511 /**
512 * Returns TRUE if the table has a column holding the timestamp the record should not displayed before
513 *
514 * @return boolean The result
515 */
516 public function hasStartTimeColumn() {
517 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime']);
518 }
519
520 /**
521 * Returns the name of a column holding the timestamp the record should not displayed afterwards
522 *
523 * @return string The field name
524 */
525 public function getEndTimeColumnName() {
526 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime'];
527 }
528
529 /**
530 * Returns TRUE if the table has a column holding the timestamp the record should not displayed afterwards
531 *
532 * @return boolean The result
533 */
534 public function hasEndTimeColumn() {
535 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime']);
536 }
537
538 /**
539 * Returns the name of a column holding the uid of the front-end user group which is allowed to edit this record
540 *
541 * @return string The field name
542 */
543 public function getFrontEndUserGroupColumnName() {
544 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group'];
545 }
546
547 /**
548 * Returns TRUE if the table has a column holding the uid of the front-end user group which is allowed to edit this record
549 *
550 * @return boolean The result
551 */
552 public function hasFrontEndUserGroupColumn() {
553 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group']);
554 }
555
556 /**
557 * Converts a field name to the property name. It respects property name aliases defined in $TCA.
558 *
559 * @param string $fieldName The field name
560 * @return string $propertyName The property name
561 */
562 public function convertFieldNameToPropertyName($fieldName) {
563 $propertyName = $fieldName;
564 return $propertyName; // TODO Implement aliases for field names (see also convertPropertyNameToFieldName())
565 }
566
567 /**
568 * Converts a preoperty name to the field name. It respects property name aliases defined in $TCA.
569 *
570 * @param string $fieldName The field name
571 * @return string $propertyName The property name
572 */
573 public function convertPropertyNameToFieldName($propertyName) {
574 $fieldName = $propertyName;
575 return $fieldName;
576 }
577
578 /**
579 * Converts the given string into the given type
580 *
581 * @param integer $type one of the constants defined in Tx_Extbase_Persistence_PropertyType
582 * @param string $string a string representing a value of the given type
583 *
584 * @return string|int|float|DateTime|boolean
585 */
586 public function convertFieldValueToPropertyValue($type, $string) {
587 switch ($type) {
588 case Tx_Extbase_Persistence_PropertyType::LONG:
589 return (int) $string;
590 case Tx_Extbase_Persistence_PropertyType::DOUBLE:
591 case Tx_Extbase_Persistence_PropertyType::DECIMAL:
592 return (float) $string;
593 case Tx_Extbase_Persistence_PropertyType::DATE:
594 return new DateTime(strftime('%Y-%m-%d %H:%M:%S', $string)); // TODO Check for Time Zone issues. Encode the time zone as well, and use the format defined by ISO used by TYPO3CR as well
595 case Tx_Extbase_Persistence_PropertyType::BOOLEAN:
596 return (boolean) $string;
597 default:
598 return $string;
599 }
600 }
601
602 /**
603 * Converts a value from a property type to a database field type
604 *
605 * @param mixed $propertyValue The property value
606 * @return mixed The converted value
607 */
608 public function convertPropertyValueToFieldValue($propertyValue) {
609 if (is_bool($propertyValue)) {
610 $convertedValue = $propertyValue ? 1 : 0;
611 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
612 $convertedValue = $propertyValue->getUid();
613 } elseif (is_a($propertyValue, 'DateTime')) {
614 $convertedValue = $propertyValue->format('U');
615 } elseif (is_int($propertyValue)) {
616 $convertedValue = $propertyValue;
617 } else {
618 $convertedValue = $propertyValue;
619 }
620 return $convertedValue;
621 }
622
623 }