‪TYPO3CMS  11.5
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 
18 use Psr\Container\ContainerInterface;
33 
39 {
40  protected ContainerInterface ‪$container;
42 
53  protected ‪$typeConverters = [];
54 
60  protected ‪$messages;
61 
62  public function ‪__construct(
63  ContainerInterface ‪$container,
65  ) {
66  $this->container = ‪$container;
67  $this->configurationBuilder = ‪$configurationBuilder;
68  }
69 
77  public function ‪initializeObject()
78  {
79  $this->‪resetMessages();
80  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'] as $typeConverterClassName) {
81  if ($this->container->has($typeConverterClassName)) {
82  $typeConverter = $this->container->get($typeConverterClassName);
83  } else {
84  // @deprecated since v11, will be removed in v12.
85  $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
86  $typeConverter = $objectManager->get($typeConverterClassName);
87  }
88  foreach ($typeConverter->getSupportedSourceTypes() as $supportedSourceType) {
89  if (isset($this->typeConverters[$supportedSourceType][$typeConverter->getSupportedTargetType()][$typeConverter->getPriority()])) {
90  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);
91  }
92  $this->typeConverters[$supportedSourceType][$typeConverter->getSupportedTargetType()][$typeConverter->getPriority()] = $typeConverter;
93  }
94  }
95  }
96 
106  public function ‪convert($source, $targetType, PropertyMappingConfigurationInterface $configuration = null)
107  {
108  if ($configuration === null) {
109  $configuration = $this->configurationBuilder->build();
110  }
111  $currentPropertyPath = [];
112  try {
113  $result = $this->‪doMapping($source, $targetType, $configuration, $currentPropertyPath);
114  if ($result instanceof Error) {
115  return null;
116  }
117 
118  return $result;
119  } catch (TargetNotFoundException $e) {
120  throw $e;
121  } catch (\Exception $e) {
122  throw new Exception('Exception while property mapping at property path "' . implode('.', $currentPropertyPath) . '": ' . $e->getMessage(), 1297759968, $e);
123  }
124  }
125 
131  public function ‪getMessages()
132  {
133  return ‪$this->messages;
134  }
135 
139  public function ‪resetMessages(): void
140  {
141  $this->messages = new Result();
142  }
143 
155  protected function ‪doMapping($source, $targetType, PropertyMappingConfigurationInterface $configuration, &$currentPropertyPath)
156  {
157  if (is_object($source)) {
158  $targetType = $this->‪parseCompositeType($targetType);
159  if ($source instanceof $targetType) {
160  return $source;
161  }
162  }
163 
164  if ($source === null) {
165  $source = '';
166  }
167 
168  $typeConverter = $this->‪findTypeConverter($source, $targetType, $configuration);
169  $targetType = $typeConverter->getTargetTypeForSource($source, $targetType, $configuration);
170 
171  if (!is_object($typeConverter) || !$typeConverter instanceof TypeConverterInterface) {
172  // todo: this Exception is never thrown as findTypeConverter returns an object or throws an Exception.
173  throw new TypeConverterException(
174  'Type converter for "' . $source . '" -> "' . $targetType . '" not found.',
175  1476045062
176  );
177  }
178 
179  $convertedChildProperties = [];
180  foreach ($typeConverter->getSourceChildPropertiesToBeConverted($source) as $sourcePropertyName => $sourcePropertyValue) {
181  $targetPropertyName = $configuration->getTargetPropertyName($sourcePropertyName);
182  if ($configuration->shouldSkip($targetPropertyName)) {
183  continue;
184  }
185 
186  if (!$configuration->shouldMap($targetPropertyName)) {
187  if ($configuration->shouldSkipUnknownProperties()) {
188  continue;
189  }
190  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);
191  }
192 
193  $targetPropertyType = $typeConverter->getTypeOfChildProperty($targetType, $targetPropertyName, $configuration);
194 
195  $subConfiguration = $configuration->getConfigurationFor($targetPropertyName);
196 
197  $currentPropertyPath[] = $targetPropertyName;
198  $targetPropertyValue = $this->doMapping($sourcePropertyValue, $targetPropertyType, $subConfiguration, $currentPropertyPath);
199  array_pop($currentPropertyPath);
200  if (!($targetPropertyValue instanceof Error)) {
201  $convertedChildProperties[$targetPropertyName] = $targetPropertyValue;
202  }
203  }
204  $result = $typeConverter->convertFrom($source, $targetType, $convertedChildProperties, $configuration);
205 
206  if ($result instanceof Error) {
207  $this->messages->forProperty(implode('.', $currentPropertyPath))->addError($result);
208  }
209 
210  return $result;
211  }
212 
223  protected function findTypeConverter($source, $targetType, PropertyMappingConfigurationInterface $configuration)
224  {
225  if ($configuration->getTypeConverter() !== null) {
226  return $configuration->getTypeConverter();
227  }
228 
229  $sourceType = $this->determineSourceType($source);
230 
231  if (!is_string($targetType)) {
232  throw new InvalidTargetException('The target type was no string, but of type "' . gettype($targetType) . '"', 1297941727);
233  }
234 
235  $targetType = $this->parseCompositeType($targetType);
236  // This is needed to correctly convert old class names to new ones
237  // This compatibility layer will be removed with 7.0
238  $targetType = ClassLoadingInformation::getClassNameForAlias($targetType);
239 
240  $targetType = TypeHandlingUtility::normalizeType($targetType);
241 
242  $converter = null;
243 
244  if (TypeHandlingUtility::isSimpleType($targetType)) {
245  if (isset($this->typeConverters[$sourceType][$targetType])) {
246  $converter = $this->findEligibleConverterWithHighestPriority($this->typeConverters[$sourceType][$targetType], $source, $targetType);
247  }
248  } else {
249  $converter = $this->findFirstEligibleTypeConverterInObjectHierarchy($source, $sourceType, $targetType);
250  }
251 
252  if ($converter === null) {
253  throw new TypeConverterException(
254  'No converter found which can be used to ‪convert from "' . $sourceType . '" to "' . $targetType . '".',
255  1476044883
256  );
257  }
258 
259  return $converter;
260  }
261 
271  protected function findFirstEligibleTypeConverterInObjectHierarchy($source, $sourceType, $targetClass)
272  {
273  if (!class_exists($targetClass) && !interface_exists($targetClass)) {
274  throw new InvalidTargetException('Could not find a suitable type converter for "' . $targetClass . '" because no such class or interface exists.', 1297948764);
275  }
276 
277  if (!isset($this->typeConverters[$sourceType])) {
278  return null;
279  }
280 
281  $convertersForSource = $this->typeConverters[$sourceType];
282  if (isset($convertersForSource[$targetClass])) {
283  $converter = $this->‪findEligibleConverterWithHighestPriority($convertersForSource[$targetClass], $source, $targetClass);
284  if ($converter !== null) {
285  return $converter;
286  }
287  }
288 
289  foreach (class_parents($targetClass) as $parentClass) {
290  if (!isset($convertersForSource[$parentClass])) {
291  continue;
292  }
293 
294  $converter = $this->‪findEligibleConverterWithHighestPriority($convertersForSource[$parentClass], $source, $targetClass);
295  if ($converter !== null) {
296  return $converter;
297  }
298  }
299 
300  $converters = $this->‪getConvertersForInterfaces($convertersForSource, class_implements($targetClass) ?: []);
301  $converter = $this->‪findEligibleConverterWithHighestPriority($converters, $source, $targetClass);
302 
303  if ($converter !== null) {
304  return $converter;
305  }
306  if (isset($convertersForSource['object'])) {
307  return $this->‪findEligibleConverterWithHighestPriority($convertersForSource['object'], $source, $targetClass);
308  }
309 
310  // todo: this case is impossible because at this point there must be an ObjectConverter
311  // todo: which allowed the processing up to this point.
312  return null;
313  }
314 
321  protected function ‪findEligibleConverterWithHighestPriority($converters, $source, $targetType)
322  {
323  if (!is_array($converters)) {
324  // todo: this case is impossible as initializeObject always defines an array.
325  return null;
326  }
327  krsort($converters, SORT_NUMERIC);
328  reset($converters);
330  foreach ($converters as $converter) {
331  if ($converter->canConvertFrom($source, $targetType)) {
332  return $converter;
333  }
334  }
335  return null;
336  }
337 
344  protected function ‪getConvertersForInterfaces(array $convertersForSource, array $interfaceNames)
345  {
346  $convertersForInterface = [];
347  foreach ($interfaceNames as $implementedInterface) {
348  if (isset($convertersForSource[$implementedInterface])) {
349  foreach ($convertersForSource[$implementedInterface] as $priority => $converter) {
350  if (isset($convertersForInterface[$priority])) {
351  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);
352  }
353  $convertersForInterface[$priority] = $converter;
354  }
355  }
356  }
357  return $convertersForInterface;
358  }
359 
367  protected function ‪determineSourceType($source)
368  {
369  if (is_string($source)) {
370  return 'string';
371  }
372  if (is_array($source)) {
373  return 'array';
374  }
375  if (is_float($source)) {
376  return 'float';
377  }
378  if (is_int($source)) {
379  return 'integer';
380  }
381  if (is_bool($source)) {
382  return 'boolean';
383  }
384  throw new InvalidSourceException('The source is not of type string, array, float, integer or boolean, but of type "' . gettype($source) . '"', 1297773150);
385  }
386 
395  public function ‪parseCompositeType($compositeType)
396  {
397  if (str_contains($compositeType, '<')) {
398  $compositeType = substr($compositeType, 0, (int)strpos($compositeType, '<'));
399  }
400  return $compositeType;
401  }
402 }
‪TYPO3\CMS\Extbase\Property\Exception\TypeConverterException
Definition: TypeConverterException.php:25
‪TYPO3\CMS\Extbase\Property\Exception\DuplicateTypeConverterException
Definition: DuplicateTypeConverterException.php:25
‪TYPO3\CMS\Extbase\Property\TypeConverterInterface
Definition: TypeConverterInterface.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper\__construct
‪__construct(ContainerInterface $container, PropertyMappingConfigurationBuilder $configurationBuilder)
Definition: PropertyMapper.php:60
‪TYPO3\CMS\Extbase\Property\PropertyMapper\resetMessages
‪resetMessages()
Definition: PropertyMapper.php:137
‪TYPO3\CMS\Extbase\Property\PropertyMapper\$typeConverters
‪array $typeConverters
Definition: PropertyMapper.php:52
‪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:104
‪TYPO3\CMS\Extbase\Property
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface
Definition: PropertyMappingConfigurationInterface.php:22
‪TYPO3\CMS\Extbase\Error\Result
Definition: Result.php:24
‪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\$configurationBuilder
‪PropertyMappingConfigurationBuilder $configurationBuilder
Definition: PropertyMapper.php:41
‪TYPO3\CMS\Extbase\Property\PropertyMapper\parseCompositeType
‪string parseCompositeType($compositeType)
Definition: PropertyMapper.php:393
‪TYPO3\CMS\Extbase\Property\PropertyMapper\findTypeConverter
‪TYPO3 CMS Extbase Property TypeConverterInterface findTypeConverter($source, $targetType, PropertyMappingConfigurationInterface $configuration)
Definition: PropertyMapper.php:221
‪TYPO3\CMS\Extbase\Property\PropertyMapper\$container
‪ContainerInterface $container
Definition: PropertyMapper.php:40
‪TYPO3\CMS\Extbase\Property\PropertyMapper\getConvertersForInterfaces
‪array getConvertersForInterfaces(array $convertersForSource, array $interfaceNames)
Definition: PropertyMapper.php:342
‪TYPO3\CMS\Extbase\Property\Exception
Definition: Exception.php:25
‪TYPO3\CMS\Extbase\Property\PropertyMapper
Definition: PropertyMapper.php:39
‪TYPO3\CMS\Extbase\Property\PropertyMapper\$messages
‪TYPO3 CMS Extbase Error Result $messages
Definition: PropertyMapper.php:58
‪TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter
Definition: AbstractTypeConverter.php:34
‪TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
Definition: InvalidPropertyMappingConfigurationException.php:25
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationBuilder
Definition: PropertyMappingConfigurationBuilder.php:26
‪TYPO3\CMS\Extbase\Property\PropertyMapper\determineSourceType
‪string determineSourceType($source)
Definition: PropertyMapper.php:365
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
Definition: InvalidTargetException.php:25
‪TYPO3\CMS\Extbase\Property\PropertyMapper\findEligibleConverterWithHighestPriority
‪mixed findEligibleConverterWithHighestPriority($converters, $source, $targetType)
Definition: PropertyMapper.php:319
‪TYPO3\CMS\Extbase\Property\PropertyMapper\getMessages
‪TYPO3 CMS Extbase Error Result getMessages()
Definition: PropertyMapper.php:129
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException
Definition: TargetNotFoundException.php:25
‪TYPO3\CMS\Extbase\Object\ObjectManager
Definition: ObjectManager.php:31
‪TYPO3\CMS\Extbase\Property\PropertyMapper\doMapping
‪mixed doMapping($source, $targetType, PropertyMappingConfigurationInterface $configuration, &$currentPropertyPath)
Definition: PropertyMapper.php:153
‪TYPO3\CMS\Extbase\Property\PropertyMapper\initializeObject
‪initializeObject()
Definition: PropertyMapper.php:75
‪TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException
Definition: InvalidSourceException.php:25