[CLEANUP] The correct case must be used for standard PHP types in phpdoc
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Persistence / Generic / Mapper / DataMapperTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence\Generic\Mapper;
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\DomainObject\DomainObjectInterface;
18 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException;
19 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap;
20 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
21 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
22
23 /**
24 * Test case
25 */
26 class DataMapperTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
27 {
28 /**
29 * @test
30 */
31 public function mapMapsArrayToObjectByCallingmapToObject()
32 {
33 $rows = [['uid' => '1234']];
34 $object = new \stdClass();
35 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $dataMapper */
36 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['mapSingleRow', 'getTargetType']);
37 $dataMapper->expects($this->any())->method('getTargetType')->will($this->returnArgument(1));
38 $dataMapFactory = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapFactory::class);
39 $dataMapper->_set('dataMapFactory', $dataMapFactory);
40 $dataMapper->expects($this->once())->method('mapSingleRow')->with($rows[0])->will($this->returnValue($object));
41 $dataMapper->map(get_class($object), $rows);
42 }
43
44 /**
45 * @test
46 */
47 public function mapSingleRowReturnsObjectFromPersistenceSessionIfAvailable()
48 {
49 $row = ['uid' => '1234'];
50 $object = new \stdClass();
51 $persistenceSession = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Session::class);
52 $persistenceSession->expects($this->once())->method('hasIdentifier')->with('1234')->will($this->returnValue(true));
53 $persistenceSession->expects($this->once())->method('getObjectByIdentifier')->with('1234')->will($this->returnValue($object));
54 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['dummy']);
55 $dataMapper->_set('persistenceSession', $persistenceSession);
56 $dataMapper->_call('mapSingleRow', get_class($object), $row);
57 }
58
59 /**
60 * @test
61 */
62 public function thawPropertiesSetsPropertyValues()
63 {
64 $className = $this->getUniqueId('Class');
65 $classNameWithNS = __NAMESPACE__ . '\\' . $className;
66 eval('namespace ' . __NAMESPACE__ . '; class ' . $className . ' extends \\' . \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class . ' {
67 public $firstProperty; public $secondProperty; public $thirdProperty; public $fourthProperty;
68 }'
69 );
70 $object = new $classNameWithNS();
71 $row = [
72 'uid' => '1234',
73 'firstProperty' => 'firstValue',
74 'secondProperty' => 1234,
75 'thirdProperty' => 1.234,
76 'fourthProperty' => false
77 ];
78 $columnMaps = [
79 'uid' => new ColumnMap('uid', 'uid'),
80 'pid' => new ColumnMap('pid', 'pid'),
81 'firstProperty' => new ColumnMap('firstProperty', 'firstProperty'),
82 'secondProperty' => new ColumnMap('secondProperty', 'secondProperty'),
83 'thirdProperty' => new ColumnMap('thirdProperty', 'thirdProperty')
84 ];
85 $dataMap = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class, ['dummy'], [$className, $className]);
86 $dataMap->_set('columnMaps', $columnMaps);
87 $dataMaps = [
88 $classNameWithNS => $dataMap
89 ];
90 /** @var AccessibleObjectInterface|\TYPO3\CMS\Extbase\Reflection\ClassSchema $classSchema */
91 $classSchema = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, ['dummy'], [$classNameWithNS]);
92 $classSchema->addProperty('pid', 'integer');
93 $classSchema->addProperty('uid', 'integer');
94 $classSchema->addProperty('firstProperty', 'string');
95 $classSchema->addProperty('secondProperty', 'integer');
96 $classSchema->addProperty('thirdProperty', 'float');
97 $classSchema->addProperty('fourthProperty', 'boolean');
98 $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
99 ->setMethods(['getClassSchema'])
100 ->getMock();
101 $mockReflectionService->expects($this->any())->method('getClassSchema')->will($this->returnValue($classSchema));
102 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['dummy']);
103 $dataMapper->_set('dataMaps', $dataMaps);
104 $dataMapper->_set('reflectionService', $mockReflectionService);
105 $dataMapper->_call('thawProperties', $object, $row);
106 $this->assertAttributeEquals('firstValue', 'firstProperty', $object);
107 $this->assertAttributeEquals(1234, 'secondProperty', $object);
108 $this->assertAttributeEquals(1.234, 'thirdProperty', $object);
109 $this->assertAttributeEquals(false, 'fourthProperty', $object);
110 }
111
112 /**
113 * Test if fetchRelatedEager method returns NULL when $fieldValue = '' and relation type == RELATION_HAS_ONE
114 *
115 * @test
116 */
117 public function fetchRelatedEagerReturnsNullForEmptyRelationHasOne()
118 {
119 $columnMap = new ColumnMap('columnName', 'propertyName');
120 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
121 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
122 ->setMethods(['getColumnMap'])
123 ->disableOriginalConstructor()
124 ->getMock();
125 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
126 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap']);
127 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
128 $result = $dataMapper->_call('fetchRelatedEager', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
129 $this->assertEquals(null, $result);
130 }
131
132 /**
133 * Test if fetchRelatedEager method returns empty array when $fieldValue = '' and relation type != RELATION_HAS_ONE
134 *
135 * @test
136 */
137 public function fetchRelatedEagerReturnsEmptyArrayForEmptyRelationNotHasOne()
138 {
139 $columnMap = new ColumnMap('columnName', 'propertyName');
140 $columnMap->setTypeOfRelation(ColumnMap::RELATION_BELONGS_TO_MANY);
141 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
142 ->setMethods(['getColumnMap'])
143 ->disableOriginalConstructor()
144 ->getMock();
145 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
146 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap']);
147 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
148 $result = $dataMapper->_call('fetchRelatedEager', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
149 $this->assertEquals([], $result);
150 }
151
152 /**
153 * Test if fetchRelatedEager method returns NULL when $fieldValue = ''
154 * and relation type == RELATION_HAS_ONE without calling fetchRelated
155 *
156 * @test
157 */
158 public function mapObjectToClassPropertyReturnsNullForEmptyRelationHasOne()
159 {
160 $columnMap = new ColumnMap('columnName', 'propertyName');
161 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
162 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
163 ->setMethods(['getColumnMap'])
164 ->disableOriginalConstructor()
165 ->getMock();
166 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
167 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap', 'fetchRelated']);
168 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
169 $dataMapper->expects($this->never())->method('fetchRelated');
170 $result = $dataMapper->_call('mapObjectToClassProperty', $this->createMock(\TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class), 'SomeName', '');
171 $this->assertEquals(null, $result);
172 }
173
174 /**
175 * Test if mapObjectToClassProperty method returns objects
176 * that are already registered in the persistence session
177 * without query it from the persistence layer
178 *
179 * @test
180 */
181 public function mapObjectToClassPropertyReturnsExistingObjectWithoutCallingFetchRelated()
182 {
183 $columnMap = new ColumnMap('columnName', 'propertyName');
184 $columnMap->setTypeOfRelation(ColumnMap::RELATION_HAS_ONE);
185 $dataMap = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMap::class)
186 ->setMethods(['getColumnMap'])
187 ->disableOriginalConstructor()
188 ->getMock();
189
190 $className = $this->getUniqueId('Class1');
191 $classNameWithNS = __NAMESPACE__ . '\\' . $className;
192 eval('namespace ' . __NAMESPACE__ . '; class ' . $className . ' extends \\' . \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class . ' { public $relationProperty; }');
193 $object = new $classNameWithNS();
194
195 $className2 = $this->getUniqueId('Class2');
196 $className2WithNS = __NAMESPACE__ . '\\' . $className2;
197 eval('namespace ' . __NAMESPACE__ . '; class ' . $className2 . ' extends \\' . \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class . ' { }');
198 $child = new $className2WithNS();
199
200 /** @var \TYPO3\CMS\Extbase\Reflection\ClassSchema|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $classSchema1 */
201 $classSchema1 = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, ['dummy'], [$classNameWithNS]);
202 $classSchema1->addProperty('relationProperty', $className2WithNS);
203 $identifier = 1;
204
205 $session = new \TYPO3\CMS\Extbase\Persistence\Generic\Session();
206 $session->registerObject($child, $identifier);
207
208 $mockReflectionService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class)
209 ->setMethods(['getClassSchema'])
210 ->disableOriginalConstructor()
211 ->getMock();
212 $mockReflectionService->expects($this->any())->method('getClassSchema')->will($this->returnValue($classSchema1));
213
214 $dataMap->expects($this->any())->method('getColumnMap')->will($this->returnValue($columnMap));
215 $dataMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class, ['getDataMap', 'getNonEmptyRelationValue']);
216 $dataMapper->_set('reflectionService', $mockReflectionService);
217 $dataMapper->_set('persistenceSession', $session);
218 $dataMapper->expects($this->any())->method('getDataMap')->will($this->returnValue($dataMap));
219 $dataMapper->expects($this->never())->method('getNonEmptyRelationValue');
220 $result = $dataMapper->_call('mapObjectToClassProperty', $object, 'relationProperty', $identifier);
221 $this->assertEquals($child, $result);
222 }
223
224 /**
225 * Data provider for date checks. Date will be stored based on UTC in
226 * the database. That's why it's not possible to check for explicit date
227 * strings but using the date('c') conversion instead, which considers the
228 * current local timezone setting.
229 *
230 * @return array
231 */
232 public function mapDateTimeHandlesDifferentFieldEvaluationsDataProvider()
233 {
234 return [
235 'nothing' => [null, null, null],
236 'timestamp' => [1, null, date('c', 1)],
237 'empty date' => ['0000-00-00', 'date', null],
238 'valid date' => ['2013-01-01', 'date', date('c', strtotime('2013-01-01T00:00:00+00:00'))],
239 'empty datetime' => ['0000-00-00 00:00:00', 'datetime', null],
240 'valid datetime' => ['2013-01-01 01:02:03', 'datetime', date('c', strtotime('2013-01-01T01:02:03+00:00'))],
241 ];
242 }
243
244 /**
245 * @param null|string|int $value
246 * @param null|string $storageFormat
247 * @param null|string $expectedValue
248 * @test
249 * @dataProvider mapDateTimeHandlesDifferentFieldEvaluationsDataProvider
250 */
251 public function mapDateTimeHandlesDifferentFieldEvaluations($value, $storageFormat, $expectedValue)
252 {
253 $accessibleClassName = $this->buildAccessibleProxy(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
254
255 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $accessibleDataMapFactory */
256 $accessibleDataMapFactory = new $accessibleClassName();
257
258 /** @var $dateTime NULL|\DateTime */
259 $dateTime = $accessibleDataMapFactory->_callRef('mapDateTime', $value, $storageFormat);
260
261 if ($expectedValue === null) {
262 $this->assertNull($dateTime);
263 } else {
264 $this->assertEquals($expectedValue, $dateTime->format('c'));
265 }
266 }
267
268 /**
269 * @test
270 */
271 public function mapDateTimeHandlesSubclassesOfDateTime()
272 {
273 $accessibleClassName = $this->buildAccessibleProxy(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
274
275 /** @var DataMapper|AccessibleObjectInterface|\PHPUnit_Framework_MockObject_MockObject $accessibleDataMapFactory */
276 $accessibleDataMapFactory = new $accessibleClassName();
277 $targetType = 'TYPO3\CMS\Extbase\Tests\Unit\Persistence\Fixture\Model\CustomDateTime';
278 $date = '2013-01-01 01:02:03';
279 $storageFormat = 'datetime';
280
281 /** @var $dateTime NULL|\DateTime */
282 $dateTime = $accessibleDataMapFactory->_callRef('mapDateTime', $date, $storageFormat, $targetType);
283
284 $this->assertInstanceOf($targetType, $dateTime);
285 }
286
287 /**
288 * @test
289 */
290 public function getPlainValueReturnsCorrectDateTimeFormat()
291 {
292 $subject = new DataMapper();
293 $columnMap = new ColumnMap('column_name', 'propertyName');
294 $columnMap->setDateTimeStorageFormat('datetime');
295 $datetimeAsString = '2013-04-15 09:30:00';
296 $input = new \DateTime($datetimeAsString, new \DateTimeZone('UTC'));
297 $this->assertEquals('2013-04-15 09:30:00', $subject->getPlainValue($input, $columnMap));
298 $columnMap->setDateTimeStorageFormat('date');
299 $this->assertEquals('2013-04-15', $subject->getPlainValue($input, $columnMap));
300 }
301
302 /**
303 * @test
304 * @dataProvider getPlainValueReturnsExpectedValuesDataProvider
305 */
306 public function getPlainValueReturnsExpectedValues($expectedValue, $input)
307 {
308 $dataMapper = new DataMapper();
309 $this->assertSame($expectedValue, $dataMapper->getPlainValue($input));
310 }
311
312 /**
313 * @return array
314 */
315 public function getPlainValueReturnsExpectedValuesDataProvider()
316 {
317 $traversableDomainObject = $this->prophesize()
318 ->willImplement(\Iterator::class)
319 ->willImplement(DomainObjectInterface::class);
320 $traversableDomainObject->getUid()->willReturn(1);
321
322 return [
323 'datetime to timestamp' => ['1365866253', new \DateTime('@1365866253')],
324 'boolean true to 1' => [1, true],
325 'boolean false to 0' => [0, false],
326 'NULL is handled as string' => ['NULL', null],
327 'string value is returned unchanged' => ['RANDOM string', 'RANDOM string'],
328 'array is flattened' => ['a,b,c', ['a', 'b', 'c']],
329 'deep array is flattened' => ['a,b,c', [['a', 'b'], 'c']],
330 'traversable domain object to identifier' => [1, $traversableDomainObject->reveal()],
331 'integer value is returned unchanged' => [1234, 1234],
332 'float is converted to string' => ['1234.56', 1234.56],
333 ];
334 }
335
336 /**
337 * @test
338 */
339 public function getPlainValueCallsGetRealInstanceOnInputIfInputIsInstanceOfLazyLoadingProxy()
340 {
341 $this->expectException(UnexpectedTypeException::class);
342 $this->expectExceptionCode(1274799934);
343 $dataMapper = new DataMapper();
344 $input = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy::class);
345 $input->expects($this->once())->method('_loadRealInstance')->will($this->returnValue($dataMapper));
346 $dataMapper->getPlainValue($input);
347 }
348
349 /**
350 * @test
351 */
352 public function getPlainValueCallsGetUidOnDomainObjectInterfaceInput()
353 {
354 $dataMapper = new DataMapper();
355 $input = $this->createMock(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface::class);
356
357 $input->expects($this->once())->method('getUid')->will($this->returnValue(23));
358 $this->assertSame(23, $dataMapper->getPlainValue($input));
359 }
360 }