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_Reflection_Service
42 protected $reflectionService;
45 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactory
47 protected $QOMFactory;
50 * @var Tx_Extbase_Persistence_Session
52 protected $persistenceSession;
55 * A reference to the page select object providing methods to perform language and work space overlays
57 * @var t3lib_pageSelect
59 protected $pageSelectObject;
66 protected $dataMaps = array();
69 * @var Tx_Extbase_Persistence_QueryFactoryInterface
71 protected $queryFactory;
74 * The TYPO3 reference index object
78 protected $referenceIndex;
81 * Constructs a new mapper
84 public function __construct() {
85 $this->queryFactory
= t3lib_div
::makeInstance('Tx_Extbase_Persistence_QueryFactory');
89 * Injects the identity map
91 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
94 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap
$identityMap) {
95 $this->identityMap
= $identityMap;
99 * Injects the persistence manager
101 * @param Tx_Extbase_Persistence_ManagerInterface $persistenceManager
104 public function injectPersistenceManager(Tx_Extbase_Persistence_ManagerInterface
$persistenceManager) {
105 $this->QOMFactory
= $persistenceManager->getBackend()->getQOMFactory();
106 $this->persistenceSession
= $persistenceManager->getSession();
110 * Injects the Reflection Service
112 * @param Tx_Extbase_Reflection_Service
115 public function injectReflectionService(Tx_Extbase_Reflection_Service
$reflectionService) {
116 $this->reflectionService
= $reflectionService;
120 * Maps the (aggregate root) rows and registers them as reconstituted
123 * @param Tx_Extbase_Persistence_RowIteratorInterface $rows
126 public function map($className, Tx_Extbase_Persistence_RowIteratorInterface
$rows) {
128 foreach ($rows as $row) {
129 $objects[] = $this->mapSingleRow($className, $row);
135 * Maps a single node into the object it represents
137 * @param Tx_Extbase_Persistence_RowInterface $node
140 protected function mapSingleRow($className, Tx_Extbase_Persistence_RowInterface
$row) {
141 if ($this->identityMap
->hasIdentifier($row->getValue('uid'), $className)) {
142 $object = $this->identityMap
->getObjectByIdentifier($row->getValue('uid'), $className);
144 $object = $this->createEmptyObject($className);
145 $this->thawProperties($object, $row);
146 $this->identityMap
->registerObject($object, $object->getUid());
147 $object->_memorizeCleanState();
148 $this->persistenceSession
->registerReconstitutedObject($object);
154 * Creates a skeleton of the specified object
156 * @param string $className Name of the class to create a skeleton for
157 * @return object The object skeleton
159 protected function createEmptyObject($className) {
160 // Note: The class_implements() function also invokes autoload to assure that the interfaces
161 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it.
162 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);
163 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
168 * Sets the given properties on the object.
170 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on
171 * @param Tx_Extbase_Persistence_RowInterface $row
174 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface
$object, Tx_Extbase_Persistence_RowInterface
$row) {
175 $className = get_class($object);
176 $dataMap = $this->getDataMap($className);
177 $properties = $object->_getProperties();
178 $object->_setProperty('uid', $row->getValue('uid'));
179 foreach ($properties as $propertyName => $propertyValue) {
180 if (!$dataMap->isPersistableProperty($propertyName)) continue;
181 $columnMap = $dataMap->getColumnMap($propertyName);
182 $columnName = $columnMap->getColumnName();
183 $propertyValue = NULL;
184 $propertyType = $columnMap->getPropertyType();
185 switch ($propertyType) {
186 case Tx_Extbase_Persistence_PropertyType
::STRING;
187 case Tx_Extbase_Persistence_PropertyType
::DATE
;
188 case Tx_Extbase_Persistence_PropertyType
::LONG
;
189 case Tx_Extbase_Persistence_PropertyType
::DOUBLE;
190 case Tx_Extbase_Persistence_PropertyType
::BOOLEAN
;
191 if ($row->hasValue($columnName)) {
192 $rawPropertyValue = $row->getValue($columnName);
193 $propertyValue = $dataMap->convertFieldValueToPropertyValue($propertyType, $rawPropertyValue);
196 case (Tx_Extbase_Persistence_PropertyType
::REFERENCE
):
197 if (!is_null($row->getValue($columnName))) {
198 $propertyValue = $this->mapRelatedObjects($object, $propertyName, $row, $columnMap);
200 $propertyValue = NULL;
203 // FIXME we have an object to handle... -> exception
205 // SK: We should throw an exception as this point as there was an undefined propertyType we can not handle.
206 if ($row->hasValue($columnName)) {
207 $property = $row->getValue($columnName);
208 if (is_object($property)) {
209 $propertyValue = $this->mapObject($property);
210 // SK: THIS case can not happen I think. At least $this->mapObject() is not available.
212 // SK: This case does not make sense either. $this-mapSingleRow has a different signature
213 $propertyValue = $this->mapSingleRow($className, $property);
219 $object->_setProperty($propertyName, $propertyValue);
224 * Maps related objects to an ObjectStorage
226 * @param object $parentObject The parent object for the mapping result
227 * @param string $propertyName The target property name for the mapping result
228 * @param Tx_Extbase_Persistence_RowInterface $row The actual database row
229 * @param int $loadingStrategy The loading strategy; one of Tx_Extbase_Persistence_Mapper_ColumnMap::STRATEGY_*
230 * @return array|Tx_Extbase_Persistence_ObjectStorage|Tx_Extbase_Persistence_LazyLoadingProxy|another implementation of a loading strategy
232 protected function mapRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity
$parentObject, $propertyName, Tx_Extbase_Persistence_RowInterface
$row, Tx_Extbase_Persistence_Mapper_ColumnMap
$columnMap) {
233 $dataMap = $this->getDataMap(get_class($parentObject));
234 $columnMap = $dataMap->getColumnMap($propertyName);
235 $targetClassSchema = $this->reflectionService
->getClassSchema(get_class($parentObject));
236 $propertyMetaData = $targetClassSchema->getProperty($propertyName);
237 $fieldValue = $row->getValue($columnMap->getColumnName());
238 if ($columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap
::STRATEGY_LAZY_PROXY
) {
239 $result = t3lib_div
::makeInstance('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue, $columnMap);
241 $queryFactory = t3lib_div
::makeInstance('Tx_Extbase_Persistence_QueryFactory');
242 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_ONE
) {
243 $query = $queryFactory->create($columnMap->getChildClassName());
244 // 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
245 // enableFields and storage page settings.
246 $query->getQuerySettings()->setRespectStoragePage(FALSE);
247 $result = current($query->matching($query->withUid((int)$fieldValue))->execute());
248 } elseif (($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_MANY
) ||
($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
)) {
249 if ($propertyMetaData['lazy'] === TRUE ||
$columnMap->getLoadingStrategy() === Tx_Extbase_Persistence_Mapper_ColumnMap
::STRATEGY_LAZY_STORAGE
) {
250 $result = new Tx_Extbase_Persistence_LazyObjectStorage($parentObject, $propertyName, $fieldValue, $columnMap);
252 $objects = $this->fetchRelatedObjects($parentObject, $propertyName, $fieldValue, $columnMap);
253 if ($propertyMetaData['type'] === 'ArrayObject') {
254 $result = new ArrayObject($objects);
255 } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage' ||
$propertyMetaData['type'] === 'Tx_Extbase_Persistence_LazyObjectStorage') {
256 $result = new Tx_Extbase_Persistence_ObjectStorage();
257 foreach ($objects as $object) {
258 $result->attach($object);
270 * Fetches a collection of objects related to a property of a parent object
272 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The object instance this proxy is part of
273 * @param string $propertyName The name of the proxied property in it's parent
274 * @param mixed $fieldValue The raw field value.
275 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The corresponding Data Map of the property
276 * @return Tx_Extbase_Persistence_ObjectStorage An Object Storage containing the related objects
278 public function fetchRelatedObjects(Tx_Extbase_DomainObject_AbstractEntity
$parentObject, $propertyName, $fieldValue, Tx_Extbase_Persistence_Mapper_ColumnMap
$columnMap) {
279 $queryFactory = t3lib_div
::makeInstance('Tx_Extbase_Persistence_QueryFactory');
281 $childSortByFieldName = $columnMap->getChildSortByFieldName();
282 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
283 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_MANY
) {
284 $query = $queryFactory->create($columnMap->getChildClassName());
285 if (!empty($childSortByFieldName)) {
286 $query->setOrderings(array($childSortByFieldName => Tx_Extbase_Persistence_QueryInterface
::ORDER_ASCENDING
));
288 $parentKeyFieldName = $columnMap->getParentKeyFieldName();
289 if (isset($parentKeyFieldName)) {
290 $objects = $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()))->execute();
292 $uidArray = t3lib_div
::intExplode(',', $fieldValue);
293 $objects = $query->matching($query->equals('uid', $uidArray))->execute();
295 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
) {
296 $relationTableName = $columnMap->getRelationTableName();
297 $left = $this->QOMFactory
->selector(NULL, $relationTableName);
298 $childClassName = $columnMap->getChildClassName();
299 $childTableName = $columnMap->getChildTableName();
300 $right = $this->QOMFactory
->selector($childClassName, $childTableName);
301 $joinCondition = $this->QOMFactory
->equiJoinCondition($relationTableName, $columnMap->getChildKeyFieldName(), $childTableName, 'uid');
302 $source = $this->QOMFactory
->join(
305 Tx_Extbase_Persistence_QOM_QueryObjectModelConstantsInterface
::JCR_JOIN_TYPE_INNER
,
308 $query = $queryFactory->create($columnMap->getChildClassName());
309 $query->setSource($source);
310 if (!empty($childSortByFieldName)) {
311 $query->setOrderings(array($childSortByFieldName => Tx_Extbase_Persistence_QueryInterface
::ORDER_ASCENDING
));
313 // 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
314 // enableFields and storage page settings.
315 $query->getQuerySettings()->setRespectStoragePage(FALSE);
316 $objects = $query->matching($query->equals($parentKeyFieldName, $parentObject->getUid()))->execute();
318 throw new Tx_Extbase_Persistence_Exception('Could not determine type of relation.', 1252502725);
324 * Delegates the call to the Data Map.
325 * Returns TRUE if the property is persistable (configured in $TCA)
327 * @param string $className The property name
328 * @param string $propertyName The property name
329 * @return boolean TRUE if the property is persistable (configured in $TCA)
331 public function isPersistableProperty($className, $propertyName) {
332 $dataMap = $this->getDataMap($className);
333 return $dataMap->isPersistableProperty($propertyName);
337 * Returns a data map for a given class name
339 * @param string $className The class name you want to fetch the Data Map for
340 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map
342 public function getDataMap($className) {
343 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);
344 if (!isset($this->dataMaps
[$className])) {
345 // FIXME This is too expensive for table name aliases -> implement a DataMapBuilder (knowing the aliases defined in $TCA)
346 $columnMapping = array();
348 $extbaseSettings = Tx_Extbase_Dispatcher
::getExtbaseFrameworkConfiguration();
349 if (is_array($extbaseSettings['persistence']['classes'][$className])) {
350 $persistenceSettings = $extbaseSettings['persistence']['classes'][$className];
351 if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
352 $tableName = $persistenceSettings['mapping']['tableName'];
354 if (is_array($persistenceSettings['mapping']['columns'])) {
355 $columnMapping = $persistenceSettings['mapping']['columns'];
357 } elseif (class_exists($className)) {
358 foreach (class_parents($className) as $parentClassName) {
359 $persistenceSettings = $extbaseSettings['persistence']['classes'][$parentClassName];
360 if (is_array($persistenceSettings)) {
361 if (is_string($persistenceSettings['mapping']['tableName']) && strlen($persistenceSettings['mapping']['tableName']) > 0) {
362 $tableName = $persistenceSettings['mapping']['tableName'];
364 if (is_array($persistenceSettings['mapping']['columns'])) {
365 $columnMapping = $persistenceSettings['mapping']['columns'];
371 throw new Tx_Extbase_Persistence_Exception('Could not determine a Data Map for given class name.', 1256067130);
374 $dataMap = new Tx_Extbase_Persistence_Mapper_DataMap($className, $tableName, $columnMapping);
375 $this->dataMaps
[$className] = $dataMap;
377 return $this->dataMaps
[$className];
381 * Returns the selector (table) name for a given class name.
383 * @param string $className
384 * @return string The selector name
386 public function convertClassNameToTableName($className) {
387 return $this->getDataMap($className)->getTableName();
391 * Returns the column name for a given property name of the specified class.
393 * @param string $className
394 * @param string $propertyName
395 * @return string The column name
397 public function convertPropertyNameToColumnName($propertyName, $className = '') {
398 if (!empty($className)) {
399 $dataMap = $this->getDataMap($className);
400 if ($dataMap !== NULL) {
401 $columnMap = $dataMap->getColumnMap($propertyName);
402 if ($columnMap !== NULL) {
403 return $columnMap->getColumnName();
407 return Tx_Extbase_Utility_Extension
::convertCamelCaseToLowerCaseUnderscored($propertyName);