|
TYPO3 API
SVNRelease
|
00001 <?php 00002 /*************************************************************** 00003 * Copyright notice 00004 * 00005 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de> 00006 * All rights reserved 00007 * 00008 * This script is part of the TYPO3 project. The TYPO3 project is 00009 * free software; you can redistribute it and/or modify 00010 * it under the terms of the GNU General Public License as published by 00011 * the Free Software Foundation; either version 2 of the License, or 00012 * (at your option) any later version. 00013 * 00014 * The GNU General Public License can be found at 00015 * http://www.gnu.org/copyleft/gpl.html. 00016 * 00017 * This script is distributed in the hope that it will be useful, 00018 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 * GNU General Public License for more details. 00021 * 00022 * This copyright notice MUST APPEAR in all copies of the script! 00023 ***************************************************************/ 00024 00025 /** 00026 * A mapper to map database tables configured in $TCA on domain objects. 00027 * 00028 * @package Extbase 00029 * @subpackage Persistence\Mapper 00030 * @version $ID:$ 00031 */ 00032 class Tx_Extbase_Persistence_Mapper_DataMapper implements t3lib_Singleton { 00033 00034 /** 00035 * @var Tx_Extbase_Persistence_IdentityMap 00036 */ 00037 protected $identityMap; 00038 00039 /** 00040 * @var Tx_Extbase_Reflection_Service 00041 */ 00042 protected $reflectionService; 00043 00044 /** 00045 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactory 00046 */ 00047 protected $qomFactory; 00048 00049 /** 00050 * @var Tx_Extbase_Persistence_Session 00051 */ 00052 protected $persistenceSession; 00053 00054 /** 00055 * A reference to the page select object providing methods to perform language and work space overlays 00056 * 00057 * @var t3lib_pageSelect 00058 **/ 00059 protected $pageSelectObject; 00060 00061 /** 00062 * Cached data maps 00063 * 00064 * @var array 00065 **/ 00066 protected $dataMaps = array(); 00067 00068 /** 00069 * @var Tx_Extbase_Persistence_Mapper_DataMapFactory 00070 */ 00071 protected $dataMapFactory; 00072 00073 /** 00074 * @var Tx_Extbase_Persistence_QueryFactoryInterface 00075 */ 00076 protected $queryFactory; 00077 00078 /** 00079 * The TYPO3 reference index object 00080 * 00081 * @var t3lib_refindex 00082 **/ 00083 protected $referenceIndex; 00084 00085 /** 00086 * @var Tx_Extbase_Object_ObjectManagerInterface 00087 */ 00088 protected $objectManager; 00089 00090 /** 00091 * Injects the identity map 00092 * 00093 * @param Tx_Extbase_Persistence_IdentityMap $identityMap 00094 * @return void 00095 */ 00096 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) { 00097 $this->identityMap = $identityMap; 00098 } 00099 00100 /** 00101 * Injects the persistence session 00102 * 00103 * @param Tx_Extbase_Persistence_Session $persistenceSession 00104 * @return void 00105 */ 00106 public function injectSession(Tx_Extbase_Persistence_Session $persistenceSession) { 00107 $this->persistenceSession = $persistenceSession; 00108 } 00109 00110 /** 00111 * Injects the Reflection Service 00112 * 00113 * @param Tx_Extbase_Reflection_Service 00114 * @return void 00115 */ 00116 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) { 00117 $this->reflectionService = $reflectionService; 00118 } 00119 00120 /** 00121 * Injects the DataMap Factory 00122 * 00123 * @param Tx_Extbase_Persistence_Mapper_DataMapFactory 00124 * @return void 00125 */ 00126 public function injectDataMapFactory(Tx_Extbase_Persistence_Mapper_DataMapFactory $dataMapFactory) { 00127 $this->dataMapFactory = $dataMapFactory; 00128 } 00129 00130 /** 00131 * Injects the Query Factory 00132 * 00133 * @param Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory 00134 */ 00135 public function injectQueryFactory(Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory) { 00136 $this->queryFactory = $queryFactory; 00137 } 00138 00139 /** 00140 * Sets the query object model factory 00141 * 00142 * @param Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory 00143 * @return void 00144 */ 00145 public function injectQomFactory(Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory) { 00146 $this->qomFactory = $qomFactory; 00147 } 00148 00149 /** 00150 * @param Tx_Extbase_Object_ObjectManagerInterface $objectManager 00151 * @return void 00152 */ 00153 public function injectObjectManager(Tx_Extbase_Object_ObjectManagerInterface $objectManager) { 00154 $this->objectManager = $objectManager; 00155 } 00156 00157 /** 00158 * Maps the given rows on objects 00159 * 00160 * @param string $className The name of the class 00161 * @param array $rows An array of arrays with field_name => value pairs 00162 * @return array An array of objects of the given class 00163 */ 00164 public function map($className, array $rows) { 00165 $objects = array(); 00166 foreach ($rows as $row) { 00167 $objects[] = $this->mapSingleRow($this->getTargetType($className, $row), $row); 00168 } 00169 return $objects; 00170 } 00171 00172 /** 00173 * Returns the target type for the given row. 00174 * 00175 * @param string $className The name of the class 00176 * @param array $row A single array with field_name => value pairs 00177 * @return string The target type (a class name) 00178 */ 00179 public function getTargetType($className, array $row) { 00180 $dataMap = $this->getDataMap($className); 00181 $targetType = $className; 00182 if ($dataMap->getRecordTypeColumnName() !== NULL) { 00183 foreach ($dataMap->getSubclasses() as $subclassName) { 00184 $recordSubtype = $this->getDataMap($subclassName)->getRecordType(); 00185 if ($row[$dataMap->getRecordTypeColumnName()] === $recordSubtype) { 00186 $targetType = $subclassName; 00187 break; 00188 } 00189 } 00190 } 00191 return $targetType; 00192 } 00193 00194 /** 00195 * Maps a single row on an object of the given class 00196 * 00197 * @param string $className The name of the target class 00198 * @param array $row A single array with field_name => value pairs 00199 * @return object An object of the given class 00200 */ 00201 protected function mapSingleRow($className, array $row) { 00202 if ($this->identityMap->hasIdentifier($row['uid'], $className)) { 00203 $object = $this->identityMap->getObjectByIdentifier($row['uid'], $className); 00204 } else { 00205 $object = $this->createEmptyObject($className); 00206 $this->identityMap->registerObject($object, $row['uid']); 00207 $this->thawProperties($object, $row); 00208 $object->_memorizeCleanState(); 00209 $this->persistenceSession->registerReconstitutedObject($object); 00210 } 00211 return $object; 00212 } 00213 00214 /** 00215 * Creates a skeleton of the specified object 00216 * 00217 * @param string $className Name of the class to create a skeleton for 00218 * @return object The object skeleton 00219 */ 00220 protected function createEmptyObject($className) { 00221 // Note: The class_implements() function also invokes autoload to assure that the interfaces 00222 // and the class are loaded. Would end up with __PHP_Incomplete_Class without it. 00223 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); 00224 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};'); 00225 return $object; 00226 } 00227 00228 /** 00229 * Sets the given properties on the object. 00230 * 00231 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to set properties on 00232 * @param array $row 00233 * @return void 00234 */ 00235 protected function thawProperties(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $row) { 00236 $className = get_class($object); 00237 $dataMap = $this->getDataMap($className); 00238 $object->_setProperty('uid', intval($row['uid'])); 00239 $object->_setProperty('pid', intval($row['pid'])); 00240 $object->_setProperty('_localizedUid', intval($row['uid'])); 00241 if ($dataMap->getLanguageIdColumnName() !== NULL) { 00242 $object->_setProperty('_languageUid', intval($row[$dataMap->getLanguageIdColumnName()])); 00243 if (isset($row['_LOCALIZED_UID'])) { 00244 $object->_setProperty('_localizedUid', intval($row['_LOCALIZED_UID'])); 00245 } 00246 } 00247 $properties = $object->_getProperties(); 00248 foreach ($properties as $propertyName => $propertyValue) { 00249 if (!$dataMap->isPersistableProperty($propertyName)) continue; 00250 $columnMap = $dataMap->getColumnMap($propertyName); 00251 $columnName = $columnMap->getColumnName(); 00252 $propertyData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName); 00253 $propertyValue = NULL; 00254 if ($row[$columnName] !== NULL) { 00255 switch ($propertyData['type']) { 00256 case 'integer': 00257 $propertyValue = (int) $row[$columnName]; 00258 break; 00259 case 'float': 00260 $propertyValue = (float) $row[$columnName]; 00261 break; 00262 case 'boolean': 00263 $propertyValue = (boolean) $row[$columnName]; 00264 break; 00265 case 'string': 00266 $propertyValue = (string) $row[$columnName]; 00267 break; 00268 case 'array': 00269 // $propertyValue = $this->mapArray($row[$columnName]); // Not supported, yet! 00270 break; 00271 case 'SplObjectStorage': 00272 case 'Tx_Extbase_Persistence_ObjectStorage': 00273 $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $this->fetchRelated($object, $propertyName, $row[$columnName])); 00274 break; 00275 default: 00276 if (($propertyData['type'] === 'DateTime') || in_array('DateTime', class_parents($propertyData['type']))) { 00277 $propertyValue = $this->mapDateTime($row[$columnName]); 00278 } else { 00279 $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $this->fetchRelated($object, $propertyName, $row[$columnName])); 00280 // $propertyValue = $this->mapToObject($row[$columnName]); // Not supported, yet! 00281 } 00282 break; 00283 } 00284 } 00285 00286 if ($propertyValue !== NULL) { 00287 $object->_setProperty($propertyName, $propertyValue); 00288 } 00289 } 00290 } 00291 00292 /** 00293 * Creates a DateTime from an unix timestamp. If the input is empty 00294 * NULL is returned. 00295 * 00296 * @param integer $timestamp 00297 * @return DateTime 00298 */ 00299 protected function mapDateTime($timestamp) { 00300 if (empty($timestamp)) { // 0 -> NULL !!! 00301 return NULL; 00302 } else { 00303 return new DateTime(date('c', $timestamp)); 00304 } 00305 } 00306 00307 /** 00308 * Fetches a collection of objects related to a property of a parent object 00309 * 00310 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of 00311 * @param string $propertyName The name of the proxied property in it's parent 00312 * @param mixed $fieldValue The raw field value. 00313 * @param bool $enableLazyLoading A flag indication if the related objects should be lazy loaded 00314 * @param bool $performLanguageOverlay A flag indication if the related objects should be localized 00315 * @return Tx_Extbase_Persistence_LazyObjectStorage|Tx_Extbase_Persistence_QueryResultInterface The result 00316 */ 00317 public function fetchRelated(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $enableLazyLoading = TRUE) { 00318 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName); 00319 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName); 00320 if ($enableLazyLoading === TRUE && $propertyMetaData['lazy']) { 00321 if ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') { 00322 $result = $this->objectManager->create('Tx_Extbase_Persistence_LazyObjectStorage', $parentObject, $propertyName, $fieldValue); 00323 } else { 00324 if (empty($fieldValue)) { 00325 $result = NULL; 00326 } else { 00327 $result = $this->objectManager->create('Tx_Extbase_Persistence_LazyLoadingProxy', $parentObject, $propertyName, $fieldValue); 00328 } 00329 } 00330 } else { 00331 $result = $this->fetchRelatedEager($parentObject, $propertyName, $fieldValue); 00332 } 00333 return $result; 00334 } 00335 00336 /** 00337 * Fetches the related objects from the storage backend. 00338 * 00339 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of 00340 * @param string $propertyName The name of the proxied property in it's parent 00341 * @param mixed $fieldValue The raw field value. 00342 * @param bool $performLanguageOverlay A flag indication if the related objects should be localized 00343 * @return void 00344 */ 00345 protected function fetchRelatedEager(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') { 00346 if ($fieldValue === '') return array(); 00347 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue); 00348 return $query->execute(); 00349 } 00350 00351 /** 00352 * Builds and returns the prepared query, ready to be executed. 00353 * 00354 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject 00355 * @param string $propertyName 00356 * @param string $fieldValue 00357 * @return void 00358 */ 00359 protected function getPreparedQuery(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') { 00360 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName); 00361 $type = $this->getType(get_class($parentObject), $propertyName); 00362 00363 $query = $this->queryFactory->create($type); 00364 $query->getQuerySettings()->setRespectStoragePage(FALSE); 00365 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) { 00366 if ($columnMap->getChildSortByFieldName() !== NULL) { 00367 $query->setOrderings(array($columnMap->getChildSortByFieldName() => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING)); 00368 } 00369 } elseif ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) { 00370 $query->setSource($this->getSource($parentObject, $propertyName)); 00371 if ($columnMap->getChildSortByFieldName() !== NULL) { 00372 $query->setOrderings(array($columnMap->getChildSortByFieldName() => Tx_Extbase_Persistence_QueryInterface::ORDER_ASCENDING)); 00373 } 00374 } 00375 $query->matching($this->getConstraint($query, $parentObject, $propertyName, $fieldValue, $columnMap->getRelationTableMatchFields())); 00376 return $query; 00377 } 00378 00379 /** 00380 * Builds and returns the constraint for multi value properties. 00381 * 00382 * @param Tx_Extbase_Persistence_QueryInterface $query 00383 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject 00384 * @param string $propertyName 00385 * @param string $fieldValue 00386 * @param array $relationTableMatchFields 00387 * @return Tx_Extbase_Persistence_QOM_ConstraintInterface $constraint 00388 */ 00389 protected function getConstraint(Tx_Extbase_Persistence_QueryInterface $query, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '', $relationTableMatchFields = array()) { 00390 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName); 00391 if ($columnMap->getParentKeyFieldName() !== NULL) { 00392 $constraint = $query->equals($columnMap->getParentKeyFieldName(), $parentObject); 00393 if($columnMap->getParentTableFieldName() !== NULL) { 00394 $constraint = $query->logicalAnd( 00395 $constraint, 00396 $query->equals($columnMap->getParentTableFieldName(), $this->getDataMap(get_class($parentObject))->getTableName()) 00397 ); 00398 } 00399 } else { 00400 $constraint = $query->in('uid', t3lib_div::intExplode(',', $fieldValue)); 00401 } 00402 if (count($relationTableMatchFields) > 0) { 00403 foreach($relationTableMatchFields as $relationTableMatchFieldName => $relationTableMatchFieldValue) { 00404 $constraint = $query->logicalAnd($constraint, $query->equals($relationTableMatchFieldName, $relationTableMatchFieldValue)); 00405 } 00406 } 00407 return $constraint; 00408 } 00409 00410 /** 00411 * Builds and returns the source to build a join for a m:n relation. 00412 * 00413 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject 00414 * @param string $propertyName 00415 * @return Tx_Extbase_Persistence_QOM_SourceInterface $source 00416 */ 00417 protected function getSource(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName) { 00418 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName); 00419 $left = $this->qomFactory->selector(NULL, $columnMap->getRelationTableName()); 00420 $childClassName = $this->getType(get_class($parentObject), $propertyName); 00421 $right = $this->qomFactory->selector($childClassName, $columnMap->getChildTableName()); 00422 $joinCondition = $this->qomFactory->equiJoinCondition($columnMap->getRelationTableName(), $columnMap->getChildKeyFieldName(), $columnMap->getChildTableName(), 'uid'); 00423 $source = $this->qomFactory->join( 00424 $left, 00425 $right, 00426 Tx_Extbase_Persistence_QueryInterface::JCR_JOIN_TYPE_INNER, 00427 $joinCondition 00428 ); 00429 return $source; 00430 } 00431 00432 /** 00433 * Returns the given result as property value of the specified property type. 00434 * 00435 * @param mixed $result The result could be an object or an ObjectStorage 00436 * @param array $propertyMetaData The property meta data 00437 * @param Tx_Extbase_Persistence_QueryResultInterface|Tx_Extbase_Persistence_LoadingStrategyInterface $result The result 00438 * @return void 00439 */ 00440 public function mapResultToPropertyValue(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $result) { 00441 if ($result instanceof Tx_Extbase_Persistence_LoadingStrategyInterface) { 00442 $propertyValue = $result; 00443 } else { 00444 $propertyMetaData = $this->reflectionService->getClassSchema(get_class($parentObject))->getProperty($propertyName); 00445 $columnMap = $this->getDataMap(get_class($parentObject))->getColumnMap($propertyName); 00446 if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'SplObjectStorage', 'Tx_Extbase_Persistence_ObjectStorage'))) { 00447 $elementType = $this->getType(get_class($parentObject), $propertyName); 00448 $objects = array(); 00449 foreach ($result as $value) { 00450 $objects[] = $value; 00451 } 00452 00453 if ($propertyMetaData['type'] === 'ArrayObject') { 00454 $propertyValue = new ArrayObject($objects); 00455 } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') { 00456 $propertyValue = new Tx_Extbase_Persistence_ObjectStorage(); 00457 foreach ($objects as $object) { 00458 $propertyValue->attach($object); 00459 } 00460 $propertyValue->_memorizeCleanState(); 00461 } else { 00462 $propertyValue = $objects; 00463 } 00464 } elseif (strpos($propertyMetaData['type'], '_') !== FALSE) { 00465 if (is_object($result) && $result instanceof Tx_Extbase_Persistence_QueryResultInterface) { 00466 $propertyValue = $result->getFirst(); 00467 } else { 00468 $propertyValue = $result; 00469 } 00470 } 00471 } 00472 return $propertyValue; 00473 } 00474 00475 /** 00476 * Counts the number of related objects assigned to a property of a parent object 00477 * 00478 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The object instance this proxy is part of 00479 * @param string $propertyName The name of the proxied property in it's parent 00480 * @param mixed $fieldValue The raw field value. 00481 */ 00482 public function countRelated(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $fieldValue = '') { 00483 $query = $this->getPreparedQuery($parentObject, $propertyName, $fieldValue); 00484 return $query->execute()->count(); 00485 } 00486 00487 /** 00488 * Delegates the call to the Data Map. 00489 * Returns TRUE if the property is persistable (configured in $TCA) 00490 * 00491 * @param string $className The property name 00492 * @param string $propertyName The property name 00493 * @return boolean TRUE if the property is persistable (configured in $TCA) 00494 */ 00495 public function isPersistableProperty($className, $propertyName) { 00496 $dataMap = $this->getDataMap($className); 00497 return $dataMap->isPersistableProperty($propertyName); 00498 } 00499 00500 /** 00501 * Returns a data map for a given class name 00502 * 00503 * @param string $className The class name you want to fetch the Data Map for 00504 * @return Tx_Extbase_Persistence_Mapper_DataMap The data map 00505 */ 00506 public function getDataMap($className) { 00507 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); 00508 if (!isset($this->dataMaps[$className])) { 00509 $this->dataMaps[$className] = $this->dataMapFactory->buildDataMap($className); 00510 } 00511 return $this->dataMaps[$className]; 00512 } 00513 00514 /** 00515 * Returns the selector (table) name for a given class name. 00516 * 00517 * @param string $className 00518 * @return string The selector name 00519 */ 00520 public function convertClassNameToTableName($className = NULL) { 00521 if ($className !== NULL) { 00522 $tableName = $this->getDataMap($className)->getTableName(); 00523 } else { 00524 $tableName = strtolower($className); 00525 } 00526 return $tableName; 00527 } 00528 00529 /** 00530 * Returns the column name for a given property name of the specified class. 00531 * 00532 * @param string $className 00533 * @param string $propertyName 00534 * @return string The column name 00535 */ 00536 public function convertPropertyNameToColumnName($propertyName, $className = NULL) { 00537 if (!empty($className)) { 00538 $dataMap = $this->getDataMap($className); 00539 if ($dataMap !== NULL) { 00540 $columnMap = $dataMap->getColumnMap($propertyName); 00541 if ($columnMap !== NULL) { 00542 return $columnMap->getColumnName(); 00543 } 00544 } 00545 } 00546 return t3lib_div::camelCaseToLowerCaseUnderscored($propertyName); 00547 } 00548 00549 /** 00550 * Returns the type of a child object. 00551 * 00552 * @param string $parentClassName The class name of the object this proxy is part of 00553 * @param string $propertyName The name of the proxied property in it's parent 00554 * @return string The class name of the child object 00555 */ 00556 public function getType($parentClassName, $propertyName) { 00557 $propertyMetaData = $this->reflectionService->getClassSchema($parentClassName)->getProperty($propertyName); 00558 if (!empty($propertyMetaData['elementType'])) { 00559 $type = $propertyMetaData['elementType']; 00560 } elseif (!empty($propertyMetaData['type'])) { 00561 $type = $propertyMetaData['type']; 00562 } else { 00563 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Could not determine the child object type.', 1251315967); 00564 } 00565 return $type; 00566 } 00567 00568 } 00569 ?>
1.8.0