2 /***************************************************************
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
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.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
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.
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
26 * A data map to map a single table configured in $TCA on a domain object.
29 * @subpackage Persistence\Mapper
32 class Tx_Extbase_Persistence_Mapper_DataMap
{
35 * The domain class name
42 * The table name corresponding to the domain class configured in $TCA
49 * An array of column maps configured in $TCA
53 protected $columnMaps;
56 * Constructs this DataMap
58 * @param string $className The class name. This determines the table to fetch the configuration for
60 // TODO Refactor to factory pattern (DataMapFactory) and value object (DataMap)
61 public function __construct($className, $tableName = '', array $mapping = array()) {
62 $this->setClassName($className);
63 if (empty($tableName)) {
64 $this->setTableName(strtolower($className));
66 $this->setTableName($tableName);
68 $this->initialize($mapping);
72 * Sets the name of the class the colum map represents
76 public function setClassName($className) {
77 $this->className
= $className;
81 * Returns the name of the class the column map represents
83 * @return string The class name
85 public function getClassName() {
86 return $this->className
;
90 * Sets the name of the table the colum map represents
94 public function setTableName($tableName) {
95 $this->tableName
= $tableName;
99 * Returns the name of the table the column map represents
101 * @return string The table name
103 public function getTableName() {
104 return $this->tableName
;
108 * Initializes the data map by adding column maps for all the configured columns in the $TCA.
109 * It also resolves the type of values the column is holding and the typo of relation the column
114 protected function initialize(array $mapping) {
115 $this->addCommonColumns();
116 $columnConfigurations = array();
117 foreach ($this->getColumnsDefinitions() as $columnName => $columnDefinition) {
118 $columnConfigurations[$columnName] = $columnDefinition['config'];
119 $columnConfigurations[$columnName]['mapOnProperty'] = Tx_Extbase_Utility_Extension
::convertUnderscoredToLowerCamelCase($columnName);
121 $columnConfigurations = t3lib_div
::array_merge_recursive_overrule($columnConfigurations, $mapping);
122 foreach ($columnConfigurations as $columnName => $columnConfiguration) {
123 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $columnConfiguration['mapOnProperty']);
124 $this->setPropertyType($columnMap, $columnConfiguration);
125 $this->setRelations($columnMap, $columnConfiguration);
126 $this->addColumnMap($columnMap);
131 * Returns the TCA columns array of the specified table
133 * @param string $tableName An optional table name to fetch the columns definition from
134 * @return array The TCA columns definition
136 public function getColumnsDefinitions($tableName = '') {
137 $tableName = strlen($tableName) > 0 ?
$tableName : $this->getTableName();
138 if (TYPO3_MODE
=== 'FE') {
139 $GLOBALS['TSFE']->includeTCA();
141 t3lib_div
::loadTCA($tableName);
142 $columns = is_array($GLOBALS['TCA'][$tableName]['columns']) ?
$GLOBALS['TCA'][$tableName]['columns'] : array();
147 * Adds available common columns (e.g. tstamp or crdate) to the data map. It takes the configured column names
152 protected function addCommonColumns() {
153 $this->addColumn('uid', NULL, Tx_Extbase_Persistence_PropertyType
::LONG
);
154 if ($this->hasPidColumn()) {
155 $this->addColumn('pid', NULL, Tx_Extbase_Persistence_PropertyType
::LONG
);
157 if ($this->hasTimestampColumn()) {
158 $this->addColumn($this->getTimestampColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::DATE
);
160 if ($this->hasCreationDateColumn()) {
161 $this->addColumn($this->getCreationDateColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::DATE
);
163 if ($this->hasCreatorUidColumn()) {
164 $this->addColumn($this->getCreatorUidColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::LONG
);
166 if ($this->hasDeletedColumn()) {
167 $this->addColumn($this->getDeletedColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::BOOLEAN
);
169 if ($this->hasHiddenColumn()) {
170 $this->addColumn($this->getHiddenColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::BOOLEAN
);
175 * This method tries to determine the type of value the column hold by inspectiong the $TCA column configuration
178 * @param string $columnMap The column map
179 * @param string $columnConfiguration The column configuration from $TCA
182 protected function setPropertyType(Tx_Extbase_Persistence_Mapper_ColumnMap
&$columnMap, $columnConfiguration) {
183 $evalConfiguration = t3lib_div
::trimExplode(',', $columnConfiguration['eval']);
184 if (in_array('date', $evalConfiguration) ||
in_array('datetime', $evalConfiguration)) {
185 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::DATE
);
186 } elseif ($columnConfiguration['type'] === 'check' && empty($columnConfiguration['items'])) {
187 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::BOOLEAN
);
188 } elseif (in_array('int', $evalConfiguration)) {
189 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::LONG
);
190 } elseif (in_array('double2', $evalConfiguration)) {
191 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::DOUBLE);
193 if (isset($columnConfiguration['foreign_table'])) {
194 if (isset($columnConfiguration['loadingStrategy'])) {
195 $columnMap->setLoadingStrategy($columnConfiguration['loadingStrategy']);
197 $columnMap->setLoadingStrategy(Tx_Extbase_Persistence_Mapper_ColumnMap
::STRATEGY_EAGER
);
199 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::REFERENCE
);
201 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::STRING);
207 * This method tries to determine the type of type of relation to other tables and sets it based on
208 * the $TCA column configuration
210 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
211 * @param string $columnConfiguration The column configuration from $TCA
214 protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap
&$columnMap, $columnConfiguration) {
215 if (isset($columnConfiguration) && $columnConfiguration['type'] !== 'passthrough') {
216 if (isset($columnConfiguration['foreign_table']) && !isset($columnConfiguration['MM']) && !isset($columnConfiguration['foreign_label'])) {
217 if ($columnConfiguration['maxitems'] == 1) {
218 $this->setOneToOneRelation($columnMap, $columnConfiguration);
220 $this->setOneToManyRelation($columnMap, $columnConfiguration);
222 } elseif (isset($columnConfiguration['foreign_label']) ||
isset($columnConfiguration['MM'])) {
223 $this->setManyToManyRelation($columnMap, $columnConfiguration);
225 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_NONE
);
231 * This method sets the configuration for a 1:1 relation based on
232 * the $TCA column configuration
234 * @param string $columnMap The column map
235 * @param string $columnConfiguration The column configuration from $TCA
238 protected function setOneToOneRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
&$columnMap, $columnConfiguration) {
239 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_ONE
);
240 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
241 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
242 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
243 $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']);
244 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
245 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
249 * This method sets the configuration for a 1:n relation based on
250 * the $TCA column configuration
252 * @param string $columnMap The column map
253 * @param string $columnConfiguration The column configuration from $TCA
256 protected function setOneToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
&$columnMap, $columnConfiguration) {
257 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_MANY
);
258 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
259 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
260 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
261 $columnMap->setChildSortbyFieldName($columnConfiguration['foreign_sortby']);
262 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
263 $columnMap->setParentTableFieldName($columnConfiguration['foreign_table_field']);
267 * This method sets the configuration for a m:n relation based on
268 * the $TCA column configuration
270 * @param string $columnMap The column map
271 * @param string $columnConfiguration The column configuration from $TCA
274 protected function setManyToManyRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
&$columnMap, $columnConfiguration) {
275 // TODO support multi table relationships
276 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
);
277 if ($columnConfiguration['type'] === 'inline') {
278 $columns = $this->getColumnsDefinitions($columnConfiguration['foreign_table']);
279 $columnMap->setChildClassName($this->determineChildClassName($columns[$columnConfiguration['foreign_label']]));
280 $columnMap->setChildTableName($columns[$columnConfiguration['foreign_label']]['foreign_table']);
281 $columnMap->setRelationTableName($columnConfiguration['foreign_table']);
282 $columnMap->setParentKeyFieldName($columnConfiguration['foreign_field']);
283 $columnMap->setChildKeyFieldName($columnConfiguration['foreign_label']);
284 $columnMap->setChildSortByFieldName($columnConfiguration['foreign_sortby']);
286 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
287 $columnMap->setChildTableName($columnConfiguration['foreign_table']);
288 $columnMap->setChildTableWhereStatement($columnConfiguration['foreign_table_where']);
289 $columnMap->setRelationTableName($columnConfiguration['MM']);
290 if (is_array($columnConfiguration['MM_match_fields'])) {
291 $columnMap->setRelationTableMatchFields($columnConfiguration['MM_match_fields']);
293 if (is_array($columnConfiguration['MM_insert_fields'])) {
294 $columnMap->setRelationTableInsertFields($columnConfiguration['MM_insert_fields']);
296 $columnMap->setRelationTableWhereStatement($columnConfiguration['MM_table_where']);
297 if (!empty($columnConfiguration['MM_opposite_field'])) {
298 $columnMap->setParentKeyFieldName('uid_foreign');
299 $columnMap->setChildKeyFieldName('uid_local');
300 $columnMap->setChildSortByFieldName('sorting_foreign');
302 $columnMap->setParentKeyFieldName('uid_local');
303 $columnMap->setChildKeyFieldName('uid_foreign');
304 $columnMap->setChildSortByFieldName('sorting');
310 * This function determines the child class name. It can either be defined as foreign_class in the column configuration (TCA)
311 * or it must be defined in the extbase framework configuration (reverse mapping from tableName to className).
313 * @param $columnConfiguration The column configuration (from TCA)
314 * @return string The class name of the related child object
316 protected function determineChildClassName($columnConfiguration) {
317 $foreignClassName = '';
318 if (is_string($columnConfiguration['foreign_class']) && (strlen($columnConfiguration['foreign_class']) > 0)) {
319 $foreignClassName = $columnConfiguration['foreign_class'];
321 if (empty($foreignClassName)){
322 $extbaseSettings = Tx_Extbase_Dispatcher
::getExtbaseFrameworkConfiguration();
323 // TODO Apply a cache to increase performance (profile first)
324 if (is_array($extbaseSettings['persistence']['classes'])) {
325 foreach ($extbaseSettings['persistence']['classes'] as $className => $classConfiguration) {
326 if ($classConfiguration['mapping']['tableName'] === $columnConfiguration['foreign_table']) {
327 $foreignClassName = $className;
333 // TODO Throw exception if no appropriate class name was found
334 return $foreignClassName;
338 * Sets the column maps.
340 * @param array $columnMaps The column maps stored in a flat array.
343 public function setColumnMaps(array $columnMaps) {
344 $this->columnMaps
= $columnMaps;
348 * Adds a given column map to the data map.
350 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
353 public function addColumnMap(Tx_Extbase_Persistence_Mapper_ColumnMap
$columnMap) {
354 $this->columnMaps
[$columnMap->getPropertyName()] = $columnMap;
358 * Builds a column map out of the given column name, type of value (optional), and type of
359 * relation (optional) and adds it to the data map.
361 * @param string $columnName The column name
362 * @param string $propertyName The property name
363 * @param string $propertyType The type of value (default: string)
364 * @param string $typeOfRelation The type of relation (default: none)
365 * @return Tx_Extbase_Persistence_Mapper_DataMap Returns itself for a fluent interface
367 public function addColumn($columnName, $propertyName = '', $propertyType = Tx_Extbase_Persistence_PropertyType
::STRING, $typeOfRelation = Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_NONE
) {
368 if (empty($propertyName)) {
369 $propertyName = Tx_Extbase_Utility_Extension
::convertUnderscoredToLowerCamelCase($columnName);
372 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
373 $columnMap->setPropertyType($propertyType);
374 $columnMap->setTypeOfRelation($typeOfRelation);
375 $this->addColumnMap($columnMap);
380 * Returns all column maps
382 * @return array The column maps
384 public function getColumnMaps() {
385 return $this->columnMaps
;
389 * Returns the column map corresponding to the given property name.
391 * @param string $propertyName
392 * @return Tx_Extbase_Persistence_Mapper_ColumnMap|NULL The column map or NULL if no corresponding column map was found.
394 public function getColumnMap($propertyName) {
395 return $this->columnMaps
[$propertyName];
399 * Returns TRUE if the property is persistable (configured in $TCA)
401 * @param string $propertyName The property name
402 * @return boolean TRUE if the property is persistable (configured in $TCA)
404 public function isPersistableProperty($propertyName) {
405 return isset($this->columnMaps
[$propertyName]);
409 * Check if versioning is enabled .
413 public function isVersionable() {
414 return ($GLOBALS['TCA'] [$this->tableName
] ['ctrl'] ['versioningWS'] === '1');
418 * Returns TRUE if the table has a pid column holding the id of the page the record is virtually stored on.
419 * Currently we don't support tables without a pid column.
421 * @return boolean The result
423 public function hasPidColumn() {
424 // TODO Should we implement a check for having a pid column?
429 * Returns the name of a column holding the timestamp the record was modified
431 * @return string The field name
433 public function getTimestampColumnName() {
434 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp'];
438 * Returns TRUE if the table has a column holding the timestamp the record was modified
440 * @return boolean The result
442 public function hasTimestampColumn() {
443 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp']);
447 * Returns the name of a column holding the creation date timestamp
449 * @return string The field name
451 public function getCreationDateColumnName() {
452 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate'];
456 * Returns TRUE if the table has olumn holding the creation date timestamp
458 * @return boolean The result
460 public function hasCreationDateColumn() {
461 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate']);
465 * Returns the name of a column holding the uid of the back-end user who created this record
467 * @return string The field name
469 public function getCreatorUidColumnName() {
470 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id'];
474 * Returns TRUE if the table has a column holding the uid of the back-end user who created this record
476 * @return boolean The result
478 public function hasCreatorUidColumn() {
479 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id']);
483 * Returns the name of a column indicating the 'deleted' state of the row
485 * @return string The field name
487 public function getDeletedColumnName() {
488 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete'];
492 * Returns TRUE if the table has a column indicating the 'deleted' state of the row
494 * @return boolean The result
496 public function hasDeletedColumn() {
497 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete']);
501 * Returns the name of a column indicating the 'hidden' state of the row
503 * @return string The field name
505 public function getHiddenColumnName() {
506 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled'];
510 * Returns TRUE if the table has a column indicating the 'hidden' state of the row
512 * @return boolean The result
514 public function hasHiddenColumn() {
515 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled']);
519 * Returns the name of a column holding the timestamp the record should not displayed before
521 * @return string The field name
523 public function getStartTimeColumnName() {
524 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime'];
528 * Returns TRUE if the table has a column holding the timestamp the record should not displayed before
530 * @return boolean The result
532 public function hasStartTimeColumn() {
533 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime']);
537 * Returns the name of a column holding the timestamp the record should not displayed afterwards
539 * @return string The field name
541 public function getEndTimeColumnName() {
542 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime'];
546 * Returns TRUE if the table has a column holding the timestamp the record should not displayed afterwards
548 * @return boolean The result
550 public function hasEndTimeColumn() {
551 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime']);
555 * Returns the name of a column holding the uid of the front-end user group which is allowed to edit this record
557 * @return string The field name
559 public function getFrontEndUserGroupColumnName() {
560 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group'];
564 * Returns TRUE if the table has a column holding the uid of the front-end user group which is allowed to edit this record
566 * @return boolean The result
568 public function hasFrontEndUserGroupColumn() {
569 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group']);
573 * Converts a field name to the property name. It respects property name aliases defined in $TCA.
575 * @param string $fieldName The field name
576 * @return string $propertyName The property name
578 public function convertFieldNameToPropertyName($fieldName) {
579 $propertyName = $fieldName;
580 return $propertyName; // TODO Implement aliases for field names (see also convertPropertyNameToFieldName())
584 * Converts a preoperty name to the field name. It respects property name aliases defined in $TCA.
586 * @param string $fieldName The field name
587 * @return string $propertyName The property name
589 public function convertPropertyNameToFieldName($propertyName) {
590 $fieldName = $propertyName;
595 * Converts the given string into the given type
597 * @param integer $type one of the constants defined in Tx_Extbase_Persistence_PropertyType
598 * @param string $string a string representing a value of the given type
600 * @return string|int|float|DateTime|boolean
602 public function convertFieldValueToPropertyValue($type, $string) {
604 case Tx_Extbase_Persistence_PropertyType
::LONG
:
605 return (int) $string;
606 case Tx_Extbase_Persistence_PropertyType
::DOUBLE:
607 case Tx_Extbase_Persistence_PropertyType
::DECIMAL
:
608 return (float) $string;
609 case Tx_Extbase_Persistence_PropertyType
::DATE
:
610 return new DateTime(date('r', $string));
611 case Tx_Extbase_Persistence_PropertyType
::BOOLEAN
:
612 return (boolean
) $string;
619 * Converts a value from a property type to a database field type
621 * @param mixed $propertyValue The property value
622 * @return mixed The converted value
624 public function convertPropertyValueToFieldValue($propertyValue) {
625 if (is_bool($propertyValue)) {
626 $convertedValue = $propertyValue ?
1 : 0;
627 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject
) {
628 $convertedValue = $propertyValue->getUid();
629 } elseif (is_a($propertyValue, 'DateTime')) {
630 $convertedValue = $propertyValue->format('U');
631 } elseif (is_int($propertyValue)) {
632 $convertedValue = $propertyValue;
634 $convertedValue = $propertyValue;
636 return $convertedValue;