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