‪TYPO3CMS  11.5
ObjectConverter.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Container\ContainerInterface;
32 
37 {
41  public const ‪CONFIGURATION_TARGET_TYPE = 3;
42 
47 
51  protected ‪$sourceTypes = ['array'];
52 
56  protected ‪$targetType = 'object';
57 
61  protected ‪$priority = 10;
62 
67  protected ‪$objectContainer;
68 
69  protected ContainerInterface ‪$container;
70 
74  protected ‪$reflectionService;
75 
81  {
82  $this->objectContainer = ‪$objectContainer;
83  }
84 
89  {
90  $this->reflectionService = ‪$reflectionService;
91  }
92 
93  public function ‪injectContainer(ContainerInterface ‪$container)
94  {
95  $this->container = ‪$container;
96  }
97 
106  public function ‪canConvertFrom($source, string ‪$targetType): bool
107  {
108  return !is_subclass_of(‪$targetType, AbstractDomainObject::class);
109  }
110 
118  public function ‪getSourceChildPropertiesToBeConverted($source): array
119  {
120  if (isset($source['__type'])) {
121  unset($source['__type']);
122  }
123  return $source;
124  }
125 
136  public function ‪getTypeOfChildProperty(string ‪$targetType, string $propertyName, PropertyMappingConfigurationInterface $configuration): string
137  {
138  $configuredTargetType = $configuration->getConfigurationFor($propertyName)->getConfigurationValue(\‪TYPO3\CMS\‪Extbase\Property\TypeConverter\ObjectConverter::class, self::CONFIGURATION_TARGET_TYPE);
139  if ($configuredTargetType !== null) {
140  return $configuredTargetType;
141  }
142 
143  // @deprecated since v11, will be removed in v12: ContainerInterface resolves class names. v12: drop next line.
144  $specificTargetType = $this->objectContainer->getImplementationClassName(‪$targetType);
145  $classSchema = $this->reflectionService->getClassSchema($specificTargetType);
146 
147  $methodName = 'set' . ucfirst($propertyName);
148  if ($classSchema->hasMethod($methodName)) {
149  $methodParameters = $classSchema->getMethod($methodName)->getParameters();
150  $methodParameter = current($methodParameters);
151  if ($methodParameter->getType() === null) {
152  throw new InvalidTargetException('Setter for property "' . $propertyName . '" had no type hint or documentation in target object of type "' . $specificTargetType . '".', 1303379158);
153  }
154  $property = $classSchema->getProperty($propertyName);
155  if ($property->getElementType() !== null) {
156  return $methodParameter->getType() . '<' . $property->getElementType() . '>';
157  }
158  return $methodParameter->getType();
159  }
160  try {
161  $parameterType = $classSchema->getMethod('__construct')->getParameter($propertyName)->getType();
162  } catch (NoSuchMethodException $e) {
163  $exceptionMessage = sprintf('Type of child property "%s" of class "%s" could not be '
164  . 'derived from constructor arguments as said class does not have a constructor '
165  . 'defined.', $propertyName, $specificTargetType);
166  throw new InvalidTargetException($exceptionMessage, 1582385098);
167  } catch (NoSuchMethodParameterException $e) {
168  $exceptionMessage = sprintf('Type of child property "%1$s" of class "%2$s" could not be '
169  . 'derived from constructor arguments as the constructor of said class does not '
170  . 'have a parameter with property name "%1$s".', $propertyName, $specificTargetType);
171  throw new InvalidTargetException($exceptionMessage, 1303379126);
172  }
173 
174  if ($parameterType === null) {
175  $exceptionMessage = sprintf('Type of child property "%1$s" of class "%2$s" could not be '
176  . 'derived from constructor argument "%1$s". This usually happens if the argument '
177  . 'misses a type hint.', $propertyName, $specificTargetType);
178  throw new InvalidTargetException($exceptionMessage, 1582385619);
179  }
180  return $parameterType;
181  }
182 
194  public function ‪convertFrom($source, string ‪$targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null): ?object
195  {
196  $object = $this->‪buildObject($convertedChildProperties, ‪$targetType);
197  foreach ($convertedChildProperties as $propertyName => $propertyValue) {
198  $result = ‪ObjectAccess::setProperty($object, $propertyName, $propertyValue);
199  if ($result === false) {
200  $exceptionMessage = sprintf(
201  'Property "%s" having a value of type "%s" could not be set in target object of type "%s". Make sure that the property is accessible properly, for example via an appropriate setter method.',
202  $propertyName,
203  (is_object($propertyValue) ? get_class($propertyValue) : gettype($propertyValue)),
205  );
206  throw new InvalidTargetException($exceptionMessage, 1304538165);
207  }
208  }
209 
210  return $object;
211  }
212 
225  public function ‪getTargetTypeForSource($source, string $originalTargetType, PropertyMappingConfigurationInterface $configuration = null): string
226  {
227  ‪$targetType = $originalTargetType;
228 
229  if (is_array($source) && array_key_exists('__type', $source)) {
230  ‪$targetType = $source['__type'];
231 
232  if ($configuration === null) {
233  // todo: this is impossible to achieve since this methods is always called via (convert -> doMapping -> getTargetTypeForSource) and convert and doMapping create configuration objects if missing.
234  throw new \InvalidArgumentException('A property mapping configuration must be given, not NULL.', 1326277369);
235  }
236  if ($configuration->getConfigurationValue(\‪TYPO3\CMS\‪Extbase\Property\TypeConverter\ObjectConverter::class, self::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED) !== true) {
237  throw new InvalidPropertyMappingConfigurationException('Override of target type not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED" to TRUE.', 1317050430);
238  }
239 
240  if (‪$targetType !== $originalTargetType && is_a(‪$targetType, $originalTargetType, true) === false) {
241  throw new InvalidDataTypeException('The given type "' . ‪$targetType . '" is not a subtype of "' . $originalTargetType . '".', 1317048056);
242  }
243  }
244 
245  return ‪$targetType;
246  }
247 
260  protected function ‪buildObject(array &$possibleConstructorArgumentValues, string $objectType): object
261  {
262  // The ObjectConverter typically kicks in, if request arguments are to be mapped to
263  // a domain model. An example is ext:belog:Domain/Model/Demand.
264  // Domain models are data objects and should thus be fetched via makeInstance(), should
265  // not be registered as service, and should thus not be DI aware.
266  // However, historically, ObjectManager->get() made *all* classes DI aware.
267  // Additionally, all to-be-mapped arguments are hand over as "possible constructor arguments" here,
268  // and extbase is able to use single arguments as constructor arguments to domain models,
269  // if a __construct() with an argument having the same name as a to-be-mapped argument exists.
270  // This is the reason that &$possibleConstructorArgumentValues is hand over as reference here:
271  // If an argument can be hand over as constructor argument, it is considered "already mapped" and
272  // is not manually mapped calling setters later.
273  // To be as backwards compatible as possible, without using ObjectManager, the following logic
274  // is applied for now:
275  // * If the class is registered as service (container->has()=true), and if there are no
276  // $possibleConstructorArgumentValues, instantiate the class via container->get(). Easy
277  // scenario - the target class is DI aware and will get dependencies injected. A different target
278  // class can be specified using service configuration if needed.
279  // * If the class is registered as service, and if there are $possibleConstructorArgumentValues,
280  // the class is instantiated via container->get(). $possibleConstructorArgumentValues are *not* hand
281  // over to the constructor. The target class can then use constructor injection and inject* methods
282  // for DI. A different target class can be specified using service configuration if needed. Mapping
283  // of arguments is done using setters by follow-up code.
284  // * If the class is *not* registered as service, makeInstance() is used for object retrieval.
285  // * @todo delete in v12: As compat layer, if a different implementation has been registered
286  // for the ObjectManager (extbase Container->registerImplementation()), the ObjectManager target class is
287  // still used in v11, but this is marked deprecated, with makeInstance(), a different implementation should
288  // be registered as XCLASS if really needed.
289  // * If there are no $possibleConstructorArgumentValues, makeInstance() is used right away.
290  // * If there are $possibleConstructorArgumentValues and __construct() does not exist, makeInstance()
291  // is used without constructor arguments. Mapping of argument values via setters is done by follow-up code.
292  // * If there are $possibleConstructorArgumentValues and if __construct() exists, extbase reflection
293  // is used to map single arguments to constructor arguments with the same name and
294  // makeInstance() is used to instantiate the class. Mapping remaining arguments is done by follow-up code.
295  if ($this->container->has($objectType)) {
296  // @todo: consider dropping container->get() to prevent domain models being treated as services in >=v12.
297  return $this->container->get($objectType);
298  }
299 
300  $specificObjectType = $this->objectContainer->getImplementationClassName($objectType);
301  if ($specificObjectType !== $objectType) {
302  // @deprecated since v11, will be removed in v12: makeInstance() overrides should be done as XCLASS
303  trigger_error(
304  'Container->registerImplemenation() for class ' . $objectType . ' is deprecated. Use XCLASS instead.',
305  E_USER_DEPRECATED
306  );
307  }
308 
309  if (empty($possibleConstructorArgumentValues) || !method_exists($specificObjectType, '__construct')) {
310  return GeneralUtility::makeInstance($specificObjectType);
311  }
312 
313  $classSchema = $this->reflectionService->getClassSchema($specificObjectType);
314  $constructor = $classSchema->getMethod('__construct');
315  $constructorArguments = [];
316  foreach ($constructor->getParameters() as $parameterName => $parameter) {
317  if (array_key_exists($parameterName, $possibleConstructorArgumentValues)) {
318  $constructorArguments[] = $possibleConstructorArgumentValues[$parameterName];
319  unset($possibleConstructorArgumentValues[$parameterName]);
320  } elseif ($parameter->isOptional()) {
321  $constructorArguments[] = $parameter->getDefaultValue();
322  } else {
323  throw new InvalidTargetException('Missing constructor argument "' . $parameterName . '" for object of type "' . $objectType . '".', 1268734872);
324  }
325  }
326  return GeneralUtility::makeInstance(...[$specificObjectType, ...$constructorArguments]);
327  }
328 }
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\CONFIGURATION_TARGET_TYPE
‪const CONFIGURATION_TARGET_TYPE
Definition: ObjectConverter.php:41
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface\getConfigurationValue
‪mixed getConfigurationValue($typeConverterClassName, $key)
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\buildObject
‪object buildObject(array &$possibleConstructorArgumentValues, string $objectType)
Definition: ObjectConverter.php:255
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\$sourceTypes
‪array $sourceTypes
Definition: ObjectConverter.php:50
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\injectReflectionService
‪injectReflectionService(ReflectionService $reflectionService)
Definition: ObjectConverter.php:83
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\$objectContainer
‪TYPO3 CMS Extbase Object Container Container $objectContainer
Definition: ObjectConverter.php:63
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\$reflectionService
‪TYPO3 CMS Extbase Reflection ReflectionService $reflectionService
Definition: ObjectConverter.php:69
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface\getConfigurationFor
‪TYPO3 CMS Extbase Property PropertyMappingConfigurationInterface getConfigurationFor($propertyName)
‪TYPO3\CMS\Extbase\Annotation
Definition: IgnoreValidation.php:18
‪TYPO3\CMS\Extbase\Object\Container\Container
Definition: Container.php:42
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\$targetType
‪string $targetType
Definition: ObjectConverter.php:54
‪TYPO3
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchMethodParameterException
Definition: NoSuchMethodParameterException.php:24
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\injectObjectContainer
‪injectObjectContainer(Container $objectContainer)
Definition: ObjectConverter.php:75
‪TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface
Definition: PropertyMappingConfigurationInterface.php:22
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED
‪const CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED
Definition: ObjectConverter.php:46
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess
Definition: ObjectAccess.php:39
‪TYPO3\CMS\Extbase\Reflection\ReflectionService
Definition: ReflectionService.php:28
‪TYPO3\CMS\Extbase\Reflection\ClassSchema\Exception\NoSuchMethodException
Definition: NoSuchMethodException.php:24
‪TYPO3\CMS\Extbase\Property\TypeConverter
Definition: AbstractFileFolderConverter.php:18
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\getTargetTypeForSource
‪string getTargetTypeForSource($source, string $originalTargetType, PropertyMappingConfigurationInterface $configuration=null)
Definition: ObjectConverter.php:220
‪TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject
Definition: AbstractDomainObject.php:31
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\getTypeOfChildProperty
‪string getTypeOfChildProperty(string $targetType, string $propertyName, PropertyMappingConfigurationInterface $configuration)
Definition: ObjectConverter.php:131
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\canConvertFrom
‪bool canConvertFrom($source, string $targetType)
Definition: ObjectConverter.php:101
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\getSourceChildPropertiesToBeConverted
‪array getSourceChildPropertiesToBeConverted($source)
Definition: ObjectConverter.php:113
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter
Definition: ObjectConverter.php:37
‪TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter
Definition: AbstractTypeConverter.php:34
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\injectContainer
‪injectContainer(ContainerInterface $container)
Definition: ObjectConverter.php:88
‪TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
Definition: InvalidPropertyMappingConfigurationException.php:25
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\$container
‪ContainerInterface $container
Definition: ObjectConverter.php:65
‪TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
Definition: InvalidTargetException.php:25
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\convertFrom
‪object null convertFrom($source, string $targetType, array $convertedChildProperties=[], PropertyMappingConfigurationInterface $configuration=null)
Definition: ObjectConverter.php:189
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\setProperty
‪static bool setProperty(&$subject, string $propertyName, $propertyValue)
Definition: ObjectAccess.php:170
‪TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter\$priority
‪int $priority
Definition: ObjectConverter.php:58
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException
Definition: InvalidDataTypeException.php:25