[BUGFIX] Native DateTime values cannot be mapped from storage
authorOliver Hader <oliver@typo3.org>
Mon, 11 Mar 2013 23:20:19 +0000 (00:20 +0100)
committerAnja Leichsenring <aleichsenring@ab-softlab.de>
Sat, 30 Mar 2013 13:52:02 +0000 (14:52 +0100)
DateTime values that are stored with an accordant (native) type
cannot be mapped correctly since a unix timestamp is expected.
The TCA configuration property 'dbType' for 'date' and
'datetime' fields reflects the non-timestamp way.

Change-Id: I214c857bcb8b0380554b481870ab5f3556713226
Fixes: #46193
Releases: 6.0, 6.1
Reviewed-on: https://review.typo3.org/19009
Reviewed-by: Wouter Wolters
Tested-by: Wouter Wolters
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/ColumnMap.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapFactory.php
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapFactoryTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapperTest.php

index 406290f..64dd47e 100644 (file)
@@ -162,6 +162,15 @@ class ColumnMap {
        protected $childKeyFieldName;
 
        /**
+        * Alternative format for storing DataTime formats
+        * (instead of using unix-time stamps). Allowed values
+        * are 'date' and 'datetime'
+        *
+        * @var string
+        */
+       protected $dateTimeStorageFormat;
+
+       /**
         * Constructs a Column Map
         *
         * @param string $columnName The column name
@@ -369,6 +378,20 @@ class ColumnMap {
        public function getChildKeyFieldName() {
                return $this->childKeyFieldName;
        }
+
+       /**
+        * @param string $dateTimeStorageFormat
+        */
+       public function setDateTimeStorageFormat($dateTimeStorageFormat) {
+               $this->dateTimeStorageFormat = $dateTimeStorageFormat;
+       }
+
+       /**
+        * @return string
+        */
+       public function getDateTimeStorageFormat() {
+               return $this->dateTimeStorageFormat;
+       }
 }
 
 ?>
\ No newline at end of file
index 76c5ca7..77b512b 100644 (file)
@@ -176,6 +176,7 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface {
                        $columnMap = $this->createColumnMap($columnName, $propertyName);
                        $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
                        $columnMap = $this->setRelations($columnMap, $columnDefinition['config'], $propertyMetaData);
+                       $columnMap = $this->setFieldEvaluations($columnMap, $columnDefinition['config']);
                        $dataMap->addColumnMap($columnMap);
                }
                // debug($dataMap);
@@ -322,6 +323,26 @@ class DataMapFactory implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
+        * Sets field evaluations based on $TCA column configuration.
+        *
+        * @param ColumnMap $columnMap The column map
+        * @param NULL|array $columnConfiguration The column configuration from $TCA
+        * @return ColumnMap
+        */
+       protected function setFieldEvaluations(ColumnMap $columnMap, array $columnConfiguration = NULL) {
+               if (!empty($columnConfiguration['eval'])) {
+                       $fieldEvaluations = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $columnConfiguration['eval'], TRUE);
+                       $dateTimeEvaluations = array('date', 'datetime');
+
+                       if (count(array_intersect($dateTimeEvaluations, $fieldEvaluations)) > 0 && !empty($columnConfiguration['dbType'])) {
+                               $columnMap->setDateTimeStorageFormat($columnConfiguration['dbType']);
+                       }
+               }
+
+               return $columnMap;
+       }
+
+       /**
         * This method sets the configuration for a 1:1 relation based on
         * the $TCA column configuration
         *
index 0f46344..3bcb0ac 100644 (file)
@@ -281,7 +281,7 @@ class DataMapper implements \TYPO3\CMS\Core\SingletonInterface {
                                                break;
                                        default:
                                                if ($propertyData['type'] === 'DateTime' || in_array('DateTime', class_parents($propertyData['type']))) {
-                                                       $propertyValue = $this->mapDateTime($row[$columnName]);
+                                                       $propertyValue = $this->mapDateTime($row[$columnName], $columnMap->getDateTimeStorageFormat());
                                                } else {
                                                        $propertyValue = $this->mapResultToPropertyValue($object, $propertyName, $this->fetchRelated($object, $propertyName, $row[$columnName]));
                                                }
@@ -295,18 +295,21 @@ class DataMapper implements \TYPO3\CMS\Core\SingletonInterface {
        }
 
        /**
-        * Creates a DateTime from an unix timestamp. If the input is empty
-        * NULL is returned.
+        * Creates a DateTime from an unix timestamp or date/datetime value.
+        * If the input is empty, NULL is returned.
         *
-        * @param integer $timestamp
+        * @param integer|string $value Unix timestamp or date/datetime value
+        * @param NULL|string $storageFormat Storage format for native date/datetime fields
         * @return \DateTime
         */
-       protected function mapDateTime($timestamp) {
-               if (empty($timestamp)) {
+       protected function mapDateTime($value, $storageFormat = NULL) {
+               if (empty($value) || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') {
                        // 0 -> NULL !!!
                        return NULL;
+               } elseif ($storageFormat === 'date' || $storageFormat === 'datetime') {
+                       return new \DateTime($value);
                } else {
-                       return new \DateTime(date('c', $timestamp));
+                       return new \DateTime(date('c', $value));
                }
        }
 
index 0f58077..bfdfeb2 100644 (file)
@@ -314,6 +314,43 @@ class DataMapFactoryTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
        }
 
        /**
+        * @return array
+        */
+       public function columnMapIsInitializedWithFieldEvaluationsForDateTimeFieldsDataProvider() {
+               return array(
+                       'date field' => array('date', 'date'),
+                       'datetime field' => array('datetime', 'datetime'),
+                       'no date/datetime field' => array('', NULL),
+               );
+       }
+
+       /**
+        * @param string $type
+        * @param NULL|string $expectedValue
+        * @test
+        * @dataProvider columnMapIsInitializedWithFieldEvaluationsForDateTimeFieldsDataProvider
+        */
+       public function columnMapIsInitializedWithFieldEvaluationsForDateTimeFields($type, $expectedValue) {
+               $columnDefinition = array(
+                       'type' => 'input',
+                       'dbType' => $type,
+                       'eval' => $type,
+               );
+
+               $mockColumnMap = $this->getMock('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Mapper\\ColumnMap', array('setDateTimeStorageFormat'), array(), '', FALSE);
+
+               if ($expectedValue !== NULL) {
+                       $mockColumnMap->expects($this->once())->method('setDateTimeStorageFormat')->with($this->equalTo($type));
+               } else {
+                       $mockColumnMap->expects($this->never())->method('setDateTimeStorageFormat');
+               }
+
+               $accessibleClassName = $this->buildAccessibleProxy('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Mapper\\DataMapFactory');
+               $accessibleDataMapFactory = new $accessibleClassName();
+               $accessibleDataMapFactory->_callRef('setFieldEvaluations', $mockColumnMap, $columnDefinition);
+       }
+
+       /**
         * @test
         * @expectedException \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidClassException
         */
index 22ad063..df99dc6 100644 (file)
@@ -136,6 +136,43 @@ class DataMapperTest extends \TYPO3\CMS\Extbase\Tests\Unit\BaseTestCase {
                $result = $dataMapper->_call('fetchRelatedEager', $this->getMock('TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity'), 'SomeName', '');
                $this->assertEquals(array(), $result);
        }
+
+       /**
+        * @return array
+        */
+       public function mapDateTimeHandlesDifferentFieldEvaluationsDataProvider() {
+               $localTimeZoneOffset = date('Z');
+
+               return array(
+                       'nothing' => array(NULL, NULL, NULL),
+                       'timestamp' => array(86401 - $localTimeZoneOffset, NULL, '1970-01-02 00:00:01'),
+                       'empty date' => array('0000-00-00', 'date', NULL),
+                       'valid date' => array('2013-01-01', 'date', '2013-01-01 00:00:00'),
+                       'empty datetime' => array('0000-00-00 00:00:00', 'datetime', NULL),
+                       'valid datetime' => array('2013-01-01 01:02:03', 'datetime', '2013-01-01 01:02:03'),
+               );
+       }
+
+       /**
+        * @param NULL|string|integer $value
+        * @param NULL|string $storageFormat
+        * @param NULL|string $expectedValue
+        * @test
+        * @dataProvider mapDateTimeHandlesDifferentFieldEvaluationsDataProvider
+        */
+       public function mapDateTimeHandlesDifferentFieldEvaluations($value, $storageFormat, $expectedValue) {
+               $accessibleClassName = $this->buildAccessibleProxy('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Mapper\\DataMapper');
+               $accessibleDataMapFactory = new $accessibleClassName();
+
+               /** @var $dateTime NULL|\DateTime */
+               $dateTime = $accessibleDataMapFactory->_callRef('mapDateTime', $value, $storageFormat);
+
+               if ($expectedValue === NULL) {
+                       $this->assertNull($dateTime);
+               } else {
+                       $this->assertEquals($expectedValue, $dateTime->format('Y-m-d H:i:s'));
+               }
+       }
 }
 
 ?>
\ No newline at end of file