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