[TASK] Backport Flow property mapper
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Property / PropertyMapperTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Property;
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 require_once (__DIR__ . '/../../Fixture/ClassWithSetters.php');
25
26 /**
27 * Testcase for the Property Mapper
28 *
29 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
30 * @covers \TYPO3\CMS\Extbase\Property\PropertyMapper
31 */
32 class PropertyMapperTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
33
34 protected $mockConfigurationBuilder;
35
36 protected $mockConfiguration;
37
38 /**
39 * Sets up this test case
40 *
41 * @return void
42 * @author Sebastian Kurfürst <sebastian@typo3.org>
43 */
44 public function setUp() {
45 $this->mockConfigurationBuilder = $this->getMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMappingConfigurationBuilder');
46 $this->mockConfiguration = $this->getMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMappingConfigurationInterface');
47 }
48
49 /**
50 * @return array
51 */
52 public function validSourceTypes() {
53 return array(
54 array('someString', 'string'),
55 array(42, 'integer'),
56 array(3.5, 'float'),
57 array(TRUE, 'boolean'),
58 array(array(), 'array')
59 );
60 }
61
62 /**
63 * @test
64 * @dataProvider validSourceTypes
65 * @author Sebastian Kurfürst <sebastian@typo3.org>
66 * @param mixed $source
67 * @param mixed $sourceType
68 */
69 public function sourceTypeCanBeCorrectlyDetermined($source, $sourceType) {
70 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
71 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
72 $this->assertEquals($sourceType, $propertyMapper->_call('determineSourceType', $source));
73 }
74
75 /**
76 * @return array
77 */
78 public function invalidSourceTypes() {
79 return array(
80 array(NULL),
81 array(new \stdClass()),
82 array(new \ArrayObject())
83 );
84 }
85
86 /**
87 * @test
88 * @dataProvider invalidSourceTypes
89 * @expectedException \TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException
90 * @author Sebastian Kurfürst <sebastian@typo3.org>
91 * @param mixed $source
92 */
93 public function sourceWhichIsNoSimpleTypeThrowsException($source) {
94 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
95 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
96 $propertyMapper->_call('determineSourceType', $source);
97 }
98
99 /**
100 * @param string $name
101 * @param boolean $canConvertFrom
102 * @param array $properties
103 * @param string $typeOfSubObject
104 * @return \PHPUnit_Framework_MockObject_MockObject
105 */
106 protected function getMockTypeConverter($name = '', $canConvertFrom = TRUE, $properties = array(), $typeOfSubObject = '') {
107 $mockTypeConverter = $this->getMock('TYPO3\\CMS\\Extbase\\Property\\TypeConverterInterface');
108 $mockTypeConverter->_name = $name;
109 $mockTypeConverter->expects($this->any())->method('canConvertFrom')->will($this->returnValue($canConvertFrom));
110 $mockTypeConverter->expects($this->any())->method('convertFrom')->will($this->returnValue($name));
111 $mockTypeConverter->expects($this->any())->method('getSourceChildPropertiesToBeConverted')->will($this->returnValue($properties));
112 $mockTypeConverter->expects($this->any())->method('getTypeOfChildProperty')->will($this->returnValue($typeOfSubObject));
113 return $mockTypeConverter;
114 }
115
116 /**
117 * @test
118 * @author Sebastian Kurfürst <sebastian@typo3.org>
119 */
120 public function findTypeConverterShouldReturnTypeConverterFromConfigurationIfItIsSet() {
121 $mockTypeConverter = $this->getMockTypeConverter();
122 $this->mockConfiguration->expects($this->any())->method('getTypeConverter')->will($this->returnValue($mockTypeConverter));
123 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
124 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
125 $this->assertSame($mockTypeConverter, $propertyMapper->_call('findTypeConverter', 'someSource', 'someTargetType', $this->mockConfiguration));
126 }
127
128 /**
129 * Simple type conversion
130 * @return array
131 */
132 public function dataProviderForFindTypeConverter() {
133 return array(
134 array(
135 'someStringSource',
136 'string',
137 array(
138 'string' => array(
139 'string' => array(
140 10 => $this->getMockTypeConverter('string2string,prio10'),
141 1 => $this->getMockTypeConverter('string2string,prio1')
142 )
143 )
144 ),
145 'string2string,prio10'
146 ),
147 array(array('some' => 'array'), 'string', array(
148 'array' => array(
149 'string' => array(
150 10 => $this->getMockTypeConverter('array2string,prio10'),
151 1 => $this->getMockTypeConverter('array2string,prio1')
152 )
153 )
154 ), 'array2string,prio10')
155 );
156 }
157
158 /**
159 * @test
160 * @dataProvider dataProviderForFindTypeConverter
161 * @author Sebastian Kurfürst <sebastian@typo3.org>
162 * @param mixed $source
163 * @param mixed $targetType
164 * @param mixed $typeConverters
165 * @param mixed $expectedTypeConverter
166 */
167 public function findTypeConverterShouldReturnHighestPriorityTypeConverterForSimpleType($source, $targetType, $typeConverters, $expectedTypeConverter) {
168 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
169 $propertyMapper->_set('typeConverters', $typeConverters);
170 $actualTypeConverter = $propertyMapper->_call('findTypeConverter', $source, $targetType, $this->mockConfiguration);
171 $this->assertSame($expectedTypeConverter, $actualTypeConverter->_name);
172 }
173
174 /**
175 * @return array
176 */
177 public function dataProviderForObjectTypeConverters() {
178 $data = array();
179
180 $className1 = uniqid('TYPO3_Flow_Testclass1_', FALSE);
181 $className2 = uniqid('TYPO3_Flow_Testclass2_', FALSE);
182 $className3 = uniqid('TYPO3_Flow_Testclass3_', FALSE);
183
184 $interfaceName1 = uniqid('TYPO3_Flow_TestInterface1_', FALSE);
185 $interfaceName2 = uniqid('TYPO3_Flow_TestInterface2_', FALSE);
186 $interfaceName3 = uniqid('TYPO3_Flow_TestInterface3_', FALSE);
187
188 eval("
189 interface $interfaceName2 {}
190 interface $interfaceName1 {}
191
192 interface $interfaceName3 extends $interfaceName2 {}
193
194 class $className1 implements $interfaceName1 {}
195 class $className2 extends $className1 {}
196 class $className3 extends $className2 implements $interfaceName3 {}
197 ");
198
199 // The most specific converter should win
200 $data[] = array(
201 'target' => $className3,
202 'expectedConverter' => 'Class3Converter',
203 'typeConverters' => array(
204 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter')),
205 $className3 => array(0 => $this->getMockTypeConverter('Class3Converter')),
206
207 $interfaceName1 => array(0 => $this->getMockTypeConverter('Interface1Converter')),
208 $interfaceName2 => array(0 => $this->getMockTypeConverter('Interface2Converter')),
209 $interfaceName3 => array(0 => $this->getMockTypeConverter('Interface3Converter')),
210 )
211 );
212
213 // In case the most specific converter does not want to handle this conversion, the second one is taken.
214 $data[] = array(
215 'target' => $className3,
216 'expectedConverter' => 'Class2Converter',
217 'typeConverters' => array(
218 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter')),
219 $className3 => array(0 => $this->getMockTypeConverter('Class3Converter', FALSE)),
220
221 $interfaceName1 => array(0 => $this->getMockTypeConverter('Interface1Converter')),
222 $interfaceName2 => array(0 => $this->getMockTypeConverter('Interface2Converter')),
223 $interfaceName3 => array(0 => $this->getMockTypeConverter('Interface3Converter')),
224 )
225 );
226
227 // In case there is no most-specific-converter, we climb ub the type hierarchy
228 $data[] = array(
229 'target' => $className3,
230 'expectedConverter' => 'Class2Converter-HighPriority',
231 'typeConverters' => array(
232 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter'), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority'))
233 )
234 );
235
236 // If no parent class converter wants to handle it, we ask for all interface converters.
237 $data[] = array(
238 'target' => $className3,
239 'expectedConverter' => 'Interface1Converter',
240 'typeConverters' => array(
241 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
242
243 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter')),
244 $interfaceName2 => array(1 => $this->getMockTypeConverter('Interface2Converter')),
245 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter')),
246 )
247 );
248
249 // If two interface converters have the same priority, an exception is thrown.
250 $data[] = array(
251 'target' => $className3,
252 'expectedConverter' => 'Interface1Converter',
253 'typeConverters' => array(
254 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
255
256 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter')),
257 $interfaceName2 => array(2 => $this->getMockTypeConverter('Interface2Converter')),
258 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter')),
259 ),
260 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\DuplicateTypeConverterException'
261 );
262
263 // If no interface converter wants to handle it, a converter for "object" is looked up.
264 $data[] = array(
265 'target' => $className3,
266 'expectedConverter' => 'GenericObjectConverter-HighPriority',
267 'typeConverters' => array(
268 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
269
270 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter', FALSE)),
271 $interfaceName2 => array(3 => $this->getMockTypeConverter('Interface2Converter', FALSE)),
272 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter', FALSE)),
273 'object' => array(1 => $this->getMockTypeConverter('GenericObjectConverter'), 10 => $this->getMockTypeConverter('GenericObjectConverter-HighPriority'))
274 ),
275 );
276
277 // If the target is no valid class name and no simple type, an exception is thrown
278 $data[] = array(
279 'target' => 'SomeNotExistingClassName',
280 'expectedConverter' => 'GenericObjectConverter-HighPriority',
281 'typeConverters' => array(),
282 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\InvalidTargetException'
283 );
284
285 // if the type converter is not found, we expect an exception
286 $data[] = array(
287 'target' => $className3,
288 'expectedConverter' => 'Class3Converter',
289 'typeConverters' => array(),
290 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\TypeConverterException'
291 );
292
293 // If The target type is no string, we expect an exception.
294 $data[] = array(
295 'target' => new \stdClass(),
296 'expectedConverter' => '',
297 'typeConverters' => array(),
298 'shouldFailWithException' => 'TYPO3\\CMS\\Extbase\\Property\\Exception\\InvalidTargetException'
299 );
300 return $data;
301 }
302
303 /**
304 * @test
305 * @dataProvider dataProviderForObjectTypeConverters
306 * @author Sebastian Kurfürst <sebastian@typo3.org>
307 * @param mixed $targetClass
308 * @param mixed $expectedTypeConverter
309 * @param mixed $typeConverters
310 * @param boolean $shouldFailWithException
311 * @throws \Exception
312 * @return void
313 */
314 public function findTypeConverterShouldReturnConverterForTargetObjectIfItExists($targetClass, $expectedTypeConverter, $typeConverters, $shouldFailWithException = FALSE) {
315 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
316 $propertyMapper->_set('typeConverters', array('string' => $typeConverters));
317 try {
318 $actualTypeConverter = $propertyMapper->_call('findTypeConverter', 'someSourceString', $targetClass, $this->mockConfiguration);
319 if ($shouldFailWithException) {
320 $this->fail('Expected exception ' . $shouldFailWithException . ' which was not thrown.');
321 }
322 $this->assertSame($expectedTypeConverter, $actualTypeConverter->_name);
323 } catch (\Exception $e) {
324 if ($shouldFailWithException === FALSE) {
325 throw $e;
326 }
327 $this->assertInstanceOf($shouldFailWithException, $e);
328 }
329 }
330
331 /**
332 * @test
333 */
334 public function convertShouldAskConfigurationBuilderForDefaultConfiguration() {
335 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
336 $this->inject($propertyMapper, 'configurationBuilder', $this->mockConfigurationBuilder);
337
338 $this->mockConfigurationBuilder->expects($this->once())->method('build')->will($this->returnValue($this->mockConfiguration));
339
340 $converter = $this->getMockTypeConverter('string2string');
341 $typeConverters = array(
342 'string' => array(
343 'string' => array(10 => $converter)
344 )
345 );
346
347 $propertyMapper->_set('typeConverters', $typeConverters);
348 $this->assertEquals('string2string', $propertyMapper->convert('source', 'string'));
349 }
350
351 /**
352 * @test
353 */
354 public function findFirstEligibleTypeConverterInObjectHierarchyShouldReturnNullIfSourceTypeIsUnknown() {
355 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
356 $propertyMapper = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Property\\PropertyMapper', array('dummy'));
357 $this->assertNull($propertyMapper->_call('findFirstEligibleTypeConverterInObjectHierarchy', 'source', 'unknownSourceType', 'TYPO3\\CMS\\Extbase\\Core\\Bootstrap'));
358 }
359
360 /**
361 * @test
362 */
363 public function doMappingReturnsSourceUnchangedIfAlreadyConverted() {
364 $source = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
365 $targetType = 'TYPO3\CMS\Extbase\Persistence\ObjectStorage';
366 $propertyPath = '';
367 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
368 $this->assertSame($source, $propertyMapper->_callRef('doMapping', $source, $targetType, $this->mockConfiguration, $propertyPath));
369 }
370
371 /**
372 * @test
373 */
374 public function doMappingReturnsSourceUnchangedIfAlreadyConvertedToCompositeType() {
375 $source = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
376 $targetType = 'TYPO3\CMS\Extbase\Persistence\ObjectStorage<SomeEntity>';
377 $propertyPath = '';
378 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
379 $this->assertSame($source, $propertyMapper->_callRef('doMapping', $source, $targetType, $this->mockConfiguration, $propertyPath));
380 }
381
382 /**
383 * @test
384 */
385 public function convertSkipsPropertiesIfConfiguredTo() {
386 $source = array('firstProperty' => 1, 'secondProperty' => 2);
387 $typeConverters = array(
388 'array' => array(
389 'stdClass' => array(10 => $this->getMockTypeConverter('array2object', TRUE, $source, 'integer'))
390 ),
391 'integer' => array(
392 'integer' => array(10 => $this->getMockTypeConverter('integer2integer'))
393 )
394 );
395 $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
396
397 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
398 $propertyMapper->_set('typeConverters', $typeConverters);
399
400 $propertyMapper->convert($source, 'stdClass', $configuration->allowProperties('firstProperty')->skipProperties('secondProperty'));
401 }
402
403 /**
404 * @test
405 */
406 public function convertSkipsUnknownPropertiesIfConfiguredTo() {
407 $source = array('firstProperty' => 1, 'secondProperty' => 2);
408 $typeConverters = array(
409 'array' => array(
410 'stdClass' => array(10 => $this->getMockTypeConverter('array2object', TRUE, $source, 'integer'))
411 ),
412 'integer' => array(
413 'integer' => array(10 => $this->getMockTypeConverter('integer2integer'))
414 )
415 );
416 $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
417
418 $propertyMapper = $this->getAccessibleMock('TYPO3\CMS\Extbase\Property\PropertyMapper', array('dummy'));
419 $propertyMapper->_set('typeConverters', $typeConverters);
420
421 $propertyMapper->convert($source, 'stdClass', $configuration->allowProperties('firstProperty')->skipUnknownProperties());
422 }
423 }
424
425 ?>