|
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 class is a backport of the corresponding class of FLOW3. 00009 * All credits go to the v5 team. 00010 * 00011 * This script is part of the TYPO3 project. The TYPO3 project is 00012 * free software; you can redistribute it and/or modify 00013 * it under the terms of the GNU General Public License as published by 00014 * the Free Software Foundation; either version 2 of the License, or 00015 * (at your option) any later version. 00016 * 00017 * The GNU General Public License can be found at 00018 * http://www.gnu.org/copyleft/gpl.html. 00019 * 00020 * This script is distributed in the hope that it will be useful, 00021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00023 * GNU General Public License for more details. 00024 * 00025 * This copyright notice MUST APPEAR in all copies of the script! 00026 ***************************************************************/ 00027 00028 /** 00029 * The Property Mapper maps properties from a source onto a given target object, often a 00030 * (domain-) model. Which properties are required and how they should be filtered can 00031 * be customized. 00032 * 00033 * During the mapping process, the property values are validated and the result of this 00034 * validation can be queried. 00035 * 00036 * The following code would map the property of the source array to the target: 00037 * 00038 * $target = new ArrayObject(); 00039 * $source = new ArrayObject( 00040 * array( 00041 * 'someProperty' => 'SomeValue' 00042 * ) 00043 * ); 00044 * $mapper->mapAndValidate(array('someProperty'), $source, $target); 00045 * 00046 * Now the target object equals the source object. 00047 * 00048 * @package Extbase 00049 * @subpackage Property 00050 * @version $Id: Mapper.php 2259 2010-04-29 07:53:46Z jocrau $ 00051 * @api 00052 */ 00053 class Tx_Extbase_Property_Mapper implements t3lib_Singleton { 00054 00055 /** 00056 * Results of the last mapping operation 00057 * @var Tx_Extbase_Property_MappingResults 00058 */ 00059 protected $mappingResults; 00060 00061 /** 00062 * @var Tx_Extbase_Validation_ValidatorResolver 00063 */ 00064 protected $validatorResolver; 00065 00066 /** 00067 * @var Tx_Extbase_Reflection_Service 00068 */ 00069 protected $reflectionService; 00070 00071 /** 00072 * @var Tx_Extbase_Persistence_ManagerInterface 00073 */ 00074 protected $persistenceManager; 00075 00076 /** 00077 * @var Tx_Extbase_Persistence_QueryFactory 00078 */ 00079 protected $queryFactory; 00080 00081 /** 00082 * @param Tx_Extbase_Validation_ValidatorResolver $validatorResolver 00083 * @return void 00084 */ 00085 public function injectValidatorResolver(Tx_Extbase_Validation_ValidatorResolver $validatorResolver) { 00086 $this->validatorResolver = $validatorResolver; 00087 } 00088 00089 /** 00090 * 00091 * @param Tx_Extbase_Persistence_QueryFactory $queryFactory 00092 * @return void 00093 */ 00094 public function injectQueryFactory(Tx_Extbase_Persistence_QueryFactory $queryFactory) { 00095 $this->queryFactory = $queryFactory; 00096 } 00097 00098 /** 00099 * @param Tx_Extbase_Persistence_Manager $persistenceManager 00100 * @return void 00101 */ 00102 public function injectPersistenceManager(Tx_Extbase_Persistence_Manager $persistenceManager) { 00103 $this->persistenceManager = $persistenceManager; 00104 } 00105 00106 /** 00107 * Injects the Reflection Service 00108 * 00109 * @param Tx_Extbase_Reflection_Service 00110 * @return void 00111 */ 00112 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) { 00113 $this->reflectionService = $reflectionService; 00114 } 00115 00116 /** 00117 * Maps the given properties to the target object and validates the properties according to the defined 00118 * validators. If the result object is not valid, the operation will be undone (the target object remains 00119 * unchanged) and this method returns FALSE. 00120 * 00121 * If in doubt, always prefer this method over the map() method because skipping validation can easily become 00122 * a security issue. 00123 * 00124 * @param array $propertyNames Names of the properties to map. 00125 * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object. 00126 * @param object $target The target object 00127 * @param Tx_Extbase_Validation_Validator_ObjectValidatorInterface $targetObjectValidator A validator used for validating the target object 00128 * @param array $optionalPropertyNames Names of optional properties. If a property is specified here and it doesn't exist in the source, no error is issued. 00129 * @return boolean TRUE if the mapped properties are valid, otherwise FALSE 00130 * @see getMappingResults() 00131 * @see map() 00132 * @api 00133 */ 00134 public function mapAndValidate(array $propertyNames, $source, $target, $optionalPropertyNames = array(), Tx_Extbase_Validation_Validator_ObjectValidatorInterface $targetObjectValidator) { 00135 $backupProperties = array(); 00136 00137 $this->map($propertyNames, $source, $backupProperties, $optionalPropertyNames); 00138 if ($this->mappingResults->hasErrors()) return FALSE; 00139 00140 $this->map($propertyNames, $source, $target, $optionalPropertyNames); 00141 if ($this->mappingResults->hasErrors()) return FALSE; 00142 00143 if ($targetObjectValidator->isValid($target) !== TRUE) { 00144 $this->addErrorsFromObjectValidator($targetObjectValidator->getErrors()); 00145 $backupMappingResult = $this->mappingResults; 00146 $this->map($propertyNames, $backupProperties, $source, $optionalPropertyNames); 00147 $this->mappingResults = $backupMappingResult; 00148 } 00149 return (!$this->mappingResults->hasErrors()); 00150 } 00151 00152 /** 00153 * Add errors to the mapping result from an object validator (property errors). 00154 * 00155 * @param array Array of Tx_Extbase_Validation_PropertyError 00156 * @return void 00157 */ 00158 protected function addErrorsFromObjectValidator($errors) { 00159 foreach ($errors as $error) { 00160 if ($error instanceof Tx_Extbase_Validation_PropertyError) { 00161 $propertyName = $error->getPropertyName(); 00162 $this->mappingResults->addError($error, $propertyName); 00163 } 00164 } 00165 } 00166 00167 /** 00168 * Maps the given properties to the target object WITHOUT VALIDATING THE RESULT. 00169 * If the properties could be set, this method returns TRUE, otherwise FALSE. 00170 * Returning TRUE does not mean that the target object is valid and secure! 00171 * 00172 * Only use this method if you're sure that you don't need validation! 00173 * 00174 * @param array $propertyNames Names of the properties to map. 00175 * @param mixed $source Source containing the properties to map to the target object. Must either be an array, ArrayObject or any other object. 00176 * @param object $target The target object 00177 * @param array $optionalPropertyNames Names of optional properties. If a property is specified here and it doesn't exist in the source, no error is issued. 00178 * @return boolean TRUE if the properties could be mapped, otherwise FALSE 00179 * @see mapAndValidate() 00180 * @api 00181 */ 00182 public function map(array $propertyNames, $source, $target, $optionalPropertyNames = array()) { 00183 if (!is_object($source) && !is_array($source)) throw new Tx_Extbase_Property_Exception_InvalidSource('The source object must be a valid object or array, ' . gettype($target) . ' given.', 1187807099); 00184 00185 if (is_string($target) && strpos($target, '_') !== FALSE) { 00186 return $this->transformToObject($source, $target, '--none--'); 00187 } 00188 00189 if (!is_object($target) && !is_array($target)) throw new Tx_Extbase_Property_Exception_InvalidTarget('The target object must be a valid object or array, ' . gettype($target) . ' given.', 1187807099); 00190 00191 $this->mappingResults = new Tx_Extbase_Property_MappingResults(); 00192 if (is_object($target)) { 00193 $targetClassSchema = $this->reflectionService->getClassSchema(get_class($target)); 00194 } else { 00195 $targetClassSchema = NULL; 00196 } 00197 00198 foreach ($propertyNames as $propertyName) { 00199 $propertyValue = NULL; 00200 if (is_array($source) || $source instanceof ArrayAccess) { 00201 if (isset($source[$propertyName])) { 00202 $propertyValue = $source[$propertyName]; 00203 } 00204 } else { 00205 $propertyValue = Tx_Extbase_Reflection_ObjectAccess::getProperty($source, $propertyName); 00206 } 00207 00208 if ($propertyValue === NULL && !in_array($propertyName, $optionalPropertyNames)) { 00209 $this->mappingResults->addError(new Tx_Extbase_Error_Error("Required property '$propertyName' does not exist." , 1236785359), $propertyName); 00210 } else { 00211 if ($targetClassSchema !== NULL && $targetClassSchema->hasProperty($propertyName)) { 00212 $propertyMetaData = $targetClassSchema->getProperty($propertyName); 00213 00214 if (in_array($propertyMetaData['type'], array('array', 'ArrayObject', 'Tx_Extbase_Persistence_ObjectStorage')) && (strpos($propertyMetaData['elementType'], '_') !== FALSE || $propertyValue === '')) { 00215 $objects = array(); 00216 if (is_array($propertyValue)) { 00217 foreach ($propertyValue as $value) { 00218 $objects[] = $this->transformToObject($value, $propertyMetaData['elementType'], $propertyName); 00219 } 00220 } 00221 00222 // make sure we hand out what is expected 00223 if ($propertyMetaData['type'] === 'ArrayObject') { 00224 $propertyValue = new ArrayObject($objects); 00225 } elseif ($propertyMetaData['type'] === 'Tx_Extbase_Persistence_ObjectStorage') { 00226 $propertyValue = new Tx_Extbase_Persistence_ObjectStorage(); 00227 foreach ($objects as $object) { 00228 $propertyValue->attach($object); 00229 } 00230 } else { 00231 $propertyValue = $objects; 00232 } 00233 } elseif ($propertyMetaData['type'] === 'DateTime' || strpos($propertyMetaData['type'], '_') !== FALSE) { 00234 $propertyValue = $this->transformToObject($propertyValue, $propertyMetaData['type'], $propertyName); 00235 if ($propertyValue === NULL) { 00236 continue; 00237 } 00238 } 00239 } elseif ($targetClassSchema !== NULL) { 00240 $this->mappingResults->addError(new Tx_Extbase_Error_Error("Property '$propertyName' does not exist in target class schema." , 1251813614), $propertyName); 00241 } 00242 00243 if (is_array($target)) { 00244 $target[$propertyName] = $propertyValue; 00245 } elseif (Tx_Extbase_Reflection_ObjectAccess::setProperty($target, $propertyName, $propertyValue) === FALSE) { 00246 $this->mappingResults->addError(new Tx_Extbase_Error_Error("Property '$propertyName' could not be set." , 1236783102), $propertyName); 00247 } 00248 } 00249 } 00250 00251 return !$this->mappingResults->hasErrors(); 00252 } 00253 00254 /** 00255 * Transforms strings with UUIDs or arrays with UUIDs/identity properties 00256 * into the requested type, if possible. 00257 * 00258 * @param mixed $propertyValue The value to transform, string or array 00259 * @param string $targetType The type to transform to 00260 * @param string $propertyName In case of an error we add this to the error message 00261 * @return object The object, when no transformation was possible this may return NULL as well 00262 */ 00263 protected function transformToObject($propertyValue, $targetType, $propertyName) { 00264 if ($targetType === 'DateTime' || is_subclass_of($targetType, 'DateTime')) { 00265 // TODO replace this with converter implementation of FLOW3 00266 if ($propertyValue === '') { 00267 $propertyValue = NULL; 00268 } else { 00269 try { 00270 $propertyValue = new $targetType($propertyValue); 00271 } catch (Exception $e) { 00272 $propertyValue = NULL; 00273 } 00274 } 00275 } else { 00276 if (is_numeric($propertyValue)) { 00277 $propertyValue = $this->findObjectByUid($targetType, $propertyValue); 00278 if ($propertyValue === FALSE) { 00279 $this->mappingResults->addError(new Tx_Extbase_Error_Error('Querying the repository for the specified object with UUID ' . $propertyValue . ' was not successful.' , 1249379517), $propertyName); 00280 } 00281 } elseif (is_array($propertyValue)) { 00282 if (isset($propertyValue['__identity'])) { 00283 $existingObject = $this->findObjectByUid($targetType, $propertyValue['__identity']); 00284 if ($existingObject === FALSE) throw new Tx_Extbase_Property_Exception_TargetNotFound('Querying the repository for the specified object was not successful.', 1237305720); 00285 unset($propertyValue['__identity']); 00286 if (count($propertyValue) === 0) { 00287 $propertyValue = $existingObject; 00288 } elseif ($existingObject !== NULL) { 00289 $newObject = clone $existingObject; 00290 if ($this->map(array_keys($propertyValue), $propertyValue, $newObject)) { 00291 $propertyValue = $newObject; 00292 } else { 00293 $propertyValue = NULL; 00294 } 00295 } 00296 } else { 00297 $newObject = new $targetType; 00298 if ($this->map(array_keys($propertyValue), $propertyValue, $newObject)) { 00299 $propertyValue = $newObject; 00300 } else { 00301 $propertyValue = NULL; 00302 } 00303 } 00304 } else { 00305 throw new InvalidArgumentException('transformToObject() accepts only numeric values and arrays.', 1251814355); 00306 } 00307 } 00308 00309 return $propertyValue; 00310 } 00311 00312 /** 00313 * Returns the results of the last mapping operation. 00314 * 00315 * @return Tx_Extbase_Property_MappingResults The mapping results (or NULL if no mapping has been carried out yet) 00316 * @api 00317 */ 00318 public function getMappingResults() { 00319 return $this->mappingResults; 00320 } 00321 00322 /** 00323 * Finds an object from the repository by searching for its technical UID. 00324 * 00325 * @param string $dataType the data type to fetch 00326 * @param int $uid The object's uid 00327 * @return object Either the object matching the uid or, if none or more than one object was found, NULL 00328 */ 00329 // TODO This is duplicated code; see Argument class 00330 protected function findObjectByUid($dataType, $uid) { 00331 $query = $this->queryFactory->create($dataType); 00332 $query->getQuerySettings()->setRespectSysLanguage(FALSE); 00333 $query->getQuerySettings()->setRespectStoragePage(FALSE); 00334 return $query->matching( 00335 $query->equals('uid', intval($uid))) 00336 ->execute() 00337 ->getFirst(); 00338 } 00339 } 00340 00341 ?>
1.8.0