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.
32 class Tx_Extbase_Persistence_Mapper_DataMapper
implements t3lib_Singleton
{
35 * @var Tx_Extbase_Persistence_IdentityMap
37 protected $identityMap;
40 * @var Tx_Extbase_Persistence_ManagerInterface
42 protected $persistenceManager;
45 * A reference to the page select object providing methods to perform language and work space overlays
47 * @var t3lib_pageSelect
49 protected $pageSelectObject;
56 protected $dataMaps = array();
59 * @var Tx_Extbase_Persistence_QueryFactoryInterface
61 protected $queryFactory;
64 * The TYPO3 reference index object
68 protected $referenceIndex;
71 * Constructs a new mapper
74 public function __construct() {
75 $this->queryFactory
= t3lib_div
::makeInstance('Tx_Extbase_Persistence_QueryFactory');
79 * Injects the identity map
81 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
84 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap
$identityMap) {
85 $this->identityMap
= $identityMap;
89 * Injects the persistence manager
91 * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
94 public function injectPersistenceManager(Tx_Extbase_Persistence_ManagerInterface
$persistenceManager) {
95 $this->persistenceManager
= $persistenceManager;
96 $this->QOMFactory
= $this->persistenceManager
->getBackend()->getQOMFactory();
100 * Maps the (aggregate root) rows and registers them as reconstituted
103 * @param Tx_Extbase_Persistence_RowIteratorInterface $rows
106 public function map($className, Tx_Extbase_Persistence_RowIteratorInterface
$rows) {
108 foreach ($rows as $row) {
109 $objects[] = $this->mapSingleRow($className, $row);
115 * Maps a single node into the object it represents
117 * @param Tx_Extbase_Persistence_RowInterface $node
120 protected function mapSingleRow($className, Tx_Extbase_Persistence_RowInterface
$row) {
121 if ($this->identityMap
->hasUid($className, $row['uid'])) {
122 $object = $this->identityMap
->getObjectByUid($className, $row['uid']);
124 $object = $this->createEmptyObject($className);
125 $this->thawProperties($object, $row);
126 $this->identityMap
->registerObject($object, $object->getUid());
127 $object->_memorizeCleanState();
133 * Creates a skeleton of the specified object
135 * @param string $className Name of the class to create a skeleton for
136 * @return object The object skeleton
139 protected function createEmptyObject($className) {
140 // Note: The class_implements() function also invokes autoload to assure that the interfaces
141 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
142 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);
143 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
148 * Sets the given properties on the object.
150 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on
151 * @param Tx_Extbase_Persistence_RowInterface $row
154 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface
$object, Tx_Extbase_Persistence_RowInterface
$row) {
155 $className = get_class($object);
156 $dataMap = $this->getDataMap($className);
157 $properties = $object->_getProperties();
158 $object->_setProperty('uid', $row['uid']);
159 foreach ($properties as $propertyName => $propertyValue) {
160 if (!$dataMap->isPersistableProperty($propertyName)) continue;
161 $columnMap = $dataMap->getColumnMap($propertyName);
162 $columnName = $columnMap->getColumnName();
163 $propertyValue = NULL;
164 $propertyType = $columnMap->getPropertyType();
165 switch ($propertyType) {
166 case Tx_Extbase_Persistence_PropertyType
::STRING;
167 case Tx_Extbase_Persistence_PropertyType
::DATE
;
168 case Tx_Extbase_Persistence_PropertyType
::LONG
;
169 case Tx_Extbase_Persistence_PropertyType
::DOUBLE;
170 case Tx_Extbase_Persistence_PropertyType
::BOOLEAN
;
171 if (isset($row[$columnName])) {
172 $rawPropertyValue = $row[$columnName];
173 $propertyValue = $dataMap->convertFieldValueToPropertyValue($propertyType, $rawPropertyValue);
176 case (Tx_Extbase_Persistence_PropertyType
::REFERENCE
):
177 if (!is_null($row[$columnName])) {
178 $propertyValue = $this->mapRelatedObjects($object, $propertyName, $row, $columnMap);
180 $propertyValue = NULL;
183 // FIXME we have an object to handle... -> exception
185 // SK: We should throw an exception as this point as there was an undefined propertyType we can not handle.
186 if (isset($row[$columnName])) {
187 $property = $row[$columnName];
188 if (is_object($property)) {
189 $propertyValue = $this->mapObject($property);
190 // SK: THIS case can not happen I think. At least $this->mapObject() is not available.
192 // SK: This case does not make sense either. $this-mapSingleRow has a different signature
193 $propertyValue = $this->mapSingleRow($className, $property);
199 $object->_setProperty($propertyName, $propertyValue);
204 * Maps related objects to an ObjectStorage
206 * @param object $parentObject The parent object for the mapping result
207 * @param string $propertyName The target property name for the mapping result
208 * @param Tx_Extbase_Persistence_RowInterface $row The actual database row
209 * @param int $loadingStrategy The loading strategy; one of Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_*
210 * @return array|Tx_Extbase_Persistence_ObjectStorage|Tx_Extbase_Persistence_LazyLoadingProxy|another implementation of a loading strategy
212 protected function mapRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity
$parentObject, $propertyName, Tx_Extbase_Persistence_RowInterface
$row, Tx_Extbase_Persistence_Mapper_ColumnMap
$columnMap) {
213 $dataMap = $this->getDataMap(get_class($parentObject));
214 $columnMap = $dataMap->getColumnMap($propertyName);
215 if ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap
::STRATEGY_PROXY
) {
216 // TODO Remove dependency to the loading strategy implementation
217 $result = t3lib_div
::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $dataMap);
219 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_ONE
) {
220 $query = $this->queryFactory
->create($columnMap->getChildClassName(), FALSE);
221 $result = current($query->matching($query->withUid($row[$columnMap->getColumnName()]))->execute());
222 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_MANY
) {
223 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
224 $query = $this->queryFactory
->create($columnMap->getChildClassName(), FALSE);
225 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
226 if (isset($parentKeyFieldName)) {
227 $objects = $query->matching($query->equals($columnMap->getParentKeyFieldName(), $parentObject->getUid()))->execute();
229 $propertyValue = $row[$propertyName];
230 $objects = $query->matching($query->withUid((int)$propertyValue))->execute();
232 foreach ($objects as $object) {
233 $objectStorage->attach($object);
235 $result = $objectStorage;
236 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
) {
237 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
238 $relationTableName = $columnMap->getRelationTableName();
239 $left = $this->QOMFactory
->selector($relationTableName);
240 $childTableName = $columnMap->getChildTableName();
241 $right = $this->QOMFactory
->selector($childTableName);
242 $joinCondition = $this->QOMFactory
->equiJoinCondition($relationTableName, $columnMap->getChildKeyFieldName(), $childTableName, 'uid');
243 $source = $this->QOMFactory
->join(
246 Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface
::JCR_JOIN_TYPE_INNER
,
249 $query = $this->queryFactory
->create($columnMap->getChildClassName(), FALSE);
250 $query->setSource($source);
251 $objects = $query->matching($query->equals($columnMap->getParentKeyFieldName(), $parentObject->getUid()))->execute();
252 foreach ($objects as $object) {
253 $objectStorage->attach($object);
255 $result = $objectStorage;
263 * Delegates the call to the Data Map.
264 * Returns TRUE if the property is persistable (configured in $TCA)
266 * @param string $className The property name
267 * @param string $propertyName The property name
268 * @return boolean TRUE if the property is persistable (configured in $TCA)
270 public function isPersistableProperty($className, $propertyName) {
271 $dataMap = $this->getDataMap($className);
272 return $dataMap->isPersistableProperty($propertyName);
276 * Returns a data map for a given class name
278 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
280 public function getDataMap($className) {
281 if (empty($this->dataMaps
[$className])) {
282 // FIXME This is a costy for table name aliases -> implement a DataMapBuilder (knowing the aliases defined in $TCA)
284 $extbaseSettings = Tx_Extbase_Dispatcher
::getSettings();
285 if (isset($extbaseSettings['classes'][$className]) && !empty($extbaseSettings['classes'][$className]['mapping']['tableName'])) {
286 $tableName = $extbaseSettings['classes'][$className]['mapping']['tableName'];
288 foreach (class_parents($className) as $parentClassName) {
289 if (isset($extbaseSettings['classes'][$parentClassName]) && !empty($extbaseSettings['classes'][$parentClassName]['mapping']['tableName'])) {
290 $tableName = $extbaseSettings['classes'][$parentClassName]['mapping']['tableName'];
293 // TODO throw Exception
296 if (is_array($extbaseSettings['classes'][$parentClassName]['mapping']['columns'])) {
297 $mapping = $extbaseSettings['classes'][$parentClassName]['mapping']['columns'];
300 $dataMap = new Tx_Extbase_Persistence_Mapper_DataMap($className, $tableName, $mapping);
301 $this->dataMaps
[$className] = $dataMap;
303 return $this->dataMaps
[$className];
307 * Returns the selector (table) name for a given class name.
309 * @param string $className
310 * @return string The selector name
312 public function convertClassNameToSelectorName($className) {
313 return $this->getDataMap($className)->getTableName();