[+FEATURE] Extbase (Persistence): Implemented a second Lazy Loading strategy called...
[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 t3lib_div::loadTCA($this->getTableName());
115 $columns = $GLOBALS['TCA'][$this->getTableName()]['columns'];
116 $this->addCommonColumns();
117 if (is_array($columns)) {
118 foreach ($columns as $columnName => $columnConfiguration) {
119 if (!empty($mapping[$columnName]['mapOnProperty'])) {
120 $propertyName = $mapping[$columnName]['mapOnProperty'];
121 } else {
122 $propertyName = Tx_Extbase_Utility_Extension::convertUnderscoredToLowerCamelCase($columnName);
123 }
124 if (isset($mapping[$columnName]['foreignClass']) && !isset($columnConfiguration['config']['foreign_class'])) {
125 $columnConfiguration['config']['foreign_class'] = $mapping[$columnName]['foreignClass'];
126 }
127 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
128 $this->setPropertyType($columnMap, $columnConfiguration);
129 $this->setRelations($columnMap, $columnConfiguration);
130 $this->addColumnMap($columnMap);
131 }
132 }
133 }
134
135 /**
136 * Adds available common columns (e.g. tstamp or crdate) to the data map. It takes the configured column names
137 * into account.
138 *
139 * @return void
140 */
141 protected function addCommonColumns() {
142 // TODO Decide whether we should add pid and uid columns by default
143 $this->addColumn('uid', NULL, Tx_Extbase_Persistence_PropertyType::LONG);
144 if ($this->hasPidColumn()) {
145 $this->addColumn('pid', NULL, Tx_Extbase_Persistence_PropertyType::LONG);
146 }
147 if ($this->hasTimestampColumn()) {
148 $this->addColumn($this->getTimestampColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::DATE);
149 }
150 if ($this->hasCreationDateColumn()) {
151 $this->addColumn($this->getCreationDateColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::DATE);
152 }
153 if ($this->hasCreatorUidColumn()) {
154 $this->addColumn($this->getCreatorUidColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::LONG);
155 }
156 if ($this->hasDeletedColumn()) {
157 $this->addColumn($this->getDeletedColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::BOOLEAN);
158 }
159 if ($this->hasHiddenColumn()) {
160 $this->addColumn($this->getHiddenColumnName(), NULL, Tx_Extbase_Persistence_PropertyType::BOOLEAN);
161 }
162 }
163
164 /**
165 * This method tries to determine the type of value the column hold by inspectiong the $TCA column configuration
166 * and sets it.
167 *
168 * @param string $columnMap The column map
169 * @param string $columnConfiguration The column configuration from $TCA
170 * @return void
171 */
172 protected function setPropertyType(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
173 $evalConfiguration = t3lib_div::trimExplode(',', $columnConfiguration['config']['eval']);
174 if (in_array('date', $evalConfiguration) || in_array('datetime', $evalConfiguration)) {
175 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DATE);
176 } elseif ($columnConfiguration['config']['type'] === 'check' && empty($columnConfiguration['config']['items'])) {
177 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::BOOLEAN);
178 } elseif (in_array('int', $evalConfiguration)) {
179 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::LONG);
180 } elseif (in_array('double2', $evalConfiguration)) {
181 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::DOUBLE);
182 } else {
183 if (isset($columnConfiguration['config']['foreign_table'])) {
184 if (isset($columnConfiguration['config']['loadingStrategy'])) {
185 $columnMap->setLoadingStrategy($columnConfiguration['config']['loadingStrategy']);
186 } else {
187 $columnMap->setLoadingStrategy(Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_EAGER);
188 }
189 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::REFERENCE);
190 } else {
191 $columnMap->setPropertyType(Tx_Extbase_Persistence_PropertyType::STRING);
192 }
193 }
194 }
195
196 /**
197 * This method tries to determine the type of type of relation to other tables and sets it based on
198 * the $TCA column configuration
199 *
200 * @param string $columnMap The column map
201 * @param string $columnConfiguration The column configuration from $TCA
202 * @return void
203 */
204 protected function setRelations(Tx_Extbase_Persistence_Mapper_ColumnMap &$columnMap, $columnConfiguration) {
205 if (isset($columnConfiguration['config'])) {
206 if (isset($columnConfiguration['config']['foreign_table']) && !isset($columnConfiguration['config']['MM'])) {
207 if ($columnConfiguration['config']['maxitems'] == 1) {
208 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE);
209 } else {
210 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY);
211 }
212 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
213 $columnMap->setChildTableName($columnConfiguration['config']['foreign_table']);
214 $columnMap->setChildTableWhereStatement($columnConfiguration['config']['foreign_table_where']);
215 $columnMap->setChildSortbyFieldName($columnConfiguration['config']['foreign_sortby']);
216 $columnMap->setDeleteChildObjectsState($columnConfiguration['config']['deleteRelationsWithParent']);
217 $columnMap->setParentKeyFieldName($columnConfiguration['config']['foreign_field']);
218 $columnMap->setParentTableFieldName($columnConfiguration['config']['foreign_table_field']);
219 } elseif (array_key_exists('MM', $columnConfiguration['config'])) {
220 // TODO support for MM_insert_fields and MM_match_fields
221 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY);
222 $columnMap->setChildClassName($this->determineChildClassName($columnConfiguration));
223 $columnMap->setChildTableName($columnConfiguration['config']['foreign_table']);
224 $columnMap->setRelationTableName($columnConfiguration['config']['MM']);
225 if (is_array($columnConfiguration['config']['MM_match_fields'])) {
226 $columnMap->setRelationTableMatchFields($columnConfiguration['config']['MM_match_fields']);
227 }
228 $columnMap->setRelationTableWhereStatement($columnConfiguration['config']['MM_table_where']);
229 // TODO We currently do not support multi table relationships
230 if ($columnConfiguration['config']['MM_opposite_field']) {
231 $columnMap->setParentKeyFieldName('uid_foreign');
232 $columnMap->setChildKeyFieldName('uid_local');
233 $columnMap->setChildSortByFieldName('sorting_foreign');
234 } else {
235 $columnMap->setParentKeyFieldName('uid_local');
236 $columnMap->setChildKeyFieldName('uid_foreign');
237 $columnMap->setChildSortByFieldName('sorting');
238 }
239 } else {
240 $columnMap->setTypeOfRelation(Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE);
241 }
242 }
243 }
244
245 /**
246 * This function determines the child class name. It can either be defined as foreign_class in the column configuration (TCA)
247 * or it must be defined in the extbase framework configuration (reverse mapping from tableName to className).
248 *
249 * @param $columnConfiguration The column configuration (from TCA)
250 * @return string The class name of the related child object
251 */
252 protected function determineChildClassName($columnConfiguration) {
253 $foreignClassName = '';
254 if (is_string($columnConfiguration['config']['foreign_class']) && (strlen($columnConfiguration['config']['foreign_class']) > 0)) {
255 $foreignClassName = $columnConfiguration['config']['foreign_class'];
256 } else {
257 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
258 // TODO Apply a cache to increase performance (profile first)
259 foreach ($extbaseSettings['persistence']['classes'] as $className => $classConfiguration) {
260 if ($classConfiguration['mapping']['tableName'] === $columnConfiguration['config']['foreign_table']) {
261 $foreignClassName = $className;
262 break;
263 }
264 }
265 }
266 // TODO Throw exception if no appropriate class name was found
267 return $foreignClassName;
268 }
269
270 /**
271 * Sets the column maps.
272 *
273 * @param array $columnMaps The column maps stored in a flat array.
274 * @return void
275 */
276 public function setColumnMaps(array $columnMaps) {
277 $this->columnMaps = $columnMaps;
278 }
279
280 /**
281 * Adds a given column map to the data map.
282 *
283 * @param Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap The column map
284 * @return void
285 */
286 public function addColumnMap(Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
287 $this->columnMaps[$columnMap->getPropertyName()] = $columnMap;
288 }
289
290 /**
291 * Builds a column map out of the given column name, type of value (optional), and type of
292 * relation (optional) and adds it to the data map.
293 *
294 * @param string $columnName The column name
295 * @param string $propertyName The property name
296 * @param string $propertyType The type of value (default: string)
297 * @param string $typeOfRelation The type of relation (default: none)
298 * @return Tx_Extbase_Persistence_Mapper_DataMap Returns itself for a fluent interface
299 */
300 public function addColumn($columnName, $propertyName = '', $propertyType = Tx_Extbase_Persistence_PropertyType::STRING, $typeOfRelation = Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_NONE) {
301 if (empty($propertyName)) {
302 $propertyName = Tx_Extbase_Utility_Extension::convertUnderscoredToLowerCamelCase($columnName);
303 }
304
305 $columnMap = new Tx_Extbase_Persistence_Mapper_ColumnMap($columnName, $propertyName);
306 $columnMap->setPropertyType($propertyType);
307 $columnMap->setTypeOfRelation($typeOfRelation);
308 $this->addColumnMap($columnMap);
309 return $this;
310 }
311
312 /**
313 * Returns all column maps
314 *
315 * @return array The column maps
316 */
317 public function getColumnMaps() {
318 return $this->columnMaps;
319 }
320
321 /**
322 * Returns the column map corresponding to the given property name.
323 *
324 * @param string $propertyName
325 * @return Tx_Extbase_Persistence_Mapper_ColumnMap|NULL The column map or NULL if no corresponding column map was found.
326 */
327 public function getColumnMap($propertyName) {
328 return $this->columnMaps[$propertyName];
329 }
330
331 /**
332 * Returns TRUE if the property is persistable (configured in $TCA)
333 *
334 * @param string $propertyName The property name
335 * @return boolean TRUE if the property is persistable (configured in $TCA)
336 */
337 public function isPersistableProperty($propertyName) {
338 return isset($this->columnMaps[$propertyName]);
339 }
340
341 /**
342 * Check if versioning is enabled .
343 *
344 * @return boolean
345 */
346 public function isVersionable() {
347 return ($GLOBALS['TCA'] [$this->tableName] ['ctrl'] ['versioningWS'] === '1');
348 }
349
350 /**
351 * Returns TRUE if the table has a pid column holding the id of the page the record is virtually stored on.
352 * Currently we don't support tables without a pid column.
353 *
354 * @return boolean The result
355 */
356 public function hasPidColumn() {
357 // TODO Should we implement a check for having a pid column?
358 return TRUE;
359 }
360
361 /**
362 * Returns the name of a column holding the timestamp the record was modified
363 *
364 * @return string The field name
365 */
366 public function getTimestampColumnName() {
367 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp'];
368 }
369
370 /**
371 * Returns TRUE if the table has a column holding the timestamp the record was modified
372 *
373 * @return boolean The result
374 */
375 public function hasTimestampColumn() {
376 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['tstamp']);
377 }
378
379 /**
380 * Returns the name of a column holding the creation date timestamp
381 *
382 * @return string The field name
383 */
384 public function getCreationDateColumnName() {
385 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate'];
386 }
387
388 /**
389 * Returns TRUE if the table has olumn holding the creation date timestamp
390 *
391 * @return boolean The result
392 */
393 public function hasCreationDateColumn() {
394 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['crdate']);
395 }
396
397 /**
398 * Returns the name of a column holding the uid of the back-end user who created this record
399 *
400 * @return string The field name
401 */
402 public function getCreatorUidColumnName() {
403 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id'];
404 }
405
406 /**
407 * Returns TRUE if the table has a column holding the uid of the back-end user who created this record
408 *
409 * @return boolean The result
410 */
411 public function hasCreatorUidColumn() {
412 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['cruser_id']);
413 }
414
415 /**
416 * Returns the name of a column indicating the 'deleted' state of the row
417 *
418 * @return string The field name
419 */
420 public function getDeletedColumnName() {
421 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete'];
422 }
423
424 /**
425 * Returns TRUE if the table has a column indicating the 'deleted' state of the row
426 *
427 * @return boolean The result
428 */
429 public function hasDeletedColumn() {
430 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['delete']);
431 }
432
433 /**
434 * Returns the name of a column indicating the 'hidden' state of the row
435 *
436 * @return string The field name
437 */
438 public function getHiddenColumnName() {
439 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled'];
440 }
441
442 /**
443 * Returns TRUE if the table has a column indicating the 'hidden' state of the row
444 *
445 * @return boolean The result
446 */
447 public function hasHiddenColumn() {
448 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['disabled']);
449 }
450
451 /**
452 * Returns the name of a column holding the timestamp the record should not displayed before
453 *
454 * @return string The field name
455 */
456 public function getStartTimeColumnName() {
457 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime'];
458 }
459
460 /**
461 * Returns TRUE if the table has a column holding the timestamp the record should not displayed before
462 *
463 * @return boolean The result
464 */
465 public function hasStartTimeColumn() {
466 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['starttime']);
467 }
468
469 /**
470 * Returns the name of a column holding the timestamp the record should not displayed afterwards
471 *
472 * @return string The field name
473 */
474 public function getEndTimeColumnName() {
475 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime'];
476 }
477
478 /**
479 * Returns TRUE if the table has a column holding the timestamp the record should not displayed afterwards
480 *
481 * @return boolean The result
482 */
483 public function hasEndTimeColumn() {
484 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['endtime']);
485 }
486
487 /**
488 * Returns the name of a column holding the uid of the front-end user group which is allowed to edit this record
489 *
490 * @return string The field name
491 */
492 public function getFrontEndUserGroupColumnName() {
493 return $GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group'];
494 }
495
496 /**
497 * Returns TRUE if the table has a column holding the uid of the front-end user group which is allowed to edit this record
498 *
499 * @return boolean The result
500 */
501 public function hasFrontEndUserGroupColumn() {
502 return !empty($GLOBALS['TCA'][$this->getTableName()]['ctrl']['enablecolumns']['fe_group']);
503 }
504
505 /**
506 * Converts a field name to the property name. It respects property name aliases defined in $TCA.
507 *
508 * @param string $fieldName The field name
509 * @return string $propertyName The property name
510 */
511 public function convertFieldNameToPropertyName($fieldName) {
512 $propertyName = $fieldName;
513 return $propertyName; // TODO Implement aliases for field names (see also convertPropertyNameToFieldName())
514 }
515
516 /**
517 * Converts a preoperty name to the field name. It respects property name aliases defined in $TCA.
518 *
519 * @param string $fieldName The field name
520 * @return string $propertyName The property name
521 */
522 public function convertPropertyNameToFieldName($propertyName) {
523 $fieldName = $propertyName;
524 return $fieldName;
525 }
526
527 /**
528 * Converts the given string into the given type
529 *
530 * @param integer $type one of the constants defined in Tx_Extbase_Persistence_PropertyType
531 * @param string $string a string representing a value of the given type
532 *
533 * @return string|int|float|DateTime|boolean
534 */
535 public function convertFieldValueToPropertyValue($type, $string) {
536 switch ($type) {
537 case Tx_Extbase_Persistence_PropertyType::LONG:
538 return (int) $string;
539 case Tx_Extbase_Persistence_PropertyType::DOUBLE:
540 case Tx_Extbase_Persistence_PropertyType::DECIMAL:
541 return (float) $string;
542 case Tx_Extbase_Persistence_PropertyType::DATE:
543 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
544 case Tx_Extbase_Persistence_PropertyType::BOOLEAN:
545 return (boolean) $string;
546 default:
547 return $string;
548 }
549 }
550
551 /**
552 * Converts a value from a property type to a database field type
553 *
554 * @param mixed $propertyValue The property value
555 * @return mixed The converted value
556 */
557 public function convertPropertyValueToFieldValue($propertyValue) {
558 if (is_bool($propertyValue)) {
559 $convertedValue = $propertyValue ? 1 : 0;
560 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
561 $convertedValue = $propertyValue->getUid();
562 } elseif (is_a($propertyValue, 'DateTime')) {
563 $convertedValue = $propertyValue->format('U');
564 } elseif (is_int($propertyValue)) {
565 $convertedValue = $propertyValue;
566 } else {
567 $convertedValue = $propertyValue;
568 }
569 return $convertedValue;
570 }
571
572 }