‪TYPO3CMS  10.4
PropertyMapper.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
31 
37 {
41  protected ‪$objectManager;
42 
46  protected ‪$configurationBuilder;
47 
58  protected ‪$typeConverters = [];
59 
65  protected ‪$messages;
66 
72  {
73  $this->objectManager = ‪$objectManager;
74  }
75 
81  {
82  $this->configurationBuilder = ‪$configurationBuilder;
83  }
84 
92  public function ‪initializeObject()
93  {
94  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'] as $typeConverterClassName) {
95  $typeConverter = $this->objectManager->get($typeConverterClassName);
96  foreach ($typeConverter->getSupportedSourceTypes() as $supportedSourceType) {
97  if (isset($this->typeConverters[$supportedSourceType][$typeConverter->getSupportedTargetType()][$typeConverter->getPriority()])) {
98  throw new ‪DuplicateTypeConverterException('There exist at least two converters which handle the conversion from "' . $supportedSourceType . '" to "' . $typeConverter->getSupportedTargetType() . '" with priority "' . $typeConverter->getPriority() . '": ' . get_class($this->typeConverters[$supportedSourceType][$typeConverter->getSupportedTargetType()][$typeConverter->getPriority()]) . ' and ' . get_class($typeConverter), 1297951378);
99  }
100  $this->typeConverters[$supportedSourceType][$typeConverter->getSupportedTargetType()][$typeConverter->getPriority()] = $typeConverter;
101  }
102  }
103  }
104 
114  public function ‪convert($source, $targetType, ‪PropertyMappingConfigurationInterface $configuration = null)
115  {
116  if ($configuration === null) {
117  $configuration = $this->configurationBuilder->build();
118  }
119  $currentPropertyPath = [];
120  $this->messages = new ‪Result();
121  try {
122  $result = $this->‪doMapping($source, $targetType, $configuration, $currentPropertyPath);
123  if ($result instanceof ‪Error) {
124  return null;
125  }
126 
127  return $result;
128  } catch (‪TargetNotFoundException $e) {
129  throw $e;
130  } catch (\‪Exception $e) {
131  throw new ‪Exception('Exception while property mapping at property path "' . implode('.', $currentPropertyPath) . '": ' . $e->getMessage(), 1297759968, $e);
132  }
133  }
134 
140  public function ‪getMessages()
141  {
142  return ‪$this->messages;
143  }
144 
156  protected function ‪doMapping($source, $targetType, ‪PropertyMappingConfigurationInterface $configuration, &$currentPropertyPath)
157  {
158  if (is_object($source)) {
159  $targetType = $this->‪parseCompositeType($targetType);
160  if ($source instanceof $targetType) {
161  return $source;
162  }
163  }
164 
165  if ($source === null) {
166  $source = '';
167  }
168 
169  $typeConverter = $this->‪findTypeConverter($source, $targetType, $configuration);
170  $targetType = $typeConverter->getTargetTypeForSource($source, $targetType, $configuration);
171 
172  if (!is_object($typeConverter) || !$typeConverter instanceof ‪TypeConverterInterface) {
173  // todo: this Exception is never thrown as findTypeConverter returns an object or throws an Exception.
174  throw new ‪TypeConverterException(
175  'Type converter for "' . $source . '" -> "' . $targetType . '" not found.',
176  1476045062
177  );
178  }
179 
180  $convertedChildProperties = [];
181  foreach ($typeConverter->getSourceChildPropertiesToBeConverted($source) as $sourcePropertyName => $sourcePropertyValue) {
182  $targetPropertyName = $configuration->‪getTargetPropertyName($sourcePropertyName);
183  if ($configuration->‪shouldSkip($targetPropertyName)) {
184  continue;
185  }
186 
187  if (!$configuration->shouldMap($targetPropertyName)) {
188  if ($configuration->‪shouldSkipUnknownProperties()) {
189  continue;
190  }
191  throw new ‪InvalidPropertyMappingConfigurationException('It is not allowed to map property "' . $targetPropertyName . '". You need to use $propertyMappingConfiguration->allowProperties(\'' . $targetPropertyName . '\') to enable mapping of this property.', 1355155913);
192  }
193 
194  $targetPropertyType = $typeConverter->getTypeOfChildProperty($targetType, $targetPropertyName, $configuration);
195 
196  $subConfiguration = $configuration->getConfigurationFor($targetPropertyName);
197 
198  $currentPropertyPath[] = $targetPropertyName;
199  $targetPropertyValue = $this->doMapping($sourcePropertyValue, $targetPropertyType, $subConfiguration, $currentPropertyPath);
200  array_pop($currentPropertyPath);
201  if (!($targetPropertyValue instanceof Error)) {
202  $convertedChildProperties[$targetPropertyName] = $targetPropertyValue;
203  }
204  }
205  $result = $typeConverter->convertFrom($source, $targetType, $convertedChildProperties, $configuration);
206 
207  if ($result instanceof Error) {
208  $this->messages->forProperty(implode('.', $currentPropertyPath))->addError($result);
209  }
210 
211  return $result;
212  }
213 
224  protected function findTypeConverter($source, $targetType, PropertyMappingConfigurationInterface $configuration)
225  {
226  if ($configuration->getTypeConverter() !== null) {
227  return $configuration->getTypeConverter();
228  }
229 
230  $sourceType = $this->determineSourceType($source);
231 
232  if (!is_string($targetType)) {
233  throw new InvalidTargetException('The target type was no string, but of type "' . gettype($targetType) . '"', 1297941727);
234  }
235 
236  $targetType = $this->parseCompositeType($targetType);
237  // This is needed to correctly convert old class names to new ones
238  // This compatibility layer will be removed with 7.0
239  $targetType = ClassLoadingInformation::getClassNameForAlias($targetType);
240 
241  $targetType = TypeHandlingUtility::normalizeType($targetType);
242 
243  $converter = null;
244 
245  if (TypeHandlingUtility::isSimpleType($targetType)) {
246  if (isset($this->typeConverters[$sourceType][$targetType])) {
247  $converter = $this->findEligibleConverterWithHighestPriority($this->typeConverters[$sourceType][$targetType], $source, $targetType);
248  }
249  } else {
250  $converter = $this->findFirstEligibleTypeConverterInObjectHierarchy($source, $sourceType, $targetType);
251  }
252 
253  if ($converter === null) {
254  throw new TypeConverterException(
255  'No converter found which can be used to ‪convert from "' . $sourceType . '" to "' . $targetType . '".',
256  1476044883
257  );
258  }
259 
260  return $converter;
261  }
262 
272  protected function findFirstEligibleTypeConverterInObjectHierarchy($source, $sourceType, $targetClass)
273  {
274  if (!class_exists($targetClass) && !interface_exists($targetClass)) {
275  throw new InvalidTargetException('Could not find a suitable type converter for "' . $targetClass . '" because no such class or interface exists.', 1297948764);
276  }
277 
278  if (!isset($this->typeConverters[$sourceType])) {
279  return null;
280  }
281 
282  $convertersForSource = $this->typeConverters[$sourceType];
283  if (isset($convertersForSource[$targetClass])) {
284  $converter = $this->‪findEligibleConverterWithHighestPriority($convertersForSource[$targetClass], $source, $targetClass);
285  if ($converter !== null) {
286  return $converter;
287  }
288  }
289 
290  foreach (class_parents($targetClass) as $parentClass) {
291  if (!isset($convertersForSource[$parentClass])) {
292  continue;
293  }
294 
295  $converter = $this->‪findEligibleConverterWithHighestPriority($convertersForSource[$parentClass], $source, $targetClass);
296  if ($converter !== null) {
297  return $converter;
298  }
299  }
300 
301  $converters = $this->‪getConvertersForInterfaces($convertersForSource, class_implements($targetClass));
302  $converter = $this->‪findEligibleConverterWithHighestPriority($converters, $source, $targetClass);
303 
304  if ($converter !== null) {
305  return $converter;
306  }
307  if (isset($convertersForSource['object'])) {
308  return $this->‪findEligibleConverterWithHighestPriority($convertersForSource['object'], $source, $targetClass);
309  }
310 
311  // todo: this case is impossible because at this point there must be an ObjectConverter
312  // todo: which allowed the processing up to this point.
313  return null;
314  }
315 
322  protected function ‪findEligibleConverterWithHighestPriority($converters, $source, $targetType)
323  {
324  if (!is_array($converters)) {
325  // todo: this case is impossible as initializeObject always defines an array.
326  return null;
327  }
328  krsort($converters, SORT_NUMERIC);
329  reset($converters);
331  foreach ($converters as $converter) {
332  if ($converter->canConvertFrom($source, $targetType)) {
333  return $converter;
334  }
335  }
336  return null;
337  }
338 
345  protected function ‪getConvertersForInterfaces(array $convertersForSource, array $interfaceNames)
346  {
347  $convertersForInterface = [];
348  foreach ($interfaceNames as $implementedInterface) {
349  if (isset($convertersForSource[$implementedInterface])) {
350  foreach ($convertersForSource[$implementedInterface] as $priority => $converter) {
351  if (isset($convertersForInterface[$priority])) {
352  throw new ‪DuplicateTypeConverterException('There exist at least two converters which handle the conversion to an interface with priority "' . $priority . '". ' . get_class($convertersForInterface[$priority]) . ' and ' . get_class($converter), 1297951338);
353  }
354  $convertersForInterface[$priority] = $converter;
355  }
356  }
357  }
358  return $convertersForInterface;
359  }
360 
368  protected function ‪determineSourceType($source)
369  {
370  if (is_string($source)) {
371  return 'string';
372  }
373  if (is_array($source)) {
374  return 'array';
375  }
376  if (is_float($source)) {
377  return 'float';
378  }
379  if (is_int($source)) {
380  return 'integer';
381  }
382  if (is_bool($source)) {
383  return 'boolean';
384  }
385  throw new ‪InvalidSourceException('The source is not of type string, array, float, integer or boolean, but of type "' . gettype($source) . '"', 1297773150);
386  }
387 
396  public function ‪parseCompositeType($compositeType)
397  {
398  if (strpos($compositeType, '<') !== false) {
399  $compositeType = substr($compositeType, 0, strpos($compositeType, '<'));
400  }
401  return $compositeType;
402  }
403 }
‪TYPO3\CMS\Extbase\Property\Exception\TypeConverterException
Definition: TypeConverterException.php:26
‪TYPO3\CMS\Extbase\Property\Exception\DuplicateTypeConverterException
Definition: DuplicateTypeConverterException.php:26
‪TYPO3\CMS\Extbase\Property\TypeConverterInterface
Definition: TypeConverterInterface.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper\$typeConverters
‪array $typeConverters
Definition: PropertyMapper.php:55
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface\getTargetPropertyName
‪string getTargetPropertyName($sourcePropertyName)
‪TYPO3\CMS\Core\Core\ClassLoadingInformation
Definition: ClassLoadingInformation.php:35
‪TYPO3\CMS\Extbase\Property\PropertyMapper\convert
‪mixed convert($source, $targetType, PropertyMappingConfigurationInterface $configuration=null)
Definition: PropertyMapper.php:110
‪TYPO3\CMS\Extbase\Property
‪TYPO3\CMS\Extbase\Property\PropertyMapper\$configurationBuilder
‪TYPO3 CMS Extbase Property PropertyMappingConfigurationBuilder $configurationBuilder
Definition: PropertyMapper.php:44
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface
Definition: PropertyMappingConfigurationInterface.php:22
‪TYPO3\CMS\Extbase\Error\Result
Definition: Result.php:24
‪TYPO3\CMS\Extbase\Object\ObjectManagerInterface
Definition: ObjectManagerInterface.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper\$objectManager
‪TYPO3 CMS Extbase Object ObjectManagerInterface $objectManager
Definition: PropertyMapper.php:40
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface\shouldSkip
‪bool shouldSkip($propertyName)
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface\shouldSkipUnknownProperties
‪bool shouldSkipUnknownProperties()
‪TYPO3\CMS\Extbase\Error\Error
Definition: Error.php:25
‪TYPO3\CMS\Extbase\Utility\TypeHandlingUtility
Definition: TypeHandlingUtility.php:29
‪TYPO3\CMS\Extbase\Property\PropertyMapper\parseCompositeType
‪string parseCompositeType($compositeType)
Definition: PropertyMapper.php:392
‪TYPO3\CMS\Extbase\Property\PropertyMapper\findTypeConverter
‪TYPO3 CMS Extbase Property TypeConverterInterface findTypeConverter($source, $targetType, PropertyMappingConfigurationInterface $configuration)
Definition: PropertyMapper.php:220
‪TYPO3\CMS\Extbase\Property\PropertyMapper\getConvertersForInterfaces
‪array getConvertersForInterfaces(array $convertersForSource, array $interfaceNames)
Definition: PropertyMapper.php:341
‪TYPO3\CMS\Extbase\Property\Exception
Definition: Exception.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper
Definition: PropertyMapper.php:37
‪TYPO3\CMS\Extbase\Property\PropertyMapper\$messages
‪TYPO3 CMS Extbase Error Result $messages
Definition: PropertyMapper.php:61
‪TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter
Definition: AbstractTypeConverter.php:34
‪TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
Definition: InvalidPropertyMappingConfigurationException.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationBuilder
Definition: PropertyMappingConfigurationBuilder.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper\determineSourceType
‪string determineSourceType($source)
Definition: PropertyMapper.php:364
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
Definition: InvalidTargetException.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper\findEligibleConverterWithHighestPriority
‪mixed findEligibleConverterWithHighestPriority($converters, $source, $targetType)
Definition: PropertyMapper.php:318
‪TYPO3\CMS\Extbase\Property\PropertyMapper\injectConfigurationBuilder
‪injectConfigurationBuilder(PropertyMappingConfigurationBuilder $configurationBuilder)
Definition: PropertyMapper.php:76
‪TYPO3\CMS\Extbase\Property\PropertyMapper\getMessages
‪TYPO3 CMS Extbase Error Result getMessages()
Definition: PropertyMapper.php:136
‪TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException
Definition: TargetNotFoundException.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper\doMapping
‪mixed doMapping($source, $targetType, PropertyMappingConfigurationInterface $configuration, &$currentPropertyPath)
Definition: PropertyMapper.php:152
‪TYPO3\CMS\Extbase\Property\PropertyMapper\initializeObject
‪initializeObject()
Definition: PropertyMapper.php:88
‪TYPO3\CMS\Extbase\Property\PropertyMapper\injectObjectManager
‪injectObjectManager(ObjectManagerInterface $objectManager)
Definition: PropertyMapper.php:67
‪TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException
Definition: InvalidSourceException.php:26