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
{
34 * The domain class name
41 * The table name corresponding to the domain class configured in $TCA
48 * An array of column maps configured in $TCA
52 protected $columnMaps;
55 * Constructs this DataMap
57 * @param string $className The class name. This determines the table to fetch the configuration for
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));
65 $this->setTableName($tableName);
67 $this->initialize($mapping);
71 * Sets the name of the class the colum map represents
75 public function setClassName($className) {
76 $this->className
= $className;
80 * Returns the name of the class the column map represents
82 * @return string The class name
84 public function getClassName() {
85 return $this->className
;
89 * Sets the name of the table the colum map represents
93 public function setTableName($tableName) {
94 $this->tableName
= $tableName;
98 * Returns the name of the table the column map represents
100 * @return string The table name
102 public function getTableName() {
103 return $this->tableName
;
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
113 protected function initialize(array $mapping) {
114 if (TYPO3_MODE
=== 'FE') {
115 $GLOBALS['TSFE']->includeTCA();
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'];
126 $propertyName = Tx_Extbase_Utility_Extension
::convertUnderscoredToLowerCamelCase($columnName);
128 if (isset($mapping[$columnName]['foreignClass']) && !isset($columnConfiguration['foreign_class'])) {
129 $columnConfiguration['foreign_class'] = $mapping[$columnName]['foreignClass'];
131 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
132 $this->setPropertyType($columnMap, $columnConfiguration);
133 $this->setRelations($columnMap, $columnConfiguration);
134 $this->addColumnMap($columnMap);
140 * Adds available common columns (e.g. tstamp or crdate) to the data map. It takes the configured column names
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
);
151 if ($this->hasTimestampColumn()) {
152 $this->addColumn($this->getTimestampColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::DATE
);
154 if ($this->hasCreationDateColumn()) {
155 $this->addColumn($this->getCreationDateColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::DATE
);
157 if ($this->hasCreatorUidColumn()) {
158 $this->addColumn($this->getCreatorUidColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::LONG
);
160 if ($this->hasDeletedColumn()) {
161 $this->addColumn($this->getDeletedColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::BOOLEAN
);
163 if ($this->hasHiddenColumn()) {
164 $this->addColumn($this->getHiddenColumnName(), NULL, Tx_Extbase_Persistence_PropertyType
::BOOLEAN
);
169 * This method tries to determine the type of value the column hold by inspectiong the $TCA column configuration
172 * @param string $columnMap The column map
173 * @param string $columnConfiguration The column configuration from $TCA
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);
187 if (isset($columnConfiguration['foreign_table'])) {
188 if (isset($columnConfiguration['loadingStrategy'])) {
189 $columnMap->setLoadingStrategy($columnConfiguration['loadingStrategy']);
191 $columnMap->setLoadingStrategy(Tx_Extbase_Persistence_Mapper_ColumnMap
::STRATEGY_EAGER
);
193 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::REFERENCE
);
195 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType
::STRING);
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
204 * @param string $columnMap The column map
205 * @param string $columnConfiguration The column configuration from $TCA
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);
214 $this->setOneToManyRelation($columnMap, $columnConfiguration);
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);
222 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_NONE
);
228 * This method sets the configuration for a 1:1 relation based on
229 * the $TCA column configuration
231 * @param string $columnMap The column map
232 * @param string $columnConfiguration The column configuration from $TCA
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']);
247 * This method sets the configuration for a 1:n relation based on
248 * the $TCA column configuration
250 * @param string $columnMap The column map
251 * @param string $columnConfiguration The column configuration from $TCA
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']);
266 * This method sets the configuration for a m:n relation based on
267 * the $TCA column configuration
269 * @param string $columnMap The column map
270 * @param string $columnConfiguration The column configuration from $TCA
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']);
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');
288 $columnMap->setParentKeyFieldName('uid_local');
289 $columnMap->setChildKeyFieldName('uid_foreign');
290 $columnMap->setChildSortByFieldName('sorting');
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).
298 * @param $columnConfiguration The column configuration (from TCA)
299 * @return string The class name of the related child object
301 protected function determineChildClassName($columnConfiguration) {
302 $foreignClassName = '';
303 if (is_string($columnConfiguration['foreign_class']) && (strlen($columnConfiguration['foreign_class']) > 0)) {
304 $foreignClassName = $columnConfiguration['foreign_class'];
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;
317 // TODO Throw exception if no appropriate class name was found
318 return $foreignClassName;
322 * Sets the column maps.
324 * @param array $columnMaps The column maps stored in a flat array.
327 public function setColumnMaps(array $columnMaps) {
328 $this->columnMaps
= $columnMaps;
332 * Adds a given column map to the data map.
334 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
337 public function addColumnMap(Tx_Extbase_Persistence_Mapper_ColumnMap
$columnMap) {
338 $this->columnMaps
[$columnMap->getPropertyName()] = $columnMap;
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.
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
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);
356 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
357 $columnMap->setPropertyType($propertyType);
358 $columnMap->setTypeOfRelation($typeOfRelation);
359 $this->addColumnMap($columnMap);
364 * Returns all column maps
366 * @return array The column maps
368 public function getColumnMaps() {
369 return $this->columnMaps
;
373 * Returns the column map corresponding to the given property name.
375 * @param string $propertyName
376 * @return Tx_Extbase_Persistence_Mapper_ColumnMap|NULL The column map or NULL if no corresponding column map was found.
378 public function getColumnMap($propertyName) {
379 return $this->columnMaps
[$propertyName];
383 * Returns TRUE if the property is persistable (configured in $TCA)
385 * @param string $propertyName The property name
386 * @return boolean TRUE if the property is persistable (configured in $TCA)
388 public function isPersistableProperty($propertyName) {
389 return isset($this->columnMaps
[$propertyName]);
393 * Check if versioning is enabled .
397 public function isVersionable() {
398 return ($GLOBALS['TCA'] [$this->tableName
] ['ctrl'] ['versioningWS'] === '1');
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.
405 * @return boolean The result
407 public function hasPidColumn() {
408 // TODO Should we implement a check for having a pid column?
413 * Returns the name of a column holding the timestamp the record was modified
415 * @return string The field name
417 public function getTimestampColumnName() {
418 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp'];
422 * Returns TRUE if the table has a column holding the timestamp the record was modified
424 * @return boolean The result
426 public function hasTimestampColumn() {
427 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp']);
431 * Returns the name of a column holding the creation date timestamp
433 * @return string The field name
435 public function getCreationDateColumnName() {
436 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate'];
440 * Returns TRUE if the table has olumn holding the creation date timestamp
442 * @return boolean The result
444 public function hasCreationDateColumn() {
445 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate']);
449 * Returns the name of a column holding the uid of the back-end user who created this record
451 * @return string The field name
453 public function getCreatorUidColumnName() {
454 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id'];
458 * Returns TRUE if the table has a column holding the uid of the back-end user who created this record
460 * @return boolean The result
462 public function hasCreatorUidColumn() {
463 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id']);
467 * Returns the name of a column indicating the 'deleted' state of the row
469 * @return string The field name
471 public function getDeletedColumnName() {
472 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete'];
476 * Returns TRUE if the table has a column indicating the 'deleted' state of the row
478 * @return boolean The result
480 public function hasDeletedColumn() {
481 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete']);
485 * Returns the name of a column indicating the 'hidden' state of the row
487 * @return string The field name
489 public function getHiddenColumnName() {
490 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled'];
494 * Returns TRUE if the table has a column indicating the 'hidden' state of the row
496 * @return boolean The result
498 public function hasHiddenColumn() {
499 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled']);
503 * Returns the name of a column holding the timestamp the record should not displayed before
505 * @return string The field name
507 public function getStartTimeColumnName() {
508 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime'];
512 * Returns TRUE if the table has a column holding the timestamp the record should not displayed before
514 * @return boolean The result
516 public function hasStartTimeColumn() {
517 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime']);
521 * Returns the name of a column holding the timestamp the record should not displayed afterwards
523 * @return string The field name
525 public function getEndTimeColumnName() {
526 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime'];
530 * Returns TRUE if the table has a column holding the timestamp the record should not displayed afterwards
532 * @return boolean The result
534 public function hasEndTimeColumn() {
535 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime']);
539 * Returns the name of a column holding the uid of the front-end user group which is allowed to edit this record
541 * @return string The field name
543 public function getFrontEndUserGroupColumnName() {
544 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group'];
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
550 * @return boolean The result
552 public function hasFrontEndUserGroupColumn() {
553 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group']);
557 * Converts a field name to the property name. It respects property name aliases defined in $TCA.
559 * @param string $fieldName The field name
560 * @return string $propertyName The property name
562 public function convertFieldNameToPropertyName($fieldName) {
563 $propertyName = $fieldName;
564 return $propertyName; // TODO Implement aliases for field names (see also convertPropertyNameToFieldName())
568 * Converts a preoperty name to the field name. It respects property name aliases defined in $TCA.
570 * @param string $fieldName The field name
571 * @return string $propertyName The property name
573 public function convertPropertyNameToFieldName($propertyName) {
574 $fieldName = $propertyName;
579 * Converts the given string into the given type
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
584 * @return string|int|float|DateTime|boolean
586 public function convertFieldValueToPropertyValue($type, $string) {
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;
603 * Converts a value from a property type to a database field type
605 * @param mixed $propertyValue The property value
606 * @return mixed The converted value
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;
618 $convertedValue = $propertyValue;
620 return $convertedValue;