[+TASK] Extbase (DomainObject): Added a "private" property _localizationParentUid...
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Mapper / DataMapper.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 mapper to map database tables configured in $TCA on domain objects.
27 *
28 * @package Extbase
29 * @subpackage Persistence\Mapper
30 * @version $ID:$
31 */
32 class Tx_Extbase_Persistence_Mapper_DataMapper implements t3lib_Singleton {
33
34 /**
35 * @var Tx_Extbase_Persistence_IdentityMap
36 */
37 protected $identityMap;
38
39 /**
40 * @var Tx_Extbase_Reflection_Service
41 */
42 protected $reflectionService;
43
44 /**
45 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactory
46 */
47 protected $QomFactory;
48
49 /**
50 * @var Tx_Extbase_Persistence_Session
51 */
52 protected $persistenceSession;
53
54 /**
55 * A reference to the page select object providing methods to perform language and work space overlays
56 *
57 * @var t3lib_pageSelect
58 **/
59 protected $pageSelectObject;
60
61 /**
62 * Cached data maps
63 *
64 * @var array
65 **/
66 protected $dataMaps = array();
67
68 /**
69 * @var Tx_Extbase_Persistence_QueryFactoryInterface
70 */
71 protected $queryFactory;
72
73 /**
74 * The TYPO3 reference index object
75 *
76 * @var t3lib_refindex
77 **/
78 protected $referenceIndex;
79
80 /**
81 * Constructs a new mapper
82 *
83 */
84 public function __construct() {
85 $this->queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
86 }
87
88 /**
89 * Injects the identity map
90 *
91 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
92 * @return void
93 */
94 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
95 $this->identityMap = $identityMap;
96 }
97
98 /**
99 * Injects the persistence session
100 *
101 * @param Tx_Extbase_Persistence_Session $persistenceSession
102 * @return void
103 */
104 public function injectSession(Tx_Extbase_Persistence_Session $persistenceSession) {
105 $this->persistenceSession = $persistenceSession;
106 }
107
108 /**
109 * Injects the Reflection Service
110 *
111 * @param Tx_Extbase_Reflection_Service
112 * @return void
113 */
114 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
115 $this->reflectionService = $reflectionService;
116 }
117
118 /**
119 * Sets the query object model factory
120 *
121 * @param Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory
122 * @return void
123 */
124 public function setQomFactory(Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory) {
125 $this->qomFactory = $qomFactory;
126 }
127
128 /**
129 * Maps the (aggregate root) rows and registers them as reconstituted
130 * with the session.
131 *
132 * @param Tx_Extbase_Persistence_RowIteratorInterface $rows
133 * @return array
134 */
135 public function map($className, Tx_Extbase_Persistence_RowIteratorInterface $rows) {
136 $objects = array();
137 foreach ($rows as $row) {
138 $objects[] = $this->mapSingleRow($className, $row);
139 }
140 return $objects;
141 }
142
143 /**
144 * Maps a single node into the object it represents
145 *
146 * @param Tx_Extbase_Persistence_RowInterface $node
147 * @return object
148 */
149 protected function mapSingleRow($className, Tx_Extbase_Persistence_RowInterface $row) {
150 if ($this->identityMap->hasIdentifier($row->getValue('uid'), $className)) {
151 $object = $this->identityMap->getObjectByIdentifier($row->getValue('uid'), $className);
152 } else {
153 $object = $this->createEmptyObject($className);
154 $this->thawProperties($object, $row);
155 $this->identityMap->registerObject($object, $object->getUid());
156 $object->_memorizeCleanState();
157 $this->persistenceSession->registerReconstitutedObject($object);
158 }
159 return $object;
160 }
161
162 /**
163 * Creates a skeleton of the specified object
164 *
165 * @param string $className Name of the class to create a skeleton for
166 * @return object The object skeleton
167 */
168 protected function createEmptyObject($className) {
169 // Note: The class_implements() function also invokes autoload to assure that the interfaces
170 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
171 if (!in_array('Tx_Extbase_DomainObject_DomainObjectInterface', class_implements($className))) throw new Tx_Extbase_Object_Exception_CannotReconstituteObject('Cannot create empty instance of the class "' . $className . '" because it does not implement the Tx_Extbase_DomainObject_DomainObjectInterface.', 1234386924);
172 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
173 return $object;
174 }
175
176 /**
177 * Sets the given properties on the object.
178 *
179 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on
180 * @param Tx_Extbase_Persistence_RowInterface $row
181 * @return void
182 */
183 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_Persistence_RowInterface $row) {
184 $className = get_class($object);
185 $dataMap = $this->getDataMap($className);
186 $properties = $object->_getProperties();
187 $localizedUid = $row->getValue('_LOCALIZED_UID');
188 if ($localizedUid !== NULL) {
189 $object->_setProperty('uid', $localizedUid);
190 $object->_setProperty('_localizationParentUid', $row->getValue('uid'));
191 } else {
192 $object->_setProperty('uid', $row->getValue('uid'));
193 }
194 unset($properties['uid']);
195 foreach ($properties as $propertyName => $propertyValue) {
196 if (!$dataMap->isPersistableProperty($propertyName)) continue;
197 $columnMap = $dataMap->getColumnMap($propertyName);
198 $columnName = $columnMap->getColumnName();
199 $propertyValue = NULL;
200
201 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
202 $propertyType = Tx_Extbase_Persistence_PropertyType::valueFromType($propertyMetaData['type']);
203
204 if ($propertyType == Tx_Extbase_Persistence_PropertyType::UNDEFINED) {
205 $propertyType = $columnMap->getPropertyType();
206 }
207
208 switch ($propertyType) {
209 case Tx_Extbase_Persistence_PropertyType::STRING;
210 case Tx_Extbase_Persistence_PropertyType::DATE;
211 case Tx_Extbase_Persistence_PropertyType::LONG;
212 case Tx_Extbase_Persistence_PropertyType::DOUBLE;
213 case Tx_Extbase_Persistence_PropertyType::BOOLEAN;
214 if ($row->hasValue($columnName)) {
215 $rawPropertyValue = $row->getValue($columnName);
216 $propertyValue = $dataMap->convertFieldValueToPropertyValue($propertyType, $rawPropertyValue);
217 }
218 break;
219 case (Tx_Extbase_Persistence_PropertyType::REFERENCE):
220 $propertyValue = $row->getValue($columnName);
221 if (!is_null($propertyValue)) {
222 $fieldValue = $row->getValue($columnName);
223 $result = $this->fetchRelated($object, $propertyName, $fieldValue);
224 $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $result);
225 }
226 break;
227 default:
228 // FIXME throw exception
229 break;
230 }
231 $object->_setProperty($propertyName, $propertyValue);
232 }
233 }
234
235 /**
236 * Fetches a collection of objects related to a property of a parent object
237 *
238 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of
239 * @param string $propertyName The name of the proxied property in it's parent
240 * @param mixed $fieldValue The raw field value.
241 * @param bool $enableLazyLoading A flag indication if the related objects should be lazy loaded
242 * @param bool $performLanguageOverlay A flag indication if the related objects should be localized
243 * @return mixed The result
244 */
245 public function fetchRelated(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $enableLazyLoading = TRUE, $performLanguageOverlay = TRUE) {
246 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
247 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
248 if ($enableLazyLoading === TRUE && ($propertyMetaData['lazy'] || ($columnMap->getLoadingStrategy() !== Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_EAGER))) {
249 if (($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') || ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_LAZY_STORAGE)) {
250 $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyObjectStorage', $parentObject, $propertyName, $fieldValue);
251 } else {
252 $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue);
253 }
254 } else {
255 $result = $this->fetchRelatedEager($parentObject, $propertyName, $fieldValue, $performLanguageOverlay);
256 }
257 return $result;
258 }
259
260 /**
261 * Fetches the related objects from the storage backend.
262 *
263 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of
264 * @param string $propertyName The name of the proxied property in it's parent
265 * @param mixed $fieldValue The raw field value.
266 * @param bool $performLanguageOverlay A flag indication if the related objects should be localized
267 * @return void
268 */
269 protected function fetchRelatedEager(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $performLanguageOverlay = TRUE) {
270 if ($fieldValue === '') return array();
271 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
272 $query->getQuerySettings()->setRespectSysLanguage($performLanguageOverlay);
273 return $query->execute();
274 }
275
276 /**
277 * Builds and returns the prepared query, ready to be executed.
278 *
279 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject
280 * @param string $propertyName
281 * @param string $fieldValue
282 * @return void
283 */
284 protected function getPreparedQuery(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
285 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
286 $queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
287 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
288 $childSortByFieldName = $columnMap->getChildSortByFieldName();
289 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
290 $query = $queryFactory->create($this->getType($parentObject, $propertyName));
291 if (isset($parentKeyFieldName)) {
292 $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()));
293 } else {
294 $query->matching($query->withUid(intval($fieldValue)));
295 }
296 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
297 $query = $queryFactory->create($this->getElementType($parentObject, $propertyName));
298 // TODO: This is an ugly hack, just ignoring the storage page state from here. Actually, the query settings would have to be passed into the DataMapper, so we can respect
299 // enableFields and storage page settings.
300 $query->getQuerySettings()->setRespectStoragePage(FALSE);
301 if (!empty($childSortByFieldName)) {
302 $query->setOrderings(array($childSortByFieldName => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING));
303 }
304 if (isset($parentKeyFieldName)) {
305 $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()));
306 } else {
307 $query->matching($query->equals('uid', t3lib_div::intExplode(',', $fieldValue)));
308 }
309 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
310 $query = $queryFactory->create($this->getElementType($parentObject, $propertyName));
311 // TODO: This is an ugly hack, just ignoring the storage page state from here. Actually, the query settings would have to be passed into the DataMapper, so we can respect
312 // enableFields and storage page settings.
313 $query->getQuerySettings()->setRespectStoragePage(FALSE);
314 $relationTableName = $columnMap->getRelationTableName();
315 $left = $this->qomFactory->selector(NULL, $relationTableName);
316 $childClassName = $this->getElementType($parentObject, $propertyName);
317 $childTableName = $columnMap->getChildTableName();
318 $right = $this->qomFactory->selector($childClassName, $childTableName);
319 $joinCondition = $this->qomFactory->equiJoinCondition($relationTableName, $columnMap->getChildKeyFieldName(), $childTableName, 'uid');
320 $source = $this->qomFactory->join(
321 $left,
322 $right,
323 Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER,
324 $joinCondition
325 );
326
327 $query->setSource($source);
328 if (!empty($childSortByFieldName)) {
329 $query->setOrderings(array($relationTableName . '.' . $childSortByFieldName => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING));
330 }
331
332 // attempt to support MM_match_fields
333 $conditions = $query->equals($parentKeyFieldName, $parentObject->getUid());
334
335 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
336 if (count($relationTableMatchFields)) {
337 foreach($relationTableMatchFields as $relationTableMatchFieldName => $relationTableMatchFieldValue) {
338 $relationMatchCondition = $query->equals($relationTableName . '.' . $relationTableMatchFieldName, $relationTableMatchFieldValue);
339 $conditions = $query->logicalAnd($conditions, $relationMatchCondition);
340 }
341 }
342 $query->matching($conditions);
343
344 } else {
345 throw new Tx_Extbase_Persistence_Exception('Could not determine type of relation.', 1252502725);
346 }
347 return $query;
348 }
349
350 /**
351 * Returns the given result as property value of the specified property type.
352 *
353 * @param mixed $result The result could be an object or an ObjectStorage
354 * @param array $propertyMetaData The property meta data
355 * @return void
356 */
357 public function mapResultToPropertyValue(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $result) {
358 if ($result instanceof Tx_Extbase_Persistence_LoadingStrategyInterface) {
359 $propertyValue = $result;
360 } else {
361 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
362 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
363 if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'Tx_Extbase_Persistence_ObjectStorage'))) {
364 $elementType = $this->getElementType($parentObject, $propertyName);
365 $objects = array();
366 foreach ($result as $value) {
367 $objects[] = $value;
368 }
369
370 if ($propertyMetaData['type'] === 'ArrayObject') {
371 $propertyValue = new ArrayObject($objects);
372 } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') {
373 $propertyValue = new Tx_Extbase_Persistence_ObjectStorage();
374 foreach ($objects as $object) {
375 $propertyValue->attach($object);
376 }
377 } else {
378 $propertyValue = $objects;
379 }
380 } elseif (strpos($propertyMetaData['type'], '_') !== FALSE) {
381 $propertyValue = current($result);
382 }
383 }
384 return $propertyValue;
385 }
386
387 /**
388 * Counts the number of related objects assigned to a property of a parent object
389 *
390 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of
391 * @param string $propertyName The name of the proxied property in it's parent
392 * @param mixed $fieldValue The raw field value.
393 */
394 public function countRelated(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') {
395 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
396 return $query->count();
397 }
398
399 /**
400 * Delegates the call to the Data Map.
401 * Returns TRUE if the property is persistable (configured in $TCA)
402 *
403 * @param string $className The property name
404 * @param string $propertyName The property name
405 * @return boolean TRUE if the property is persistable (configured in $TCA)
406 */
407 public function isPersistableProperty($className, $propertyName) {
408 $dataMap = $this->getDataMap($className);
409 return $dataMap->isPersistableProperty($propertyName);
410 }
411
412 /**
413 * Returns a data map for a given class name
414 *
415 * @param string $className The class name you want to fetch the Data Map for
416 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
417 */
418 public function getDataMap($className) {
419 if (!is_string($className) || strlen($className) === 0) throw new Tx_Extbase_Persistence_Exception('No class name was given to retrieve the Data Map for.', 1251315965);
420 if (!isset($this->dataMaps[$className])) {
421 // FIXME This is too expensive for table name aliases -> implement a DataMapBuilder (knowing the aliases defined in $TCA)
422 $columnMapping = array();
423 $tableName = '';
424 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
425 if (is_array($extbaseSettings['persistence']['classes'][$className])) {
426 $persistenceSettings = $extbaseSettings['persistence']['classes'][$className];
427 if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
428 $tableName = $persistenceSettings['mapping']['tableName'];
429 }
430 if (is_array($persistenceSettings['mapping']['columns'])) {
431 $columnMapping = $persistenceSettings['mapping']['columns'];
432 }
433 } elseif (class_exists($className)) {
434 foreach (class_parents($className) as $parentClassName) {
435 $persistenceSettings = $extbaseSettings['persistence']['classes'][$parentClassName];
436 if (is_array($persistenceSettings)) {
437 if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
438 $tableName = $persistenceSettings['mapping']['tableName'];
439 }
440 if (is_array($persistenceSettings['mapping']['columns'])) {
441 $columnMapping = $persistenceSettings['mapping']['columns'];
442 }
443 }
444 break;
445 }
446 } else {
447 throw new Tx_Extbase_Persistence_Exception('Could not determine a Data Map for given class name.', 1256067130);
448 }
449
450 $dataMap = new Tx_Extbase_Persistence_Mapper_DataMap($className, $tableName, $columnMapping);
451 $this->dataMaps[$className] = $dataMap;
452 }
453 return $this->dataMaps[$className];
454 }
455
456 /**
457 * Returns the selector (table) name for a given class name.
458 *
459 * @param string $className
460 * @return string The selector name
461 */
462 public function convertClassNameToTableName($className) {
463 return $this->getDataMap($className)->getTableName();
464 }
465
466 /**
467 * Returns the column name for a given property name of the specified class.
468 *
469 * @param string $className
470 * @param string $propertyName
471 * @return string The column name
472 */
473 public function convertPropertyNameToColumnName($propertyName, $className = '') {
474 if (!empty($className)) {
475 $dataMap = $this->getDataMap($className);
476 if ($dataMap !== NULL) {
477 $columnMap = $dataMap->getColumnMap($propertyName);
478 if ($columnMap !== NULL) {
479 return $columnMap->getColumnName();
480 }
481 }
482 }
483 return Tx_Extbase_Utility_Extension::convertCamelCaseToLowerCaseUnderscored($propertyName);
484 }
485
486 /**
487 * Returns the type of a child object.
488 *
489 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of
490 * @param string $propertyName The name of the proxied property in it's parent
491 * @return string The class name of the child object
492 */
493 protected function getType(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName) {
494 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
495 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
496 $childClassName = $columnMap->getChildClassName();
497 if (!empty($childClassName)) {
498 $elementType = $childClassName;
499 } elseif (!empty($propertyMetaData['type'])) {
500 $elementType = $propertyMetaData['type'];
501 } else {
502 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Could not determine the child object object type.', 1251315967);
503 }
504 return $elementType;
505 }
506
507 /**
508 * Returns the type of the elements inside an ObjectStorage or array.
509 *
510 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of
511 * @param string $propertyName The name of the proxied property in it's parent
512 * @return string The class name of the elements inside an ObjectStorage
513 */
514 protected function getElementType(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName) {
515 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
516 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
517 $childClassName = $columnMap->getChildClassName();
518 if (!empty($childClassName)) {
519 $elementType = $childClassName;
520 } elseif (!empty($propertyMetaData['elementType'])) {
521 $elementType = $propertyMetaData['elementType'];
522 } else {
523 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Could not determine the type of the contained objects.', 1251315966);
524 }
525 return $elementType;
526 }
527
528 }
529 ?>