Extbase:
[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 extbase
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_Persistence_ManagerInterface
41 */
42 protected $persistenceManager;
43
44 /**
45 * A reference to the page select object providing methods to perform language and work space overlays
46 *
47 * @var t3lib_pageSelect
48 **/
49 protected $pageSelectObject;
50
51 /**
52 * Cached data maps
53 *
54 * @var array
55 **/
56 protected $dataMaps = array();
57
58 /**
59 * @var Tx_Extbase_Persistence_QueryFactoryInterface
60 */
61 protected $queryFactory;
62
63 /**
64 * The TYPO3 reference index object
65 *
66 * @var t3lib_refindex
67 **/
68 protected $referenceIndex;
69
70 /**
71 * Constructs a new mapper
72 *
73 */
74 public function __construct() {
75 $this->queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
76 $GLOBALS['TSFE']->includeTCA(); // TODO Move this to an appropriate position
77 }
78
79 /**
80 * Injects the identity map
81 *
82 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
83 * @return void
84 */
85 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
86 $this->identityMap = $identityMap;
87 }
88
89 /**
90 * Injects the persistence manager
91 *
92 * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
93 * @return void
94 */
95 public function injectPersistenceManager(Tx_Extbase_Persistence_ManagerInterface $persistenceManager) {
96 $this->persistenceManager = $persistenceManager;
97 $this->QOMFactory = $this->persistenceManager->getBackend()->getQOMFactory();
98 }
99
100 /**
101 * Maps the (aggregate root) rows and registers them as reconstituted
102 * with the session.
103 *
104 * @param Tx_Extbase_Persistence_RowIteratorInterface $rows
105 * @return array
106 */
107 public function map($className, Tx_Extbase_Persistence_RowIteratorInterface $rows) {
108 $objects = array();
109 foreach ($rows as $row) {
110 $objects[] = $this->mapSingleRow($className, $row);
111 }
112 return $objects;
113 }
114
115 /**
116 * Maps a single node into the object it represents
117 *
118 * @param Tx_Extbase_Persistence_RowInterface $node
119 * @return object
120 */
121 protected function mapSingleRow($className, Tx_Extbase_Persistence_RowInterface $row) {
122 if ($this->identityMap->hasUid($className, $row['uid'])) {
123 $object = $this->identityMap->getObjectByUid($className, $row['uid']);
124 } else {
125 $object = $this->createEmptyObject($className);
126 $this->thawProperties($object, $row);
127 $this->identityMap->registerObject($object, $object->getUid());
128 $object->_memorizeCleanState();
129 }
130 return $object;
131 }
132
133 /**
134 * Creates a skeleton of the specified object
135 *
136 * @param string $className Name of the class to create a skeleton for
137 * @return object The object skeleton
138 * @internal
139 */
140 protected function createEmptyObject($className) {
141 // Note: The class_implements() function also invokes autoload to assure that the interfaces
142 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
143 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);
144 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
145 return $object;
146 }
147
148 /**
149 * Sets the given properties on the object.
150 *
151 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on
152 * @param Tx_Extbase_Persistence_RowInterface $row
153 * @return void
154 */
155 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_Persistence_RowInterface $row) {
156 $className = get_class($object);
157 $dataMap = $this->getDataMap($className);
158 $properties = $object->_getProperties();
159 $object->_setProperty('uid', $row['uid']);
160 foreach ($properties as $propertyName => $propertyValue) {
161 if (!$dataMap->isPersistableProperty($propertyName)) continue;
162 $columnMap = $dataMap->getColumnMap($propertyName);
163 $columnName = $columnMap->getColumnName();
164 $propertyValue = NULL;
165 $propertyType = $columnMap->getPropertyType();
166 switch ($propertyType) {
167 case Tx_Extbase_Persistence_PropertyType::STRING;
168 case Tx_Extbase_Persistence_PropertyType::DATE;
169 case Tx_Extbase_Persistence_PropertyType::LONG;
170 case Tx_Extbase_Persistence_PropertyType::DOUBLE;
171 case Tx_Extbase_Persistence_PropertyType::BOOLEAN;
172 if (isset($row[$columnName])) {
173 $rawPropertyValue = $row[$columnName];
174 $propertyValue = $dataMap->convertFieldValueToPropertyValue($propertyType, $rawPropertyValue);
175 }
176 break;
177 case (Tx_Extbase_Persistence_PropertyType::REFERENCE):
178 if (!is_null($row[$propertyName])) {
179 $propertyValue = $this->mapRelatedObjects($object, $propertyName, $row, $columnMap);
180 } else {
181 $propertyValue = NULL;
182 }
183 break;
184 // FIXME we have an object to handle... -> exception
185 default:
186 if (isset($row[$propertyName])) {
187 $property = $row[$propertyName];
188 if (is_object($property)) {
189 $propertyValue = $this->mapObject($property);
190 } else {
191 $propertyValue = $this->mapSingleRow($className, $property);
192 }
193 }
194 break;
195 }
196
197 $object->_setProperty($propertyName, $propertyValue);
198 }
199 }
200
201 /**
202 * Maps related objects to an ObjectStorage
203 *
204 * @param object $parentObject The parent object for the mapping result
205 * @param string $propertyName The target property name for the mapping result
206 * @param Tx_Extbase_Persistence_RowInterface $row The actual database row
207 * @param int $loadingStrategy The loading strategy; one of Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_*
208 * @return array|Tx_Extbase_Persistence_ObjectStorage|Tx_Extbase_Persistence_LazyLoadingProxy|another implementation of a loading strategy
209 */
210 protected function mapRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, Tx_Extbase_Persistence_RowInterface $row, Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
211 $dataMap = $this->getDataMap(get_class($parentObject));
212 $columnMap = $dataMap->getColumnMap($propertyName);
213 if ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_PROXY) {
214 // TODO Remove dependency to the loading strategy implementation
215 $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $dataMap);
216 } else {
217 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
218 $query = $this->queryFactory->create($columnMap->getChildClassName());
219 $result = current($query->matching($query->withUid($row[$columnMap->getColumnName()]))->execute());
220 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
221 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
222 $query = $this->queryFactory->create($columnMap->getChildClassName());
223 $objects = $query->matching($query->equals($columnMap->getParentKeyFieldName(), $parentObject->getUid()))->execute();
224 foreach ($objects as $object) {
225 $objectStorage->attach($object);
226 }
227 $result = $objectStorage;
228 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
229 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
230 $relationTableName = $columnMap->getRelationTableName();
231 $left = $this->QOMFactory->selector($relationTableName);
232 $childTableName = $columnMap->getChildTableName();
233 $right = $this->QOMFactory->selector($childTableName);
234 $joinCondition = $this->QOMFactory->equiJoinCondition($relationTableName, 'uid_foreign', $childTableName, 'uid');
235 $source = $this->QOMFactory->join(
236 $left,
237 $right,
238 Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER,
239 $joinCondition
240 );
241 $query = $this->queryFactory->create($columnMap->getChildClassName());
242 $query->setSource($source);
243 $objects = $query->matching($query->equals('uid_local', $parentObject->getUid()))->execute();
244 foreach ($objects as $object) {
245 $objectStorage->attach($object);
246 }
247 $result = $objectStorage;
248 }
249 }
250
251 return $result;
252 }
253
254 /**
255 * Delegates the call to the Data Map.
256 * Returns TRUE if the property is persistable (configured in $TCA)
257 *
258 * @param string $className The property name
259 * @param string $propertyName The property name
260 * @return boolean TRUE if the property is persistable (configured in $TCA)
261 */
262 public function isPersistableProperty($className, $propertyName) {
263 $dataMap = $this->getDataMap($className);
264 return $dataMap->isPersistableProperty($propertyName);
265 }
266
267 /**
268 * Returns a data map for a given class name
269 *
270 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
271 */
272 public function getDataMap($className) {
273 global $TCA;
274 if (empty($this->dataMaps[$className])) {
275 // // TODO This is a little bit costy for table name aliases -> implement a DataMapBuilder (knowing the aliases defined in $TCA)
276 // $tableName = '';
277 // if (is_array($TCA[strtolower($className)] && empty($TCA[strtolower($className)]['config']['classes']))) {
278 // $tableName = strtolower($className);
279 // } else {
280 // debug($TCA);
281 //
282 // foreach ($TCA as $configuredTableName => $tableConfiguration) {
283 // if (in_array($className, t3lib_div::trimExplode(',', $tableConfiguration['config']['classes']))) {
284 // $tableName = $configuredTableName;
285 // }
286 // }
287 // }
288 $dataMap = new Tx_Extbase_Persistence_Mapper_DataMap($className, $tableName);
289 $this->dataMaps[$className] = $dataMap;
290 }
291 return $this->dataMaps[$className];
292 }
293
294 /**
295 * Returns the selector (table) name for a given class name.
296 *
297 * @param string $className
298 * @return string The selector name
299 */
300 public function convertClassNameToSelectorName($className) {
301 return $this->getDataMap($className)->getTableName();
302 }
303
304 }
305 ?>