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