[BUGFIX] Property of alternative model implementation not found
[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\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 boolean
79 */
80 public function canConvertFrom($source, $targetType) {
81 return !is_subclass_of($targetType, 'TYPO3\\CMS\\Extbase\\DomainObject\\AbstractDomainObject');
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', 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', 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 // FIXME: The following check and the checkInheritanceChainWithoutIsA() method should be removed if we raise the PHP requirement to 5.3.9 or higher
186 if (version_compare(phpversion(), '5.3.8', '>')) {
187 if ($targetType !== $originalTargetType && is_a($targetType, $originalTargetType, TRUE) === FALSE) {
188 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException('The given type "' . $targetType . '" is not a subtype of "' . $originalTargetType . '".', 1317048056);
189 }
190 } else {
191 $targetType = $this->checkInheritanceChainWithoutIsA($targetType, $originalTargetType);
192 }
193 }
194
195 return $targetType;
196 }
197
198 /**
199 * Builds a new instance of $objectType with the given $possibleConstructorArgumentValues. If
200 * constructor argument values are missing from the given array the method
201 * looks for a default value in the constructor signature. Furthermore, the constructor arguments are removed from $possibleConstructorArgumentValues
202 *
203 * @param array &$possibleConstructorArgumentValues
204 * @param string $objectType
205 * @return object The created instance
206 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException if a required constructor argument is missing
207 */
208 protected function buildObject(array &$possibleConstructorArgumentValues, $objectType) {
209 $specificObjectType = $this->objectContainer->getImplementationClassName($objectType);
210 if ($this->reflectionService->hasMethod($specificObjectType, '__construct')) {
211 $constructorSignature = $this->reflectionService->getMethodParameters($specificObjectType, '__construct');
212 $constructorArguments = array();
213 foreach ($constructorSignature as $constructorArgumentName => $constructorArgumentInformation) {
214 if (array_key_exists($constructorArgumentName, $possibleConstructorArgumentValues)) {
215 $constructorArguments[] = $possibleConstructorArgumentValues[$constructorArgumentName];
216 unset($possibleConstructorArgumentValues[$constructorArgumentName]);
217 } elseif ($constructorArgumentInformation['optional'] === TRUE) {
218 $constructorArguments[] = $constructorArgumentInformation['defaultValue'];
219 } else {
220 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException('Missing constructor argument "' . $constructorArgumentName . '" for object of type "' . $objectType . '".', 1268734872);
221 }
222 }
223 return call_user_func_array(array($this->objectManager, 'get'), array_merge(array($objectType), $constructorArguments));
224 } else {
225 return $this->objectManager->get($objectType);
226 }
227 }
228
229 /**
230 * This is a replacement for the functionality provided by is_a() with 3 parameters which is only available from
231 * PHP 5.3.9. It can be removed if the TYPO3 CMS PHP version requirement is raised to 5.3.9 or above.
232 *
233 * @param string $targetType
234 * @param string $originalTargetType
235 * @return string
236 * @throws \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException
237 */
238 protected function checkInheritanceChainWithoutIsA($targetType, $originalTargetType) {
239 $targetTypeToCompare = $targetType;
240 do {
241 if ($targetTypeToCompare === $originalTargetType) {
242 return $targetType;
243 }
244 } while ($targetTypeToCompare = get_parent_class($targetTypeToCompare));
245
246 throw new \TYPO3\CMS\Extbase\Property\Exception\InvalidDataTypeException('The given type "' . $targetType . '" is not a subtype of "' . $originalTargetType . '".', 1360928582);
247 }
248
249 }