TYPO3 API  SVNRelease
DataMapper.php
Go to the documentation of this file.
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 ?>