9ca62f94d2e12cfd3a0b0a8785a8ef1a85b7cf7f
[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 /**
25 * Test case
26 */
27 class PropertyMapperTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
28
29 protected $mockConfigurationBuilder;
30
31 protected $mockConfiguration;
32
33 /**
34 * Sets up this test case
35 *
36 * @return void
37 */
38 protected function setUp() {
39 $this->mockConfigurationBuilder = $this->getMock(\TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationBuilder::class);
40 $this->mockConfiguration = $this->getMock(\TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface::class);
41 }
42
43 /**
44 * @return array
45 */
46 public function validSourceTypes() {
47 return array(
48 array('someString', 'string'),
49 array(42, 'integer'),
50 array(3.5, 'float'),
51 array(TRUE, 'boolean'),
52 array(array(), 'array')
53 );
54 }
55
56 /**
57 * @test
58 * @dataProvider validSourceTypes
59 * @param mixed $source
60 * @param mixed $sourceType
61 */
62 public function sourceTypeCanBeCorrectlyDetermined($source, $sourceType) {
63 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
64 $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, array('dummy'));
65 $this->assertEquals($sourceType, $propertyMapper->_call('determineSourceType', $source));
66 }
67
68 /**
69 * @return array
70 */
71 public function invalidSourceTypes() {
72 return array(
73 array(NULL),
74 array(new \stdClass()),
75 array(new \ArrayObject())
76 );
77 }
78
79 /**
80 * @test
81 * @dataProvider invalidSourceTypes
82 * @expectedException \TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException
83 * @param mixed $source
84 */
85 public function sourceWhichIsNoSimpleTypeThrowsException($source) {
86 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
87 $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, array('dummy'));
88 $propertyMapper->_call('determineSourceType', $source);
89 }
90
91 /**
92 * @param string $name
93 * @param bool $canConvertFrom
94 * @param array $properties
95 * @param string $typeOfSubObject
96 * @return \PHPUnit_Framework_MockObject_MockObject
97 */
98 protected function getMockTypeConverter($name = '', $canConvertFrom = TRUE, $properties = array(), $typeOfSubObject = '') {
99 $mockTypeConverter = $this->getMock(\TYPO3\CMS\Extbase\Property\TypeConverterInterface::class);
100 $mockTypeConverter->_name = $name;
101 $mockTypeConverter->expects($this->any())->method('canConvertFrom')->will($this->returnValue($canConvertFrom));
102 $mockTypeConverter->expects($this->any())->method('convertFrom')->will($this->returnValue($name));
103 $mockTypeConverter->expects($this->any())->method('getSourceChildPropertiesToBeConverted')->will($this->returnValue($properties));
104 $mockTypeConverter->expects($this->any())->method('getTypeOfChildProperty')->will($this->returnValue($typeOfSubObject));
105 return $mockTypeConverter;
106 }
107
108 /**
109 * @test
110 */
111 public function findTypeConverterShouldReturnTypeConverterFromConfigurationIfItIsSet() {
112 $mockTypeConverter = $this->getMockTypeConverter();
113 $this->mockConfiguration->expects($this->any())->method('getTypeConverter')->will($this->returnValue($mockTypeConverter));
114 /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface */
115 $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, array('dummy'));
116 $this->assertSame($mockTypeConverter, $propertyMapper->_call('findTypeConverter', 'someSource', 'someTargetType', $this->mockConfiguration));
117 }
118
119 /**
120 * Simple type conversion
121 * @return array
122 */
123 public function dataProviderForFindTypeConverter() {
124 return array(
125 array('someStringSource', 'string', array(
126 'string' => array(
127 'string' => array(
128 10 => $this->getMockTypeConverter('string2string,prio10'),
129 1 => $this->getMockTypeConverter('string2string,prio1'),
130 )
131 )), 'string2string,prio10'
132 ),
133 array(array('some' => 'array'), 'string', array(
134 'array' => array(
135 'string' => array(
136 10 => $this->getMockTypeConverter('array2string,prio10'),
137 1 => $this->getMockTypeConverter('array2string,prio1'),
138 )
139 )), 'array2string,prio10'
140 ),
141 array('someStringSource', 'bool', array(
142 'string' => array(
143 'boolean' => array(
144 10 => $this->getMockTypeConverter('string2boolean,prio10'),
145 1 => $this->getMockTypeConverter('string2boolean,prio1'),
146 )
147 )), 'string2boolean,prio10'
148 ),
149 array('someStringSource', 'int', array(
150 'string' => array(
151 'integer' => array(
152 10 => $this->getMockTypeConverter('string2integer,prio10'),
153 1 => $this->getMockTypeConverter('string2integer,prio1'),
154 ),
155 )), 'string2integer,prio10'
156 )
157 );
158 }
159
160 /**
161 * @test
162 * @dataProvider dataProviderForFindTypeConverter
163 * @param mixed $source
164 * @param mixed $targetType
165 * @param mixed $typeConverters
166 * @param mixed $expectedTypeConverter
167 */
168 public function findTypeConverterShouldReturnHighestPriorityTypeConverterForSimpleType($source, $targetType, $typeConverters, $expectedTypeConverter) {
169 $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, array('dummy'));
170 $propertyMapper->_set('typeConverters', $typeConverters);
171 $actualTypeConverter = $propertyMapper->_call('findTypeConverter', $source, $targetType, $this->mockConfiguration);
172 $this->assertSame($expectedTypeConverter, $actualTypeConverter->_name);
173 }
174
175 /**
176 * @return array
177 */
178 public function dataProviderForObjectTypeConverters() {
179 $data = array();
180
181 $className1 = $this->getUniqueId('TYPO3_Flow_Testclass1_', FALSE);
182 $className2 = $this->getUniqueId('TYPO3_Flow_Testclass2_', FALSE);
183 $className3 = $this->getUniqueId('TYPO3_Flow_Testclass3_', FALSE);
184
185 $interfaceName1 = $this->getUniqueId('TYPO3_Flow_TestInterface1_', FALSE);
186 $interfaceName2 = $this->getUniqueId('TYPO3_Flow_TestInterface2_', FALSE);
187 $interfaceName3 = $this->getUniqueId('TYPO3_Flow_TestInterface3_', FALSE);
188
189 eval("
190 interface $interfaceName2 {}
191 interface $interfaceName1 {}
192
193 interface $interfaceName3 extends $interfaceName2 {}
194
195 class $className1 implements $interfaceName1 {}
196 class $className2 extends $className1 {}
197 class $className3 extends $className2 implements $interfaceName3 {}
198 ");
199
200 // The most specific converter should win
201 $data[] = array(
202 'target' => $className3,
203 'expectedConverter' => 'Class3Converter',
204 'typeConverters' => array(
205 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter')),
206 $className3 => array(0 => $this->getMockTypeConverter('Class3Converter')),
207
208 $interfaceName1 => array(0 => $this->getMockTypeConverter('Interface1Converter')),
209 $interfaceName2 => array(0 => $this->getMockTypeConverter('Interface2Converter')),
210 $interfaceName3 => array(0 => $this->getMockTypeConverter('Interface3Converter')),
211 )
212 );
213
214 // In case the most specific converter does not want to handle this conversion, the second one is taken.
215 $data[] = array(
216 'target' => $className3,
217 'expectedConverter' => 'Class2Converter',
218 'typeConverters' => array(
219 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter')),
220 $className3 => array(0 => $this->getMockTypeConverter('Class3Converter', FALSE)),
221
222 $interfaceName1 => array(0 => $this->getMockTypeConverter('Interface1Converter')),
223 $interfaceName2 => array(0 => $this->getMockTypeConverter('Interface2Converter')),
224 $interfaceName3 => array(0 => $this->getMockTypeConverter('Interface3Converter')),
225 )
226 );
227
228 // In case there is no most-specific-converter, we climb ub the type hierarchy
229 $data[] = array(
230 'target' => $className3,
231 'expectedConverter' => 'Class2Converter-HighPriority',
232 'typeConverters' => array(
233 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter'), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority'))
234 )
235 );
236
237 // If no parent class converter wants to handle it, we ask for all interface converters.
238 $data[] = array(
239 'target' => $className3,
240 'expectedConverter' => 'Interface1Converter',
241 'typeConverters' => array(
242 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
243
244 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter')),
245 $interfaceName2 => array(1 => $this->getMockTypeConverter('Interface2Converter')),
246 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter')),
247 )
248 );
249
250 // If two interface converters have the same priority, an exception is thrown.
251 $data[] = array(
252 'target' => $className3,
253 'expectedConverter' => 'Interface1Converter',
254 'typeConverters' => array(
255 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
256
257 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter')),
258 $interfaceName2 => array(2 => $this->getMockTypeConverter('Interface2Converter')),
259 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter')),
260 ),
261 'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\DuplicateTypeConverterException::class
262 );
263
264 // If no interface converter wants to handle it, a converter for "object" is looked up.
265 $data[] = array(
266 'target' => $className3,
267 'expectedConverter' => 'GenericObjectConverter-HighPriority',
268 'typeConverters' => array(
269 $className2 => array(0 => $this->getMockTypeConverter('Class2Converter', FALSE), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', FALSE)),
270
271 $interfaceName1 => array(4 => $this->getMockTypeConverter('Interface1Converter', FALSE)),
272 $interfaceName2 => array(3 => $this->getMockTypeConverter('Interface2Converter', FALSE)),
273 $interfaceName3 => array(2 => $this->getMockTypeConverter('Interface3Converter', FALSE)),
274 'object' => array(1 => $this->getMockTypeConverter('GenericObjectConverter'), 10 => $this->getMockTypeConverter('GenericObjectConverter-HighPriority'))
275 ),
276 );
277
278 // If the target is no valid class name and no simple type, an exception is thrown
279 $data[] = array(
280 'target' => 'SomeNotExistingClassName',
281 'expectedConverter' => 'GenericObjectConverter-HighPriority',
282 'typeConverters' => array(),
283 'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException::class
284 );
285
286 // if the type converter is not found, we expect an exception
287 $data[] = array(
288 'target' => $className3,
289 'expectedConverter' => 'Class3Converter',
290 'typeConverters' => array(),
291 'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\TypeConverterException::class
292 );
293
294 // If The target type is no string, we expect an exception.
295 $data[] = array(
296 'target' => new \stdClass(),
297 'expectedConverter' => '',
298 'typeConverters' => array(),
299 'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException::class
300 );
301 return $data;
302 }
303
304 /**
305 * @test
306 * @dataProvider dataProviderForObjectTypeConverters
307 * @param mixed $targetClass
308 * @param mixed $expectedTypeConverter
309 * @param mixed $typeConverters
310 * @param bool $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::class, 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::class, 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::class, array('dummy'));
357 $this->assertNull($propertyMapper->_call('findFirstEligibleTypeConverterInObjectHierarchy', 'source', 'unknownSourceType', \TYPO3\CMS\Extbase\Core\Bootstrap::class));
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::class;
366 $propertyPath = '';
367 $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, 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::class . '<SomeEntity>';
377 $propertyPath = '';
378 $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, 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::class, 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::class, array('dummy'));
419 $propertyMapper->_set('typeConverters', $typeConverters);
420
421 $propertyMapper->convert($source, 'stdClass', $configuration->allowProperties('firstProperty')->skipUnknownProperties());
422 }
423
424 }