[TASK] Streamline phpdoc annotations in EXT:extbase
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Property / TypeConverter / PersistentObjectConverterTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter;
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\DuplicateObjectException;
18 use TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException;
19 use TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException;
20 use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
21 use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
22 use TYPO3\CMS\Extbase\Reflection\ClassSchema;
23 use TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\PersistentObjectEntityFixture;
24 use TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\PersistentObjectFixture;
25 use TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\PersistentObjectValueObjectFixture;
26 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
27
28 /**
29 * Test case
30 */
31 class PersistentObjectConverterTest extends UnitTestCase
32 {
33 /**
34 * @var \TYPO3\CMS\Extbase\Property\TypeConverterInterface|\PHPUnit_Framework_MockObject_MockObject
35 */
36 protected $converter;
37
38 /**
39 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService|\PHPUnit_Framework_MockObject_MockObject
40 */
41 protected $mockReflectionService;
42
43 /**
44 * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface|\PHPUnit_Framework_MockObject_MockObject
45 */
46 protected $mockPersistenceManager;
47
48 /**
49 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
50 */
51 protected $mockObjectManager;
52
53 /**
54 * @var \TYPO3\CMS\Extbase\Object\Container\Container|\PHPUnit_Framework_MockObject_MockObject
55 */
56 protected $mockContainer;
57
58 /**
59 * @throws \InvalidArgumentException
60 * @throws \PHPUnit\Framework\Exception
61 * @throws \RuntimeException
62 */
63 protected function setUp()
64 {
65 $this->converter = new PersistentObjectConverter();
66 $this->mockReflectionService = $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
67 $this->inject($this->converter, 'reflectionService', $this->mockReflectionService);
68
69 $this->mockPersistenceManager = $this->createMock(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface::class);
70 $this->inject($this->converter, 'persistenceManager', $this->mockPersistenceManager);
71
72 $this->mockObjectManager = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
73 $this->mockObjectManager->expects($this->any())
74 ->method('get')
75 ->will($this->returnCallback(function ($className, ...$arguments) {
76 $reflectionClass = new \ReflectionClass($className);
77 if (empty($arguments)) {
78 return $reflectionClass->newInstance();
79 }
80 return $reflectionClass->newInstanceArgs($arguments);
81 }));
82 $this->inject($this->converter, 'objectManager', $this->mockObjectManager);
83
84 $this->mockContainer = $this->createMock(\TYPO3\CMS\Extbase\Object\Container\Container::class);
85 $this->inject($this->converter, 'objectContainer', $this->mockContainer);
86 }
87
88 /**
89 * @test
90 */
91 public function checkMetadata()
92 {
93 $this->assertEquals(['integer', 'string', 'array'], $this->converter->getSupportedSourceTypes(), 'Source types do not match');
94 $this->assertEquals('object', $this->converter->getSupportedTargetType(), 'Target type does not match');
95 $this->assertEquals(20, $this->converter->getPriority(), 'Priority does not match');
96 }
97
98 /**
99 * @return array
100 */
101 public function dataProviderForCanConvert()
102 {
103 return [
104 [true, false, true],
105 // is entity => can convert
106 [false, true, true],
107 // is valueobject => can convert
108 [false, false, false],
109 // is no entity and no value object => can not convert
110 ];
111 }
112
113 /**
114 * @test
115 * @dataProvider dataProviderForCanConvert
116 */
117 public function canConvertFromReturnsTrueIfClassIsTaggedWithEntityOrValueObject($isEntity, $isValueObject, $expected)
118 {
119 $className = PersistentObjectFixture::class;
120
121 if ($isEntity) {
122 $className = PersistentObjectEntityFixture::class;
123 } elseif ($isValueObject) {
124 $className = PersistentObjectValueObjectFixture::class;
125 }
126 $this->assertEquals($expected, $this->converter->canConvertFrom('myInputData', $className));
127 }
128
129 /**
130 * @test
131 */
132 public function getSourceChildPropertiesToBeConvertedReturnsAllPropertiesExceptTheIdentityProperty()
133 {
134 $source = [
135 'k1' => 'v1',
136 '__identity' => 'someIdentity',
137 'k2' => 'v2'
138 ];
139 $expected = [
140 'k1' => 'v1',
141 'k2' => 'v2'
142 ];
143 $this->assertEquals($expected, $this->converter->getSourceChildPropertiesToBeConverted($source));
144 }
145
146 /**
147 * @test
148 */
149 public function getTypeOfChildPropertyShouldUseReflectionServiceToDetermineType()
150 {
151 $mockSchema = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class)->disableOriginalConstructor()->getMock();
152 $this->mockReflectionService->expects($this->any())->method('getClassSchema')->with('TheTargetType')->will($this->returnValue($mockSchema));
153
154 $this->mockContainer->expects($this->any())->method('getImplementationClassName')->will($this->returnValue('TheTargetType'));
155 $mockSchema->expects($this->any())->method('hasProperty')->with('thePropertyName')->will($this->returnValue(true));
156 $mockSchema->expects($this->any())->method('getProperty')->with('thePropertyName')->will($this->returnValue([
157 'type' => 'TheTypeOfSubObject',
158 'elementType' => null
159 ]));
160 $configuration = $this->buildConfiguration([]);
161 $this->assertEquals('TheTypeOfSubObject', $this->converter->getTypeOfChildProperty('TheTargetType', 'thePropertyName', $configuration));
162 }
163
164 /**
165 * @test
166 */
167 public function getTypeOfChildPropertyShouldUseConfiguredTypeIfItWasSet()
168 {
169 $this->mockReflectionService->expects($this->never())->method('getClassSchema');
170 $this->mockContainer->expects($this->any())->method('getImplementationClassName')->will($this->returnValue('foo'));
171
172 $configuration = $this->buildConfiguration([]);
173 $configuration->forProperty('thePropertyName')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_TARGET_TYPE, 'Foo\Bar');
174 $this->assertEquals('Foo\Bar', $this->converter->getTypeOfChildProperty('foo', 'thePropertyName', $configuration));
175 }
176
177 /**
178 * @test
179 */
180 public function convertFromShouldFetchObjectFromPersistenceIfUuidStringIsGiven()
181 {
182 $identifier = '17';
183 $object = new \stdClass();
184
185 $this->mockPersistenceManager->expects($this->any())->method('getObjectByIdentifier')->with($identifier)->will($this->returnValue($object));
186 $this->assertSame($object, $this->converter->convertFrom($identifier, 'MySpecialType'));
187 }
188
189 /**
190 * @test
191 */
192 public function convertFromShouldFetchObjectFromPersistenceIfuidStringIsGiven()
193 {
194 $identifier = '17';
195 $object = new \stdClass();
196
197 $this->mockPersistenceManager->expects($this->any())->method('getObjectByIdentifier')->with($identifier)->will($this->returnValue($object));
198 $this->assertSame($object, $this->converter->convertFrom($identifier, 'MySpecialType'));
199 }
200
201 /**
202 * @test
203 */
204 public function convertFromShouldFetchObjectFromPersistenceIfOnlyIdentityArrayGiven()
205 {
206 $identifier = '12345';
207 $object = new \stdClass();
208
209 $source = [
210 '__identity' => $identifier
211 ];
212 $this->mockPersistenceManager->expects($this->any())->method('getObjectByIdentifier')->with($identifier)->will($this->returnValue($object));
213 $this->assertSame($object, $this->converter->convertFrom($source, 'MySpecialType'));
214 }
215
216 /**
217 * @test
218 */
219 public function convertFromShouldThrowExceptionIfObjectNeedsToBeModifiedButConfigurationIsNotSet()
220 {
221 $this->expectException(InvalidPropertyMappingConfigurationException::class);
222 $this->expectExceptionCode(1297932028);
223 $identifier = '12345';
224 $object = new \stdClass();
225 $object->someProperty = 'asdf';
226
227 $source = [
228 '__identity' => $identifier,
229 'foo' => 'bar'
230 ];
231 $this->mockPersistenceManager->expects($this->any())->method('getObjectByIdentifier')->with($identifier)->will($this->returnValue($object));
232 $this->converter->convertFrom($source, 'MySpecialType');
233 }
234
235 /**
236 * @param array $typeConverterOptions
237 * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration
238 */
239 protected function buildConfiguration($typeConverterOptions)
240 {
241 $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
242 $configuration->setTypeConverterOptions(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, $typeConverterOptions);
243 return $configuration;
244 }
245
246 /**
247 * @param int $numberOfResults
248 * @param Matcher $howOftenIsGetFirstCalled
249 * @return \stdClass
250 */
251 public function setupMockQuery($numberOfResults, $howOftenIsGetFirstCalled)
252 {
253 $mockClassSchema = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class)
254 ->setConstructorArgs([\TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\Query::class])
255 ->getMock();
256 $mockClassSchema->expects($this->any())->method('getIdentityProperties')->will($this->returnValue(['key1' => 'someType']));
257 $this->mockReflectionService->expects($this->any())->method('getClassSchema')->with('SomeType')->will($this->returnValue($mockClassSchema));
258
259 $mockConstraint = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\Comparison::class)->disableOriginalConstructor()->getMock();
260
261 $mockObject = new \stdClass();
262 $mockQuery = $this->createMock(\TYPO3\CMS\Extbase\Persistence\QueryInterface::class);
263 $mockQueryResult = $this->createMock(\TYPO3\CMS\Extbase\Persistence\QueryResultInterface::class);
264 $mockQueryResult->expects($this->any())->method('count')->will($this->returnValue($numberOfResults));
265 $mockQueryResult->expects($howOftenIsGetFirstCalled)->method('getFirst')->will($this->returnValue($mockObject));
266 $mockQuery->expects($this->any())->method('equals')->with('key1', 'value1')->will($this->returnValue($mockConstraint));
267 $mockQuery->expects($this->any())->method('matching')->with($mockConstraint)->will($this->returnValue($mockQuery));
268 $mockQuery->expects($this->any())->method('execute')->will($this->returnValue($mockQueryResult));
269
270 $this->mockPersistenceManager->expects($this->any())->method('createQueryForType')->with('SomeType')->will($this->returnValue($mockQuery));
271
272 return $mockObject;
273 }
274
275 /**
276 * @test
277 */
278 public function convertFromShouldReturnExceptionIfNoMatchingObjectWasFound()
279 {
280 $this->expectException(TargetNotFoundException::class);
281 $this->expectExceptionCode(1297933823);
282 $this->setupMockQuery(0, $this->never());
283 $this->mockReflectionService->expects($this->never())->method('getClassSchema');
284
285 $source = [
286 '__identity' => 123
287 ];
288 $actual = $this->converter->convertFrom($source, 'SomeType');
289 $this->assertNull($actual);
290 }
291
292 /**
293 * @test
294 */
295 public function convertFromShouldThrowExceptionIfMoreThanOneObjectWasFound()
296 {
297 $this->expectException(DuplicateObjectException::class);
298 // @TODO expectExceptionCode is 0
299 $this->setupMockQuery(2, $this->never());
300
301 $source = [
302 '__identity' => 666
303 ];
304 $this->mockPersistenceManager
305 ->expects($this->any())
306 ->method('getObjectByIdentifier')
307 ->with(666)
308 ->will($this->throwException(new DuplicateObjectException('testing', 1476107580)));
309 $this->converter->convertFrom($source, 'SomeType');
310 }
311
312 /**
313 * @test
314 */
315 public function convertFromShouldThrowExceptionIfObjectNeedsToBeCreatedButConfigurationIsNotSet()
316 {
317 $this->expectException(InvalidPropertyMappingConfigurationException::class);
318 // @TODO expectExceptionCode is 0
319 $source = [
320 'foo' => 'bar'
321 ];
322 $this->converter->convertFrom($source, 'MySpecialType');
323 }
324
325 /**
326 * @test
327 */
328 public function convertFromShouldCreateObject()
329 {
330 $source = [
331 'propertyX' => 'bar'
332 ];
333 $convertedChildProperties = [
334 'property1' => 'bar'
335 ];
336 $expectedObject = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters();
337 $expectedObject->property1 = 'bar';
338
339 $this->mockReflectionService
340 ->expects($this->any())
341 ->method('getMethodParameters')
342 ->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class, '__construct')
343 ->will($this->throwException(new \ReflectionException('testing', 1476107618)));
344 $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
345 $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class, $convertedChildProperties, $configuration);
346 $this->assertEquals($expectedObject, $result);
347 }
348
349 /**
350 * @test
351 */
352 public function convertFromShouldThrowExceptionIfPropertyOnTargetObjectCouldNotBeSet()
353 {
354 $this->expectException(InvalidTargetException::class);
355 $this->expectExceptionCode(1297935345);
356 $source = [
357 'propertyX' => 'bar'
358 ];
359 $object = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters();
360 $convertedChildProperties = [
361 'propertyNotExisting' => 'bar'
362 ];
363 $this->mockObjectManager->expects($this->any())->method('get')->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class)->will($this->returnValue($object));
364 $this->mockReflectionService->expects($this->any())->method('getMethodParameters')->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class, '__construct')->will($this->returnValue([]));
365 $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
366 $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class, $convertedChildProperties, $configuration);
367 $this->assertSame($object, $result);
368 }
369
370 /**
371 * @test
372 */
373 public function convertFromShouldCreateObjectWhenThereAreConstructorParameters(): void
374 {
375 $classSchemaMock = $this->createMock(ClassSchema::class);
376 $classSchemaMock
377 ->expects($this->any())
378 ->method('getMethod')
379 ->with('__construct')
380 ->willReturn([
381 'params' => [
382 'property1' => ['optional' => false]
383 ]
384 ]);
385
386 $classSchemaMock
387 ->expects($this->any())
388 ->method('hasConstructor')
389 ->willReturn(true);
390
391 $this->mockReflectionService
392 ->expects($this->any())
393 ->method('getClassSchema')
394 ->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class)
395 ->willReturn($classSchemaMock);
396
397 $source = [
398 'propertyX' => 'bar'
399 ];
400 $convertedChildProperties = [
401 'property1' => 'param1',
402 'property2' => 'bar'
403 ];
404 $expectedObject = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor('param1');
405 $expectedObject->setProperty2('bar');
406
407 $this->mockContainer->expects($this->any())->method('getImplementationClassName')->will($this->returnValue(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class));
408 $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
409 $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class, $convertedChildProperties, $configuration);
410 $this->assertEquals($expectedObject, $result);
411 $this->assertEquals('bar', $expectedObject->getProperty2());
412 }
413
414 /**
415 * @test
416 */
417 public function convertFromShouldCreateObjectWhenThereAreOptionalConstructorParameters()
418 {
419 $classSchemaMock = $this->createMock(ClassSchema::class);
420 $classSchemaMock
421 ->expects($this->any())
422 ->method('getMethod')
423 ->with('__construct')
424 ->willReturn([
425 'params' => [
426 'property1' => ['optional' => true, 'defaultValue' => 'thisIsTheDefaultValue']
427 ]
428 ]);
429
430 $classSchemaMock
431 ->expects($this->any())
432 ->method('hasConstructor')
433 ->willReturn(true);
434
435 $this->mockReflectionService
436 ->expects($this->any())
437 ->method('getClassSchema')
438 ->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class)
439 ->willReturn($classSchemaMock);
440
441 $source = [
442 'propertyX' => 'bar'
443 ];
444 $expectedObject = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor('thisIsTheDefaultValue');
445
446 $this->mockContainer->expects($this->any())->method('getImplementationClassName')->will($this->returnValue(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class));
447 $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
448 $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class, [], $configuration);
449 $this->assertEquals($expectedObject, $result);
450 }
451
452 /**
453 * @test
454 */
455 public function convertFromShouldThrowExceptionIfRequiredConstructorParameterWasNotFound(): void
456 {
457 $classSchemaMock = $this->createMock(ClassSchema::class);
458 $classSchemaMock
459 ->expects($this->any())
460 ->method('getMethod')
461 ->with('__construct')
462 ->willReturn([
463 'params' => [
464 'property1' => ['optional' => false]
465 ]
466 ]);
467
468 $classSchemaMock
469 ->expects($this->any())
470 ->method('hasConstructor')
471 ->willReturn(true);
472
473 $this->mockReflectionService
474 ->expects($this->any())
475 ->method('getClassSchema')
476 ->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class)
477 ->willReturn($classSchemaMock);
478
479 $this->expectException(InvalidTargetException::class);
480 $this->expectExceptionCode(1268734872);
481 $source = [
482 'propertyX' => 'bar'
483 ];
484 $object = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor('param1');
485 $convertedChildProperties = [
486 'property2' => 'bar'
487 ];
488
489 $this->mockContainer->expects($this->any())->method('getImplementationClassName')->will($this->returnValue(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class));
490 $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
491 $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class, $convertedChildProperties, $configuration);
492 $this->assertSame($object, $result);
493 }
494
495 /**
496 * @test
497 */
498 public function convertFromShouldReturnNullForEmptyString()
499 {
500 $source = '';
501 $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class);
502 $this->assertNull($result);
503 }
504 }