[~BUG] Extbase (Persistence): The findByFooBar maps the property name to column name...
[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_Persistence_QOM_QueryObjectModelFactory
41 */
42 protected $QOMFactory;
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 }
77
78 /**
79 * Injects the identity map
80 *
81 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
82 * @return void
83 */
84 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
85 $this->identityMap = $identityMap;
86 }
87
88 /**
89 * Injects the persistence manager
90 *
91 * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
92 * @return void
93 */
94 public function injectPersistenceManager(Tx_Extbase_Persistence_ManagerInterface $persistenceManager) {
95 $this->QOMFactory = $persistenceManager->getBackend()->getQOMFactory();
96 }
97
98 /**
99 * Maps the (aggregate root) rows and registers them as reconstituted
100 * with the session.
101 *
102 * @param Tx_Extbase_Persistence_RowIteratorInterface $rows
103 * @return array
104 */
105 public function map($className, Tx_Extbase_Persistence_RowIteratorInterface $rows) {
106 $objects = array();
107 foreach ($rows as $row) {
108 $objects[] = $this->mapSingleRow($className, $row);
109 }
110 return $objects;
111 }
112
113 /**
114 * Maps a single node into the object it represents
115 *
116 * @param Tx_Extbase_Persistence_RowInterface $node
117 * @return object
118 */
119 protected function mapSingleRow($className, Tx_Extbase_Persistence_RowInterface $row) {
120 if ($this->identityMap->hasUid($className, $row['uid'])) {
121 $object = $this->identityMap->getObjectByUid($className, $row['uid']);
122 } else {
123 $object = $this->createEmptyObject($className);
124 $this->thawProperties($object, $row);
125 $this->identityMap->registerObject($object, $object->getUid());
126 $object->_memorizeCleanState();
127 }
128 return $object;
129 }
130
131 /**
132 * Creates a skeleton of the specified object
133 *
134 * @param string $className Name of the class to create a skeleton for
135 * @return object The object skeleton
136
137 */
138 protected function createEmptyObject($className) {
139 // Note: The class_implements() function also invokes autoload to assure that the interfaces
140 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
141 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);
142 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
143 return $object;
144 }
145
146 /**
147 * Sets the given properties on the object.
148 *
149 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on
150 * @param Tx_Extbase_Persistence_RowInterface $row
151 * @return void
152 */
153 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_Persistence_RowInterface $row) {
154 $className = get_class($object);
155 $dataMap = $this->getDataMap($className);
156 $properties = $object->_getProperties();
157 $object->_setProperty('uid', $row['uid']);
158 foreach ($properties as $propertyName => $propertyValue) {
159 if (!$dataMap->isPersistableProperty($propertyName)) continue;
160 $columnMap = $dataMap->getColumnMap($propertyName);
161 $columnName = $columnMap->getColumnName();
162 $propertyValue = NULL;
163 $propertyType = $columnMap->getPropertyType();
164 switch ($propertyType) {
165 case Tx_Extbase_Persistence_PropertyType::STRING;
166 case Tx_Extbase_Persistence_PropertyType::DATE;
167 case Tx_Extbase_Persistence_PropertyType::LONG;
168 case Tx_Extbase_Persistence_PropertyType::DOUBLE;
169 case Tx_Extbase_Persistence_PropertyType::BOOLEAN;
170 if (isset($row[$columnName])) {
171 $rawPropertyValue = $row[$columnName];
172 $propertyValue = $dataMap->convertFieldValueToPropertyValue($propertyType, $rawPropertyValue);
173 }
174 break;
175 case (Tx_Extbase_Persistence_PropertyType::REFERENCE):
176 if (!is_null($row[$columnName])) {
177 $propertyValue = $this->mapRelatedObjects($object, $propertyName, $row, $columnMap);
178 } else {
179 $propertyValue = NULL;
180 }
181 break;
182 // FIXME we have an object to handle... -> exception
183 default:
184 // SK: We should throw an exception as this point as there was an undefined propertyType we can not handle.
185 if (isset($row[$columnName])) {
186 $property = $row[$columnName];
187 if (is_object($property)) {
188 $propertyValue = $this->mapObject($property);
189 // SK: THIS case can not happen I think. At least $this->mapObject() is not available.
190 } else {
191 // SK: This case does not make sense either. $this-mapSingleRow has a different signature
192 $propertyValue = $this->mapSingleRow($className, $property);
193 }
194 }
195 break;
196 }
197
198 $object->_setProperty($propertyName, $propertyValue);
199 }
200 }
201
202 /**
203 * Maps related objects to an ObjectStorage
204 *
205 * @param object $parentObject The parent object for the mapping result
206 * @param string $propertyName The target property name for the mapping result
207 * @param Tx_Extbase_Persistence_RowInterface $row The actual database row
208 * @param int $loadingStrategy The loading strategy; one of Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_*
209 * @return array|Tx_Extbase_Persistence_ObjectStorage|Tx_Extbase_Persistence_LazyLoadingProxy|another implementation of a loading strategy
210 */
211 // FIXME There is a recursion problem with post1 -> post2 and post2 -> post1
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 $fieldValue = $row[$columnMap->getColumnName()];
216 if ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_PROXY) {
217 // TODO Remove dependency to the loading strategy implementation
218 $result = t3lib_div::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue, $columnMap);
219 } else {
220 $result = $this->fetchRelatedObjects( $parentObject, $propertyName, $fieldValue, $columnMap);
221 }
222
223 return $result;
224 }
225
226 /**
227 * Fetches a collection of objects related to a property of a parent object
228 *
229 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
230 * @param string $propertyName The name of the proxied property in it's parent
231 * @param mixed $fieldValue The raw field value.
232 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
233
234 */
235 public function fetchRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity $parentObject, $propertyName, $fieldValue, Tx_Extbase_Persistence_Mapper_ColumnMap $columnMap) {
236 $queryFactory = t3lib_div::makeInstance('Tx_Extbase_Persistence_QueryFactory');
237 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
238 $query = $queryFactory->create($columnMap->getChildClassName());
239 $result = current($query->matching($query->withUid((int)$fieldValue))->execute());
240 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
241 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
242 $query = $queryFactory->create($columnMap->getChildClassName());
243 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
244 if (isset($parentKeyFieldName)) {
245 $objects = $query->matching($query->equals($columnMap->getParentKeyFieldName(), $parentObject->getUid()))->execute();
246 } else {
247 // TODO Is it necessary to "normalize" and intval the list?
248 $uids = t3lib_div::trimExplode(',', $fieldValue);
249 $uidArray = array();
250 foreach ($uids as $uid) {
251 $uidArray[] = (int)$uid;
252 }
253 $uids = implode(',', $uidArray);
254 // FIXME Using statement() is only a preliminary solution
255 $objects = $query->statement('SELECT * FROM ' . $columnMap->getChildTableName() . ' WHERE uid IN (' . $uids . ')')->execute();
256 }
257 foreach ($objects as $object) {
258 $objectStorage->attach($object);
259 }
260 $result = $objectStorage;
261 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
262 $objectStorage = new Tx_Extbase_Persistence_ObjectStorage();
263 $relationTableName = $columnMap->getRelationTableName();
264 $left = $this->QOMFactory->selector(NULL, $relationTableName);
265 $childClassName = $columnMap->getChildClassName();
266 $childTableName = $columnMap->getChildTableName();
267 $right = $this->QOMFactory->selector($childClassName, $childTableName);
268 $joinCondition = $this->QOMFactory->equiJoinCondition($relationTableName, $columnMap->getChildKeyFieldName(), $childTableName, 'uid');
269 $source = $this->QOMFactory->join(
270 $left,
271 $right,
272 Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface::JCR_JOIN_TYPE_INNER,
273 $joinCondition
274 );
275 $query = $queryFactory->create($columnMap->getChildClassName());
276 $query->setSource($source);
277 $objects = $query->matching($query->equals($columnMap->getParentKeyFieldName(), $parentObject->getUid()))->execute();
278 foreach ($objects as $object) {
279 $objectStorage->attach($object);
280 }
281 $result = $objectStorage;
282 }
283
284 return $result;
285 }
286
287 /**
288 * Delegates the call to the Data Map.
289 * Returns TRUE if the property is persistable (configured in $TCA)
290 *
291 * @param string $className The property name
292 * @param string $propertyName The property name
293 * @return boolean TRUE if the property is persistable (configured in $TCA)
294 */
295 public function isPersistableProperty($className, $propertyName) {
296 $dataMap = $this->getDataMap($className);
297 return $dataMap->isPersistableProperty($propertyName);
298 }
299
300 /**
301 * Returns a data map for a given class name
302 *
303 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
304 */
305 public function getDataMap($className) {
306 if (is_string($className) && strlen($className) > 0) {
307 if (empty($this->dataMaps[$className])) {
308 // FIXME This is a costy for table name aliases -> implement a DataMapBuilder (knowing the aliases defined in $TCA)
309 $mapping = array();
310 $extbaseSettings = Tx_Extbase_Dispatcher::getSettings();
311 if (isset($extbaseSettings['classes'][$className]) && !empty($extbaseSettings['classes'][$className]['mapping']['tableName'])) {
312 $tableName = $extbaseSettings['classes'][$className]['mapping']['tableName'];
313 } else {
314 foreach (class_parents($className) as $parentClassName) {
315 if (isset($extbaseSettings['classes'][$parentClassName]) && !empty($extbaseSettings['classes'][$parentClassName]['mapping']['tableName'])) {
316 $tableName = $extbaseSettings['classes'][$parentClassName]['mapping']['tableName'];
317 break;
318 }
319 // TODO throw Exception
320 }
321 }
322 if (is_array($extbaseSettings['classes'][$parentClassName]['mapping']['columns'])) {
323 $mapping = $extbaseSettings['classes'][$parentClassName]['mapping']['columns'];
324 }
325
326 $dataMap = new Tx_Extbase_Persistence_Mapper_DataMap($className, $tableName, $mapping);
327 $this->dataMaps[$className] = $dataMap;
328 }
329 return $this->dataMaps[$className];
330 } else {
331 return NULL;
332 }
333 }
334
335 /**
336 * Returns the selector (table) name for a given class name.
337 *
338 * @param string $className
339 * @return string The selector name
340 */
341 public function convertClassNameToTableName($className) {
342
343 return $this->getDataMap($className)->getTableName();
344 }
345
346 /**
347 * Returns the column name for a given property name of the specified class.
348 *
349 * @param string $className
350 * @param string $propertyName
351 * @return string The column name
352 */
353 public function convertPropertyNameToColumnName($propertyName, $className = '') {
354 if (!empty($className)) {
355 $dataMap = $this->getDataMap($className);
356 if ($dataMap !== NULL) {
357 $columnMap = $dataMap->getColumnMap($propertyName);
358 if ($columnMap !== NULL) {
359 return $columnMap->getColumnName();
360 }
361 }
362 }
363 return Tx_Extbase_Utility_Plugin::convertCamelCaseToLowerCaseUnderscored($propertyName);
364 }
365
366 }
367 ?>