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