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