[TASK] Use name-resolution instead of strings where possible: 3
[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('array');
44
45 /**
46 * @var string
47 */
48 protected $targetType = 'object';
49
50 /**
51 * @var int
52 */
53 protected $priority = 0;
54
55 /**
56 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
57 * @inject
58 */
59 protected $objectManager;
60
61 /**
62 * @var \TYPO3\CMS\Extbase\Object\Container\Container
63 * @inject
64 */
65 protected $objectContainer;
66
67 /**
68 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
69 * @inject
70 */
71 protected $reflectionService;
72
73 /**
74 * Only convert non-persistent types
75 *
76 * @param mixed $source
77 * @param string $targetType
78 * @return bool
79 */
80 public function canConvertFrom($source, $targetType) {
81 return !is_subclass_of($targetType, \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject::class);
82 }
83
84 /**
85 * Convert all properties in the source array
86 *
87 * @param mixed $source
88 * @return array
89 */
90 public function getSourceChildPropertiesToBeConverted($source) {
91 if (isset($source['__type'])) {
92 unset($source['__type']);
93 }
94 return $source;
95 }
96
97 /**
98 * The type of a property is determined by the reflection service.
99 *
100 * @param string $targetType
101 * @param string $propertyName
102 * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
103 * @return string
104 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
105 */
106 public function getTypeOfChildProperty($targetType, $propertyName, \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration) {
107 $configuredTargetType = $configuration->getConfigurationFor($propertyName)->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter::class, self::CONFIGURATION_TARGET_TYPE);
108 if ($configuredTargetType !== NULL) {
109 return $configuredTargetType;
110 }
111
112 $specificTargetType = $this->objectContainer->getImplementationClassName($targetType);
113 if ($this->reflectionService->hasMethod($specificTargetType, \TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName($propertyName))) {
114 $methodParameters = $this->reflectionService->getMethodParameters($specificTargetType, \TYPO3\CMS\Extbase\Reflection\ObjectAccess::buildSetterMethodName($propertyName));
115 $methodParameter = current($methodParameters);
116 if (!isset($methodParameter['type'])) {
117 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);
118 } else {
119 return $methodParameter['type'];
120 }
121 } else {
122 $methodParameters = $this->reflectionService->getMethodParameters($specificTargetType, '__construct');
123 if (isset($methodParameters[$propertyName]) && isset($methodParameters[$propertyName]['type'])) {
124 return $methodParameters[$propertyName]['type'];
125 } else {
126 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException('Property "' . $propertyName . '" had no setter or constructor argument in target object of type "' . $specificTargetType . '".', 1303379126);
127 }
128 }
129 }
130
131 /**
132 * Convert an object from $source to an object.
133 *
134 * @param mixed $source
135 * @param string $targetType
136 * @param array $convertedChildProperties
137 * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
138 * @return object the target type
139 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException
140 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException
141 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
142 */
143 public function convertFrom($source, $targetType, array $convertedChildProperties = array(), \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration = NULL) {
144 $object = $this->buildObject($convertedChildProperties, $targetType);
145 foreach ($convertedChildProperties as $propertyName => $propertyValue) {
146 $result = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::setProperty($object, $propertyName, $propertyValue);
147 if ($result === FALSE) {
148 $exceptionMessage = sprintf(
149 '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.',
150 $propertyName,
151 (is_object($propertyValue) ? get_class($propertyValue) : gettype($propertyValue)),
152 $targetType
153 );
154 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException($exceptionMessage, 1304538165);
155 }
156 }
157
158 return $object;
159 }
160
161 /**
162 * Determines the target type based on the source's (optional) __type key.
163 *
164 * @param mixed $source
165 * @param string $originalTargetType
166 * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
167 * @return string
168 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException
169 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException
170 * @throws \InvalidArgumentException
171 */
172 public function getTargetTypeForSource($source, $originalTargetType, \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration = NULL) {
173 $targetType = $originalTargetType;
174
175 if (is_array($source) && array_key_exists('__type', $source)) {
176 $targetType = $source['__type'];
177
178 if ($configuration === NULL) {
179 throw new \InvalidArgumentException('A property mapping configuration must be given, not NULL.', 1326277369);
180 }
181 if ($configuration->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter::class, self::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED) !== TRUE) {
182 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);
183 }
184
185 if ($targetType !== $originalTargetType && is_a($targetType, $originalTargetType, TRUE) === FALSE) {
186 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException('The given type "' . $targetType . '" is not a subtype of "' . $originalTargetType . '".', 1317048056);
187 }
188 }
189
190 return $targetType;
191 }
192
193 /**
194 * Builds a new instance of $objectType with the given $possibleConstructorArgumentValues. If
195 * constructor argument values are missing from the given array the method
196 * looks for a default value in the constructor signature. Furthermore, the constructor arguments are removed from $possibleConstructorArgumentValues
197 *
198 * @param array &$possibleConstructorArgumentValues
199 * @param string $objectType
200 * @return object The created instance
201 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException if a required constructor argument is missing
202 */
203 protected function buildObject(array &$possibleConstructorArgumentValues, $objectType) {
204 $specificObjectType = $this->objectContainer->getImplementationClassName($objectType);
205 if ($this->reflectionService->hasMethod($specificObjectType, '__construct')) {
206 $constructorSignature = $this->reflectionService->getMethodParameters($specificObjectType, '__construct');
207 $constructorArguments = array();
208 foreach ($constructorSignature as $constructorArgumentName => $constructorArgumentInformation) {
209 if (array_key_exists($constructorArgumentName, $possibleConstructorArgumentValues)) {
210 $constructorArguments[] = $possibleConstructorArgumentValues[$constructorArgumentName];
211 unset($possibleConstructorArgumentValues[$constructorArgumentName]);
212 } elseif ($constructorArgumentInformation['optional'] === TRUE) {
213 $constructorArguments[] = $constructorArgumentInformation['defaultValue'];
214 } else {
215 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException('Missing constructor argument "' . $constructorArgumentName . '" for object of type "' . $objectType . '".', 1268734872);
216 }
217 }
218 return call_user_func_array(array($this->objectManager, 'get'), array_merge(array($objectType), $constructorArguments));
219 } else {
220 return $this->objectManager->get($objectType);
221 }
222 }
223
224 /**
225 * This is a replacement for the functionality provided by is_a() with 3 parameters which is only available from
226 * PHP 5.3.9. It can be removed if the TYPO3 CMS PHP version requirement is raised to 5.3.9 or above.
227 *
228 * @param string $targetType
229 * @param string $originalTargetType
230 * @return string
231 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException
232 */
233 protected function checkInheritanceChainWithoutIsA($targetType, $originalTargetType) {
234 $targetTypeToCompare = $targetType;
235 do {
236 if ($targetTypeToCompare === $originalTargetType) {
237 return $targetType;
238 }
239 } while ($targetTypeToCompare = get_parent_class($targetTypeToCompare));
240
241 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException('The given type "' . $targetType . '" is not a subtype of "' . $originalTargetType . '".', 1360928582);
242 }
243
244 }