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 mapper to map database tables configured in $TCA on domain objects.
29 * @subpackage Persistence\Mapper
32 class Tx_Extbase_Persistence_Mapper_DataMapper
implements t3lib_Singleton
{
35 * @var Tx_Extbase_Persistence_IdentityMap
37 protected $identityMap;
40 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactory
42 protected $QOMFactory;
45 * @var Tx_Extbase_Persistence_Session
47 protected $persistenceSession;
50 * A reference to the page select object providing methods to perform language and work space overlays
52 * @var t3lib_pageSelect
54 protected $pageSelectObject;
61 protected $dataMaps = array();
64 * @var Tx_Extbase_Persistence_QueryFactoryInterface
66 protected $queryFactory;
69 * The TYPO3 reference index object
73 protected $referenceIndex;
76 * Constructs a new mapper
79 public function __construct() {
80 $this->queryFactory
= t3lib_div
::makeInstance('Tx_Extbase_Persistence_QueryFactory');
84 * Injects the identity map
86 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
89 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap
$identityMap) {
90 $this->identityMap
= $identityMap;
94 * Injects the persistence manager
96 * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
99 public function injectPersistenceManager(Tx_Extbase_Persistence_ManagerInterface
$persistenceManager) {
100 $this->QOMFactory
= $persistenceManager->getBackend()->getQOMFactory();
101 $this->persistenceSession
= $persistenceManager->getSession();
105 * Maps the (aggregate root) rows and registers them as reconstituted
108 * @param Tx_Extbase_Persistence_RowIteratorInterface $rows
111 public function map($className, Tx_Extbase_Persistence_RowIteratorInterface
$rows) {
113 foreach ($rows as $row) {
114 $objects[] = $this->mapSingleRow($className, $row);
120 * Maps a single node into the object it represents
122 * @param Tx_Extbase_Persistence_RowInterface $node
125 protected function mapSingleRow($className, Tx_Extbase_Persistence_RowInterface
$row) {
126 if ($this->identityMap
->hasIdentifier($row['uid'], $className)) {
127 $object = $this->identityMap
->getObjectByIdentifier($row['uid'], $className);
129 $object = $this->createEmptyObject($className);
130 $this->thawProperties($object, $row);
131 $this->identityMap
->registerObject($object, $object->getUid());
132 $object->_memorizeCleanState();
133 $this->persistenceSession
->registerReconstitutedObject($object);
139 * Creates a skeleton of the specified object
141 * @param string $className Name of the class to create a skeleton for
142 * @return object The object skeleton
145 protected function createEmptyObject($className) {
146 // Note: The class_implements() function also invokes autoload to assure that the interfaces
147 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
148 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);
149 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
154 * Sets the given properties on the object.
156 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on
157 * @param Tx_Extbase_Persistence_RowInterface $row
160 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface
$object, Tx_Extbase_Persistence_RowInterface
$row) {
161 $className = get_class($object);
162 $dataMap = $this->getDataMap($className);
163 $properties = $object->_getProperties();
164 $object->_setProperty('uid', $row['uid']);
165 foreach ($properties as $propertyName => $propertyValue) {
166 if (!$dataMap->isPersistableProperty($propertyName)) continue;
167 $columnMap = $dataMap->getColumnMap($propertyName);
168 $columnName = $columnMap->getColumnName();
169 $propertyValue = NULL;
170 $propertyType = $columnMap->getPropertyType();
171 switch ($propertyType) {
172 case Tx_Extbase_Persistence_PropertyType
::STRING;
173 case Tx_Extbase_Persistence_PropertyType
::DATE
;
174 case Tx_Extbase_Persistence_PropertyType
::LONG
;
175 case Tx_Extbase_Persistence_PropertyType
::DOUBLE;
176 case Tx_Extbase_Persistence_PropertyType
::BOOLEAN
;
177 if (isset($row[$columnName])) {
178 $rawPropertyValue = $row[$columnName];
179 $propertyValue = $dataMap->convertFieldValueToPropertyValue($propertyType, $rawPropertyValue);
182 case (Tx_Extbase_Persistence_PropertyType
::REFERENCE
):
183 if (!is_null($row[$columnName])) {
184 $propertyValue = $this->mapRelatedObjects($object, $propertyName, $row, $columnMap);
186 $propertyValue = NULL;
189 // FIXME we have an object to handle... -> exception
191 // SK: We should throw an exception as this point as there was an undefined propertyType we can not handle.
192 if (isset($row[$columnName])) {
193 $property = $row[$columnName];
194 if (is_object($property)) {
195 $propertyValue = $this->mapObject($property);
196 // SK: THIS case can not happen I think. At least $this->mapObject() is not available.
198 // SK: This case does not make sense either. $this-mapSingleRow has a different signature
199 $propertyValue = $this->mapSingleRow($className, $property);
205 $object->_setProperty($propertyName, $propertyValue);
210 * Maps related objects to an ObjectStorage
212 * @param object $parentObject The parent object for the mapping result
213 * @param string $propertyName The target property name for the mapping result
214 * @param Tx_Extbase_Persistence_RowInterface $row The actual database row
215 * @param int $loadingStrategy The loading strategy; one of Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_*
216 * @return array|Tx_Extbase_Persistence_ObjectStorage|Tx_Extbase_Persistence_LazyLoadingProxy|another implementation of a loading strategy
218 // FIXME There is a recursion problem with post1 -> post2 and post2 -> post1
219 protected function mapRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity
$parentObject, $propertyName, Tx_Extbase_Persistence_RowInterface
$row, Tx_Extbase_Persistence_Mapper_ColumnMap
$columnMap) {
220 $dataMap = $this->getDataMap(get_class($parentObject));
221 $columnMap = $dataMap->getColumnMap($propertyName);
222 $fieldValue = $row[$columnMap->getColumnName()];
223 if ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap
::STRATEGY_PROXY
) {
224 // TODO Remove dependency to the loading strategy implementation
225 $result = t3lib_div
::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue, $columnMap);
227 $result = $this->fetchRelatedObjects( $parentObject, $propertyName, $fieldValue, $columnMap);
234 * Fetches a collection of objects related to a property of a parent object
236 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
237 * @param string $propertyName The name of the proxied property in it's parent
238 * @param mixed $fieldValue The raw field value.
239 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
242 public function fetchRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity
$parentObject, $propertyName, $fieldValue, Tx_Extbase_Persistence_Mapper_ColumnMap
$columnMap) {
243 $queryFactory = t3lib_div
::makeInstance('Tx_Extbase_Persistence_QueryFactory');
244 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_ONE
) {
245 $query = $queryFactory->create($columnMap->getChildClassName());
246 // 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
247 // enableFields and storage page settings.
248 $query->getQuerySettings()->setRespectStoragePage(FALSE);
249 $result = current($query->matching($query->withUid((int)$fieldValue))->execute());
250 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_MANY
) {
251 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
252 $query = $queryFactory->create($columnMap->getChildClassName());
253 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
254 if (isset($parentKeyFieldName)) {
255 $objects = $query->matching($query->equals($columnMap->getParentKeyFieldName(), $parentObject->getUid()))->execute();
257 // TODO Is it necessary to "normalize" and intval the list?
258 $uids = t3lib_div
::trimExplode(',', $fieldValue);
260 foreach ($uids as $uid) {
261 $uidArray[] = (int)$uid;
263 $uids = implode(',', $uidArray);
264 // FIXME Using statement() is only a preliminary solution
265 $objects = $query->statement('SELECT * FROM ' . $columnMap->getChildTableName() . ' WHERE uid IN (' . $uids . ')')->execute();
267 foreach ($objects as $object) {
268 $objectStorage->attach($object);
270 $result = $objectStorage;
271 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
) {
272 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
273 $relationTableName = $columnMap->getRelationTableName();
274 $left = $this->QOMFactory
->selector(NULL, $relationTableName);
275 $childClassName = $columnMap->getChildClassName();
276 $childTableName = $columnMap->getChildTableName();
277 $right = $this->QOMFactory
->selector($childClassName, $childTableName);
278 $joinCondition = $this->QOMFactory
->equiJoinCondition($relationTableName, $columnMap->getChildKeyFieldName(), $childTableName, 'uid');
279 $source = $this->QOMFactory
->join(
282 Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface
::JCR_JOIN_TYPE_INNER
,
285 $query = $queryFactory->create($columnMap->getChildClassName());
286 $query->setSource($source);
287 $objects = $query->matching($query->equals($columnMap->getParentKeyFieldName(), $parentObject->getUid()))->execute();
288 foreach ($objects as $object) {
289 $objectStorage->attach($object);
291 $result = $objectStorage;
298 * Delegates the call to the Data Map.
299 * Returns TRUE if the property is persistable (configured in $TCA)
301 * @param string $className The property name
302 * @param string $propertyName The property name
303 * @return boolean TRUE if the property is persistable (configured in $TCA)
305 public function isPersistableProperty($className, $propertyName) {
306 $dataMap = $this->getDataMap($className);
307 return $dataMap->isPersistableProperty($propertyName);
311 * Returns a data map for a given class name
313 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
315 public function getDataMap($className) {
316 if (is_string($className) && strlen($className) > 0) {
317 if (empty($this->dataMaps
[$className])) {
318 // FIXME This is a costy for table name aliases -> implement a DataMapBuilder (knowing the aliases defined in $TCA)
320 $extbaseSettings = Tx_Extbase_Dispatcher
::getExtbaseFrameworkConfiguration();
321 if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['mapping']['tableName'])) {
322 $tableName = $extbaseSettings['persistence']['classes'][$className]['mapping']['tableName'];
324 foreach (class_parents($className) as $parentClassName) {
325 if (isset($extbaseSettings['persistence']['classes'][$parentClassName]) && !empty($extbaseSettings['persistence']['classes'][$parentClassName]['mapping']['tableName'])) {
326 $tableName = $extbaseSettings['persistence']['classes'][$parentClassName]['mapping']['tableName'];
329 // TODO throw Exception
332 if (is_array($extbaseSettings['persistence']['classes'][$parentClassName]['mapping']['columns'])) {
333 $mapping = $extbaseSettings['persistence']['classes'][$parentClassName]['mapping']['columns'];
336 $dataMap = new Tx_Extbase_Persistence_Mapper_DataMap($className, $tableName, $mapping);
337 $this->dataMaps
[$className] = $dataMap;
339 return $this->dataMaps
[$className];
346 * Returns the selector (table) name for a given class name.
348 * @param string $className
349 * @return string The selector name
351 public function convertClassNameToTableName($className) {
352 return $this->getDataMap($className)->getTableName();
356 * Returns the column name for a given property name of the specified class.
358 * @param string $className
359 * @param string $propertyName
360 * @return string The column name
362 public function convertPropertyNameToColumnName($propertyName, $className = '') {
363 if (!empty($className)) {
364 $dataMap = $this->getDataMap($className);
365 if ($dataMap !== NULL) {
366 $columnMap = $dataMap->getColumnMap($propertyName);
367 if ($columnMap !== NULL) {
368 return $columnMap->getColumnName();
372 return Tx_Extbase_Utility_Extension
::convertCamelCaseToLowerCaseUnderscored($propertyName);