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