Add Extbase 1.0.1 to TYPO3core. Do NOT make changes inside! See misc/core_svn_rules...
[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 manager
100 *
101 * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
102 * @return void
103 */
104 public function injectPersistenceManager(Tx_Extbase_Persistence_ManagerInterface $persistenceManager) {
105 $this->QOMFactory = $persistenceManager->getBackend()->getQOMFactory();
106 $this->persistenceSession = $persistenceManager->getSession();
107 }
108
109 /**
110 * Injects the Reflection Service
111 *
112 * @param Tx_Extbase_Reflection_Service
113 * @return void
114 */
115 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
116 $this->reflectionService = $reflectionService;
117 }
118
119 /**
120 * Maps the (aggregate root) rows and registers them as reconstituted
121 * with the session.
122 *
123 * @param Tx_Extbase_Persistence_RowIteratorInterface $rows
124 * @return array
125 */
126 public function map($className, Tx_Extbase_Persistence_RowIteratorInterface $rows) {
127 $objects = array();
128 foreach ($rows as $row) {
129 $objects[] = $this->mapSingleRow($className, $row);
130 }
131 return $objects;
132 }
133
134 /**
135 * Maps a single node into the object it represents
136 *
137 * @param Tx_Extbase_Persistence_RowInterface $node
138 * @return object
139 */
140 protected function mapSingleRow($className, Tx_Extbase_Persistence_RowInterface $row) {
141 if ($this->identityMap->hasIdentifier($row->getValue('uid'), $className)) {
142 $object = $this->identityMap->getObjectByIdentifier($row->getValue('uid'), $className);
143 } else {
144 $object = $this->createEmptyObject($className);
145 $this->thawProperties($object, $row);
146 $this->identityMap->registerObject($object, $object->getUid());
147 $object->_memorizeCleanState();
148 $this->persistenceSession->registerReconstitutedObject($object);
149 }
150 return $object;
151 }
152
153 /**
154 * Creates a skeleton of the specified object
155 *
156 * @param string $className Name of the class to create a skeleton for
157 * @return object The object skeleton
158 */
159 protected function createEmptyObject($className) {
160 // Note: The class_implements() function also invokes autoload to assure that the interfaces
161 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
162 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);
163 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
164 return $object;
165 }
166
167 /**
168 * Sets the given properties on the object.
169 *
170 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on
171 * @param Tx_Extbase_Persistence_RowInterface $row
172 * @return void
173 */
174 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_Persistence_RowInterface $row) {
175 $className = get_class($object);
176 $dataMap = $this->getDataMap($className);
177 $properties = $object->_getProperties();
178 $object->_setProperty('uid', $row->getValue('uid'));
179 foreach ($properties as $propertyName => $propertyValue) {
180 if (!$dataMap->isPersistableProperty($propertyName)) continue;
181 $columnMap = $dataMap->getColumnMap($propertyName);
182 $columnName = $columnMap->getColumnName();
183 $propertyValue = NULL;
184 $propertyType = $columnMap->getPropertyType();
185 switch ($propertyType) {
186 case Tx_Extbase_Persistence_PropertyType::STRING;
187 case Tx_Extbase_Persistence_PropertyType::DATE;
188 case Tx_Extbase_Persistence_PropertyType::LONG;
189 case Tx_Extbase_Persistence_PropertyType::DOUBLE;
190 case Tx_Extbase_Persistence_PropertyType::BOOLEAN;
191 if ($row->hasValue($columnName)) {
192 $rawPropertyValue = $row->getValue($columnName);
193 $propertyValue = $dataMap->convertFieldValueToPropertyValue($propertyType, $rawPropertyValue);
194 }
195 break;
196 case (Tx_Extbase_Persistence_PropertyType::REFERENCE):
197 if (is_null($row->getValue($columnName))) {
198 $propertyValue = NULL;
199 } else {
200 $fieldValue = $row->getValue($columnMap->getColumnName());
201 $result = $this->fetchRelated($object, $propertyName, $fieldValue);
202 $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $result);
203 }
204 break;
205 default:
206 // FIXME throw exception
207 break;
208 }
209 $object->_setProperty($propertyName, $propertyValue);
210 }
211 }
212
213 /**
214 * Fetches a collection of objects related to a property of a parent object
215 *
216 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
217 * @param string $propertyName The name of the proxied property in it's parent
218 * @param mixed $fieldValue The raw field value.
219 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
220 * @return mixed The result
221 */
222 public function fetchRelated(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue = '', $enableLazyLoading = TRUE) {
223 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
224 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
225 if ($enableLazyLoading === TRUE && ($propertyMetaData['lazy'] || ($columnMap->getLoadingStrategy() !== Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_EAGER))) {
226 if (($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') || ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_LAZY_STORAGE)) {
227 $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyObjectStorage', $parentObject, $propertyName, $fieldValue);
228 } else {
229 $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue);
230 }
231 } else {
232 $result = $this->fetchRelatedEager($parentObject, $propertyName, $fieldValue);
233 }
234 return $result;
235 }
236
237 /**
238 * Fetches the related objects from the storage backend.
239 *
240 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
241 * @param string $propertyName The name of the proxied property in it's parent
242 * @param mixed $fieldValue The raw field value.
243 * @return void
244 */
245 protected function fetchRelatedEager(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue = '') {
246 if ($fieldValue === '') return array();
247 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
248 return $query->execute();
249 }
250
251 /**
252 * Builds and returns the prepared query, ready to be executed.
253 *
254 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
255 * @param string $propertyName
256 * @param string $fieldValue
257 * @return void
258 */
259 protected function getPreparedQuery(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue = '') {
260 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
261 $queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
262 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
263 $childSortByFieldName = $columnMap->getChildSortByFieldName();
264 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
265 $query = $queryFactory->create($this->getType($parentObject, $propertyName));
266 $query->matching($query->withUid(intval($fieldValue)));
267 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
268 $query = $queryFactory->create($this->getElementType($parentObject, $propertyName));
269 // 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
270 // enableFields and storage page settings.
271 $query->getQuerySettings()->setRespectStoragePage(FALSE);
272 if (!empty($childSortByFieldName)) {
273 $query->setOrderings(array($childSortByFieldName => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING));
274 }
275 if (isset($parentKeyFieldName)) {
276 $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()));
277 } else {
278 $query->matching($query->equals('uid', t3lib_div::intExplode(',', $fieldValue)));
279 }
280 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
281 $query = $queryFactory->create($this->getElementType($parentObject, $propertyName));
282 // 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
283 // enableFields and storage page settings.
284 $query->getQuerySettings()->setRespectStoragePage(FALSE);
285 $relationTableName = $columnMap->getRelationTableName();
286 $left = $this->QOMFactory->selector(NULL, $relationTableName);
287 $childClassName = $this->getElementType($parentObject, $propertyName);
288 $childTableName = $columnMap->getChildTableName();
289 $right = $this->QOMFactory->selector($childClassName, $childTableName);
290 $joinCondition = $this->QOMFactory->equiJoinCondition($relationTableName, $columnMap->getChildKeyFieldName(), $childTableName, 'uid');
291 $source = $this->QOMFactory->join(
292 $left,
293 $right,
294 Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER,
295 $joinCondition
296 );
297 $query->setSource($source);
298 if (!empty($childSortByFieldName)) {
299 $query->setOrderings(array($relationTableName . '.' . $childSortByFieldName => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING));
300 }
301 $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()));
302 } else {
303 throw new Tx_Extbase_Persistence_Exception('Could not determine type of relation.', 1252502725);
304 }
305 return $query;
306 }
307
308 /**
309 * Returns the given result as property value of the specified property type.
310 *
311 * @param mixed $result The result could be an object or an ObjectStorage
312 * @param array $propertyMetaData The property meta data
313 * @return void
314 */
315 public function mapResultToPropertyValue(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $result) {
316 if ($result instanceof Tx_Extbase_Persistence_LoadingStrategyInterface) {
317 $propertyValue = $result;
318 } else {
319 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
320 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
321 if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'Tx_Extbase_Persistence_ObjectStorage'))) {
322 $elementType = $this->getElementType($parentObject, $propertyName);
323 $objects = array();
324 foreach ($result as $value) {
325 $objects[] = $value;
326 }
327
328 if ($propertyMetaData['type'] === 'ArrayObject') {
329 $propertyValue = new ArrayObject($objects);
330 } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') {
331 $propertyValue = new Tx_Extbase_Persistence_ObjectStorage();
332 foreach ($objects as $object) {
333 $propertyValue->attach($object);
334 }
335 } else {
336 $propertyValue = $objects;
337 }
338 } elseif (strpos($propertyMetaData['type'], '_') !== FALSE) {
339 $propertyValue = current($result);
340 }
341 }
342 return $propertyValue;
343 }
344
345 /**
346 * Counts the number of related objects assigned to a property of a parent object
347 *
348 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
349 * @param string $propertyName The name of the proxied property in it's parent
350 * @param mixed $fieldValue The raw field value.
351 */
352 public function countRelated(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue = '') {
353 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue);
354 return $query->count();
355 }
356
357 /**
358 * Delegates the call to the Data Map.
359 * Returns TRUE if the property is persistable (configured in $TCA)
360 *
361 * @param string $className The property name
362 * @param string $propertyName The property name
363 * @return boolean TRUE if the property is persistable (configured in $TCA)
364 */
365 public function isPersistableProperty($className, $propertyName) {
366 $dataMap = $this->getDataMap($className);
367 return $dataMap->isPersistableProperty($propertyName);
368 }
369
370 /**
371 * Returns a data map for a given class name
372 *
373 * @param string $className The class name you want to fetch the Data Map for
374 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
375 */
376 public function getDataMap($className) {
377 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);
378 if (!isset($this->dataMaps[$className])) {
379 // FIXME This is too expensive for table name aliases -> implement a DataMapBuilder (knowing the aliases defined in $TCA)
380 $columnMapping = array();
381 $tableName = '';
382 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
383 if (is_array($extbaseSettings['persistence']['classes'][$className])) {
384 $persistenceSettings = $extbaseSettings['persistence']['classes'][$className];
385 if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
386 $tableName = $persistenceSettings['mapping']['tableName'];
387 }
388 if (is_array($persistenceSettings['mapping']['columns'])) {
389 $columnMapping = $persistenceSettings['mapping']['columns'];
390 }
391 } elseif (class_exists($className)) {
392 foreach (class_parents($className) as $parentClassName) {
393 $persistenceSettings = $extbaseSettings['persistence']['classes'][$parentClassName];
394 if (is_array($persistenceSettings)) {
395 if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
396 $tableName = $persistenceSettings['mapping']['tableName'];
397 }
398 if (is_array($persistenceSettings['mapping']['columns'])) {
399 $columnMapping = $persistenceSettings['mapping']['columns'];
400 }
401 }
402 break;
403 }
404 } else {
405 throw new Tx_Extbase_Persistence_Exception('Could not determine a Data Map for given class name.', 1256067130);
406 }
407
408 $dataMap = new Tx_Extbase_Persistence_Mapper_DataMap($className, $tableName, $columnMapping);
409 $this->dataMaps[$className] = $dataMap;
410 }
411 return $this->dataMaps[$className];
412 }
413
414 /**
415 * Returns the selector (table) name for a given class name.
416 *
417 * @param string $className
418 * @return string The selector name
419 */
420 public function convertClassNameToTableName($className) {
421 return $this->getDataMap($className)->getTableName();
422 }
423
424 /**
425 * Returns the column name for a given property name of the specified class.
426 *
427 * @param string $className
428 * @param string $propertyName
429 * @return string The column name
430 */
431 public function convertPropertyNameToColumnName($propertyName, $className = '') {
432 if (!empty($className)) {
433 $dataMap = $this->getDataMap($className);
434 if ($dataMap !== NULL) {
435 $columnMap = $dataMap->getColumnMap($propertyName);
436 if ($columnMap !== NULL) {
437 return $columnMap->getColumnName();
438 }
439 }
440 }
441 return Tx_Extbase_Utility_Extension::convertCamelCaseToLowerCaseUnderscored($propertyName);
442 }
443
444 /**
445 * Returns the type of a child object.
446 *
447 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
448 * @param string $propertyName The name of the proxied property in it's parent
449 * @return string The class name of the child object
450 */
451 protected function getType(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName) {
452 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
453 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
454 $childClassName = $columnMap->getChildClassName();
455 if (!empty($childClassName)) {
456 $elementType = $childClassName;
457 } elseif (!empty($propertyMetaData['type'])) {
458 $elementType = $propertyMetaData['type'];
459 } else {
460 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Could not determine the child object object type.', 1251315967);
461 }
462 return $elementType;
463 }
464
465 /**
466 * Returns the type of the elements inside an ObjectStorage or array.
467 *
468 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
469 * @param string $propertyName The name of the proxied property in it's parent
470 * @return string The class name of the elements inside an ObjectStorage
471 */
472 protected function getElementType(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName) {
473 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName);
474 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
475 $childClassName = $columnMap->getChildClassName();
476 if (!empty($childClassName)) {
477 $elementType = $childClassName;
478 } elseif (!empty($propertyMetaData['elementType'])) {
479 $elementType = $propertyMetaData['elementType'];
480 } else {
481 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Could not determine the type of the contained objects.', 1251315966);
482 }
483 return $elementType;
484 }
485
486 }
487 ?>