[TASK] Streamline phpdoc annotations in EXT:extbase
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Property / TypeConverter / ObjectConverter.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Property\TypeConverter;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * This converter transforms arrays to simple objects (POPO) by setting properties.
19 */
20 class ObjectConverter extends AbstractTypeConverter
21 {
22 /**
23 * @var int
24 */
25 const CONFIGURATION_TARGET_TYPE = 3;
26
27 /**
28 * @var int
29 */
30 const CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED = 4;
31
32 /**
33 * @var array
34 */
35 protected $sourceTypes = ['array'];
36
37 /**
38 * @var string
39 */
40 protected $targetType = 'object';
41
42 /**
43 * @var int
44 */
45 protected $priority = 10;
46
47 /**
48 * @var \TYPO3\CMS\Extbase\Object\Container\Container
49 */
50 protected $objectContainer;
51
52 /**
53 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
54 */
55 protected $reflectionService;
56
57 /**
58 * @param \TYPO3\CMS\Extbase\Object\Container\Container $objectContainer
59 */
60 public function injectObjectContainer(\TYPO3\CMS\Extbase\Object\Container\Container $objectContainer)
61 {
62 $this->objectContainer = $objectContainer;
63 }
64
65 /**
66 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
67 */
68 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
69 {
70 $this->reflectionService = $reflectionService;
71 }
72
73 /**
74 * Only convert non-persistent types
75 *
76 * @param mixed $source
77 * @param string $targetType
78 * @return bool
79 * @internal only to be used within Extbase, not part of TYPO3 Core API.
80 */
81 public function canConvertFrom($source, $targetType)
82 {
83 return !is_subclass_of($targetType, \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject::class);
84 }
85
86 /**
87 * Convert all properties in the source array
88 *
89 * @param mixed $source
90 * @return array
91 * @internal only to be used within Extbase, not part of TYPO3 Core API.
92 */
93 public function getSourceChildPropertiesToBeConverted($source)
94 {
95 if (isset($source['__type'])) {
96 unset($source['__type']);
97 }
98 return $source;
99 }
100
101 /**
102 * The type of a property is determined by the reflection service.
103 *
104 * @param string $targetType
105 * @param string $propertyName
106 * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
107 * @return string
108 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
109 * @internal only to be used within Extbase, not part of TYPO3 Core API.
110 */
111 public function getTypeOfChildProperty($targetType, $propertyName, \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration)
112 {
113 $configuredTargetType = $configuration->getConfigurationFor($propertyName)->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter::class, self::CONFIGURATION_TARGET_TYPE);
114 if ($configuredTargetType !== null) {
115 return $configuredTargetType;
116 }
117
118 $specificTargetType = $this->objectContainer->getImplementationClassName($targetType);
119 $classSchema = $this->reflectionService->getClassSchema($specificTargetType);
120
121 if ($classSchema->hasMethod(\TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName($propertyName))) {
122 $methodParameters = $classSchema->getMethod(\TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName($propertyName))['params'] ?? [];
123 $methodParameter = current($methodParameters);
124 if (!isset($methodParameter['type'])) {
125 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException('Setter for property "' . $propertyName . '" had no type hint or documentation in target object of type "' . $specificTargetType . '".', 1303379158);
126 }
127 return $methodParameter['type'];
128 }
129 $methodParameters = $classSchema->getMethod('__construct')['params'] ?? [];
130 if (isset($methodParameters[$propertyName]) && isset($methodParameters[$propertyName]['type'])) {
131 return $methodParameters[$propertyName]['type'];
132 }
133 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException('Property "' . $propertyName . '" had no setter or constructor argument in target object of type "' . $specificTargetType . '".', 1303379126);
134 }
135
136 /**
137 * Convert an object from $source to an object.
138 *
139 * @param mixed $source
140 * @param string $targetType
141 * @param array $convertedChildProperties
142 * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
143 * @return object the target type
144 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
145 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException
146 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
147 * @internal only to be used within Extbase, not part of TYPO3 Core API.
148 */
149 public function convertFrom($source, $targetType, array $convertedChildProperties = [], \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration = null)
150 {
151 $object = $this->buildObject($convertedChildProperties, $targetType);
152 foreach ($convertedChildProperties as $propertyName => $propertyValue) {
153 $result = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::setProperty($object, $propertyName, $propertyValue);
154 if ($result === false) {
155 $exceptionMessage = sprintf(
156 '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.',
157 $propertyName,
158 (is_object($propertyValue) ? get_class($propertyValue) : gettype($propertyValue)),
159 $targetType
160 );
161 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException($exceptionMessage, 1304538165);
162 }
163 }
164
165 return $object;
166 }
167
168 /**
169 * Determines the target type based on the source's (optional) __type key.
170 *
171 * @param mixed $source
172 * @param string $originalTargetType
173 * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
174 * @return string
175 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException
176 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
177 * @throws \InvalidArgumentException
178 * @internal only to be used within Extbase, not part of TYPO3 Core API.
179 */
180 public function getTargetTypeForSource($source, $originalTargetType, \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration = null)
181 {
182 $targetType = $originalTargetType;
183
184 if (is_array($source) && array_key_exists('__type', $source)) {
185 $targetType = $source['__type'];
186
187 if ($configuration === null) {
188 throw new \InvalidArgumentException('A property mapping configuration must be given, not NULL.', 1326277369);
189 }
190 if ($configuration->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter::class, self::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED) !== true) {
191 throw new \TYPO3\CMS\Extbase\Property\Exception\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);
192 }
193
194 if ($targetType !== $originalTargetType && is_a($targetType, $originalTargetType, true) === false) {
195 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException('The given type "' . $targetType . '" is not a subtype of "' . $originalTargetType . '".', 1317048056);
196 }
197 }
198
199 return $targetType;
200 }
201
202 /**
203 * Builds a new instance of $objectType with the given $possibleConstructorArgumentValues. If
204 * constructor argument values are missing from the given array the method
205 * looks for a default value in the constructor signature. Furthermore, the constructor arguments are removed from $possibleConstructorArgumentValues
206 *
207 * @param array &$possibleConstructorArgumentValues
208 * @param string $objectType
209 * @return object The created instance
210 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException if a required constructor argument is missing
211 */
212 protected function buildObject(array &$possibleConstructorArgumentValues, $objectType)
213 {
214 $specificObjectType = $this->objectContainer->getImplementationClassName($objectType);
215 $classSchema = $this->reflectionService->getClassSchema($specificObjectType);
216
217 if ($classSchema->hasConstructor()) {
218 $constructorSignature = $classSchema->getMethod('__construct')['params'] ?? [];
219 $constructorArguments = [];
220 foreach ($constructorSignature as $constructorArgumentName => $constructorArgumentInformation) {
221 if (array_key_exists($constructorArgumentName, $possibleConstructorArgumentValues)) {
222 $constructorArguments[] = $possibleConstructorArgumentValues[$constructorArgumentName];
223 unset($possibleConstructorArgumentValues[$constructorArgumentName]);
224 } elseif ($constructorArgumentInformation['optional'] === true) {
225 $constructorArguments[] = $constructorArgumentInformation['defaultValue'];
226 } else {
227 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException('Missing constructor argument "' . $constructorArgumentName . '" for object of type "' . $objectType . '".', 1268734872);
228 }
229 }
230 return call_user_func_array([$this->objectManager, 'get'], array_merge([$objectType], $constructorArguments));
231 }
232 return $this->objectManager->get($objectType);
233 }
234 }