[BUGFIX] Fix several typos in php comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Persistence / Generic / Mapper / DataMapperTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence\Generic\Mapper;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
19 use TYPO3\CMS\Extbase\Object\Container\Container;
20 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
21 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap;
22 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
23 use TYPO3\CMS\Extbase\Reflection\ClassSchema;
24 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
25 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
26
27 /**
28 * Test case
29 */
30 class DataMapperTest extends UnitTestCase
31 {
32 /**
33 * This test does not actually test anything rather than map calls both mocked methods getTargetType and mapSingleRow
34 * while completely ignoring the result of the method.
35 * @todo: Cover this functionality by a functional test
36 *
37 * @test
38 */
39 public function mapMapsArrayToObjectByCallingmapToObject()
40 {
41 $rows = [['uid' => '1234']];
42 $object = new \stdClass();
43
44 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $dataMapper */
45 $dataMapper = $this->getMockBuilder(DataMapper::class)
46 ->setConstructorArgs([
47 $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class),
48 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
49 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Session::class),
50 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class),
51 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
52 $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
53 $this->createMock(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class),
54 ])
55 ->setMethods(['mapSingleRow', 'getTargetType'])
56 ->getMock();
57
58 $dataMapper->expects($this->any())->method('getTargetType')->will($this->returnArgument(1));
59 $dataMapper->expects($this->once())->method('mapSingleRow')->with($rows[0])->will($this->returnValue($object));
60
61 $dataMapper->map(get_class($object), $rows);
62 }
63
64 /**
65 * This test does not actually test anything rather than mapSingleRow delegates functionality to
66 * the persistence session which is a mock itself.
67 * @todo: Cover this functionality by a functional test
68 *
69 * @test
70 */
71 public function mapSingleRowReturnsObjectFromPersistenceSessionIfAvailable()
72 {
73 $row = ['uid' => '1234'];
74 $object = new \stdClass();
75 $persistenceSession = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Session::class);
76 $persistenceSession->expects($this->once())->method('hasIdentifier')->with('1234')->will($this->returnValue(true));
77 $persistenceSession->expects($this->once())->method('getObjectByIdentifier')->with('1234')->will($this->returnValue($object));
78
79 $dataMapper = $this->getAccessibleMock(
80 DataMapper::class,
81 ['dummy'],
82 [
83 $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class),
84 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
85 $persistenceSession,
86 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class),
87 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
88 $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
89 $this->createMock(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class),
90 ]
91 );
92
93 $dataMapper->_call('mapSingleRow', get_class($object), $row);
94 }
95
96 /**
97 * This test has a far too complex setup to test a single unit. This actually is a functional test, accomplished
98 * by mocking the whole dependency chain. This test only tests code structure while it should test functionality.
99 * @todo: Cover this functionality by a functional test
100 *
101 * @test
102 */
103 public function thawPropertiesSetsPropertyValues()
104 {
105 $className = Fixture\DummyEntity::class;
106 $object = new Fixture\DummyEntity();
107 $row = [
108 'uid' => '1234',
109 'firstProperty' => 'firstValue',
110 'secondProperty' => 1234,
111 'thirdProperty' => 1.234,
112 'fourthProperty' => false
113 ];
114 $columnMaps = [
115 'uid' => new ColumnMap('uid', 'uid'),
116 'pid' => new ColumnMap('pid', 'pid'),
117 'firstProperty' => new ColumnMap('firstProperty', 'firstProperty'),
118 'secondProperty' => new ColumnMap('secondProperty', 'secondProperty'),
119 'thirdProperty' => new ColumnMap('thirdProperty', 'thirdProperty')
120 ];
121 $dataMap = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class, ['dummy'], [$className, $className]);
122 $dataMap->_set('columnMaps', $columnMaps);
123 $dataMaps = [
124 $className => $dataMap
125 ];
126 /** @var AccessibleObjectInterface|\TYPO3\CMS\Extbase\Reflection\ClassSchema $classSchema */
127 $classSchema = new ClassSchema($className);
128 $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
129 ->setMethods(['getClassSchema'])
130 ->getMock();
131 $mockReflectionService->expects($this->any())->method('getClassSchema')->will($this->returnValue($classSchema));
132 $dataMapFactory = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class, ['dummy'], [], '', false);
133 $dataMapFactory->_set('dataMaps', $dataMaps);
134 $dataMapper = $this->getAccessibleMock(
135 DataMapper::class,
136 ['dummy'],
137 [
138 $mockReflectionService,
139 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
140 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Session::class),
141 $dataMapFactory,
142 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
143 $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
144 $this->createMock(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class),
145 ]
146 );
147 $dataMapper->_call('thawProperties', $object, $row);
148
149 $this->assertEquals('firstValue', $object->firstProperty);
150 $this->assertEquals(1234, $object->secondProperty);
151 $this->assertEquals(1.234, $object->thirdProperty);
152 $this->assertEquals(false, $object->fourthProperty);
153 }
154
155 /**
156 * Test if fetchRelatedEager method returns NULL when $fieldValue = '' and relation type == RELATION_HAS_ONE
157 *
158 * This is actually a functional test as it tests multiple units along with a very specific setup of dependencies.
159 * @todo: Cover this functionality by a functional test
160 *
161 * @test
162 */
163 public function fetchRelatedEagerReturnsNullForEmptyRelationHasOne()
164 {
165 $columnMap = new ColumnMap('columnName', 'propertyName');
166 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
167 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
168 ->setMethods(['getColumnMap'])
169 ->disableOriginalConstructor()
170 ->getMock();
171 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
172 $dataMapper = $this->getAccessibleMock(DataMapper::class, ['getDataMap'], [], '', false);
173 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
174 $result = $dataMapper->_call('fetchRelatedEager', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
175 $this->assertEquals(null, $result);
176 }
177
178 /**
179 * Test if fetchRelatedEager method returns empty array when $fieldValue = '' and relation type != RELATION_HAS_ONE
180 *
181 * This is actually a functional test as it tests multiple units along with a very specific setup of dependencies.
182 * @todo: Cover this functionality by a functional test
183 *
184 * @test
185 */
186 public function fetchRelatedEagerReturnsEmptyArrayForEmptyRelationNotHasOne()
187 {
188 $columnMap = new ColumnMap('columnName', 'propertyName');
189 $columnMap->setTypeOfRelation(ColumnMap::RELATION_BELONGS_TO_MANY);
190 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
191 ->setMethods(['getColumnMap'])
192 ->disableOriginalConstructor()
193 ->getMock();
194 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
195 $dataMapper = $this->getAccessibleMock(DataMapper::class, ['getDataMap'], [], '', false);
196 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
197 $result = $dataMapper->_call('fetchRelatedEager', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
198 $this->assertEquals([], $result);
199 }
200
201 /**
202 * Test if fetchRelatedEager method returns NULL when $fieldValue = ''
203 * and relation type == RELATION_HAS_ONE without calling fetchRelated
204 *
205 * This is actually a functional test as it tests multiple units along with a very specific setup of dependencies.
206 * @todo: Cover this functionality by a functional test
207 *
208 * @test
209 */
210 public function MapObjectToClassPropertyReturnsNullForEmptyRelationHasOne()
211 {
212 $columnMap = new ColumnMap('columnName', 'propertyName');
213 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
214 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
215 ->setMethods(['getColumnMap'])
216 ->disableOriginalConstructor()
217 ->getMock();
218 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
219 $dataMapper = $this->getAccessibleMock(DataMapper::class, ['getDataMap', 'fetchRelated'], [], '', false);
220 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
221 $dataMapper->expects($this->never())->method('fetchRelated');
222 $result = $dataMapper->_call('mapObjectToClassProperty', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
223 $this->assertEquals(null, $result);
224 }
225
226 /**
227 * Test if mapObjectToClassProperty method returns objects
228 * that are already registered in the persistence session
229 * without query it from the persistence layer
230 *
231 * This is actually a functional test as it tests multiple units along with a very specific setup of dependencies.
232 * @todo: Cover this functionality by a functional test
233 *
234 * @test
235 */
236 public function mapObjectToClassPropertyReturnsExistingObjectWithoutCallingFetchRelated()
237 {
238 $columnMap = new ColumnMap('columnName', 'propertyName');
239 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
240 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
241 ->setMethods(['getColumnMap'])
242 ->disableOriginalConstructor()
243 ->getMock();
244
245 $object = new Fixture\DummyParentEntity();
246 $child = new Fixture\DummyChildEntity();
247
248 /** @var \TYPO3\CMS\Extbase\Reflection\ClassSchema|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $classSchema1 */
249 $classSchema1 = new ClassSchema(Fixture\DummyParentEntity::class);
250 $identifier = 1;
251
252 $psrContainer = $this->getMockBuilder(\Psr\Container\ContainerInterface::class)
253 ->setMethods(['has', 'get'])
254 ->disableOriginalConstructor()
255 ->getMock();
256 $psrContainer->expects($this->any())->method('has')->will($this->returnValue(false));
257 $container = new Container($psrContainer);
258
259 $session = new \TYPO3\CMS\Extbase\Persistence\Generic\Session(new Container($psrContainer));
260 $session->registerObject($child, $identifier);
261
262 $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
263 ->setMethods(['getClassSchema'])
264 ->disableOriginalConstructor()
265 ->getMock();
266 $mockReflectionService->expects($this->any())->method('getClassSchema')->will($this->returnValue($classSchema1));
267
268 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
269
270 $dataMapper = $this->getAccessibleMock(
271 DataMapper::class,
272 ['getDataMap', 'getNonEmptyRelationValue'],
273 [
274 $mockReflectionService,
275 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory::class),
276 $session,
277 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class),
278 $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\QueryFactoryInterface::class),
279 $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class),
280 $this->createMock(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class),
281 ]
282 );
283 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
284 $dataMapper->expects($this->never())->method('getNonEmptyRelationValue');
285 $result = $dataMapper->_call('mapObjectToClassProperty', $object, 'relationProperty', $identifier);
286 $this->assertEquals($child, $result);
287 }
288
289 /**
290 * Data provider for date checks. Date will be stored based on UTC in
291 * the database. That's why it's not possible to check for explicit date
292 * strings but using the date('c') conversion instead, which considers the
293 * current local timezone setting.
294 *
295 * @return array
296 */
297 public function mapDateTimeHandlesDifferentFieldEvaluationsDataProvider()
298 {
299 return [
300 'nothing' => [null, null, null],
301 'timestamp' => [1, null, date('c', 1)],
302 'empty date' => ['0000-00-00', 'date', null],
303 'valid date' => ['2013-01-01', 'date', date('c', strtotime('2013-01-01T00:00:00+00:00'))],
304 'empty datetime' => ['0000-00-00 00:00:00', 'datetime', null],
305 'valid datetime' => ['2013-01-01 01:02:03', 'datetime', date('c', strtotime('2013-01-01T01:02:03+00:00'))],
306 ];
307 }
308
309 /**
310 * @param string|int|null $value
311 * @param string|null $storageFormat
312 * @param string|null $expectedValue
313 * @test
314 * @dataProvider mapDateTimeHandlesDifferentFieldEvaluationsDataProvider
315 */
316 public function mapDateTimeHandlesDifferentFieldEvaluations($value, $storageFormat, $expectedValue)
317 {
318 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $accessibleDataMapFactory */
319 $accessibleDataMapFactory = $this->getAccessibleMock(DataMapper::class, ['dummy'], [], '', false);
320
321 /** @var $dateTime NULL|\DateTime */
322 $dateTime = $accessibleDataMapFactory->_callRef('mapDateTime', $value, $storageFormat);
323
324 if ($expectedValue === null) {
325 $this->assertNull($dateTime);
326 } else {
327 $this->assertEquals($expectedValue, $dateTime->format('c'));
328 }
329 }
330
331 /**
332 * @test
333 */
334 public function testMapDateTimeHandlesSubclassesOfDateTime()
335 {
336 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $accessibleDataMapFactory */
337 $accessibleDataMapFactory = $this->getAccessibleMock(DataMapper::class, ['dummy'], [], '', false);
338 $targetType = 'TYPO3\CMS\Extbase\Tests\Unit\Persistence\Fixture\Model\CustomDateTime';
339 $date = '2013-01-01 01:02:03';
340 $storageFormat = 'datetime';
341
342 /** @var $dateTime NULL|\DateTime */
343 $dateTime = $accessibleDataMapFactory->_callRef('mapDateTime', $date, $storageFormat, $targetType);
344
345 $this->assertInstanceOf($targetType, $dateTime);
346 }
347
348 /**
349 * @test
350 */
351 public function getPlainValueReturnsCorrectDateTimeFormat()
352 {
353 /** @var DataMapper $subject */
354 $subject = $this->createPartialMock(DataMapper::class, []);
355
356 $columnMap = new ColumnMap('column_name', 'propertyName');
357 $columnMap->setDateTimeStorageFormat('datetime');
358 $datetimeAsString = '2013-04-15 09:30:00';
359 $input = new \DateTime($datetimeAsString, new \DateTimeZone('UTC'));
360 $this->assertEquals('2013-04-15 09:30:00', $subject->getPlainValue($input, $columnMap));
361 $columnMap->setDateTimeStorageFormat('date');
362 $this->assertEquals('2013-04-15', $subject->getPlainValue($input, $columnMap));
363 }
364
365 /**
366 * @test
367 * @dataProvider getPlainValueReturnsExpectedValuesDataProvider
368 */
369 public function getPlainValueReturnsExpectedValues($expectedValue, $input)
370 {
371 /** @var DataMapper $dataMapper */
372 $dataMapper = $this->createPartialMock(DataMapper::class, []);
373
374 $this->assertSame($expectedValue, $dataMapper->getPlainValue($input));
375 }
376
377 /**
378 * @return array
379 */
380 public function getPlainValueReturnsExpectedValuesDataProvider()
381 {
382 $traversableDomainObject = $this->prophesize()
383 ->willImplement(\Iterator::class)
384 ->willImplement(DomainObjectInterface::class);
385 $traversableDomainObject->getUid()->willReturn(1);
386
387 return [
388 'datetime to timestamp' => ['1365866253', new \DateTime('@1365866253')],
389 'boolean true to 1' => [1, true],
390 'boolean false to 0' => [0, false],
391 'NULL is handled as string' => ['NULL', null],
392 'string value is returned unchanged' => ['RANDOM string', 'RANDOM string'],
393 'array is flattened' => ['a,b,c', ['a', 'b', 'c']],
394 'deep array is flattened' => ['a,b,c', [['a', 'b'], 'c']],
395 'traversable domain object to identifier' => [1, $traversableDomainObject->reveal()],
396 'integer value is returned unchanged' => [1234, 1234],
397 'float is converted to string' => ['1234.56', 1234.56],
398 ];
399 }
400
401 /**
402 * @test
403 */
404 public function getPlainValueCallsGetRealInstanceOnInputIfInputIsInstanceOfLazyLoadingProxy()
405 {
406 $this->expectException(UnexpectedTypeException::class);
407 $this->expectExceptionCode(1274799934);
408
409 /** @var DataMapper $dataMapper */
410 $dataMapper = $this->createPartialMock(DataMapper::class, []);
411 $input = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class);
412 $input->expects($this->once())->method('_loadRealInstance')->will($this->returnValue($dataMapper));
413 $dataMapper->getPlainValue($input);
414 }
415
416 /**
417 * @test
418 */
419 public function getPlainValueCallsGetUidOnDomainObjectInterfaceInput()
420 {
421 /** @var DataMapper $dataMapper */
422 $dataMapper = $this->createPartialMock(DataMapper::class, []);
423 $input = $this->createMock(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface::class);
424
425 $input->expects($this->once())->method('getUid')->will($this->returnValue(23));
426 $this->assertSame(23, $dataMapper->getPlainValue($input));
427 }
428 }