[BUGFIX] Respect DateTimeImmutable in Extbase 88/57388/7
authorBenni Mack <benni@typo3.org>
Wed, 27 Jun 2018 05:44:03 +0000 (07:44 +0200)
committerGeorg Ringer <georg.ringer@gmail.com>
Mon, 5 Nov 2018 08:17:34 +0000 (09:17 +0100)
The DateTimeConverter and DataMapper of Extbase now checks for
DateTimeInterface, and can thus handle DateTimeImmutable, otherwise
Extbase cannot handle these kinds of formats.

Resolves: #72053
Releases: master
Change-Id: Ic922e715a31e1d02f5f6daa18415e8376788da8b
Reviewed-on: https://review.typo3.org/57388
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
typo3/sysext/extbase/Classes/Persistence/Generic/Mapper/DataMapper.php
typo3/sysext/extbase/Classes/Property/TypeConverter/DateTimeConverter.php
typo3/sysext/extbase/Classes/Utility/TypeHandlingUtility.php
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Model/DateTimeImmutableExample.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/TCA/tx_blogexample_domain_model_datetimeimmutableexample.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/ext_tables.sql
typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/DataMapperTest.php

index f44f153..5599fa0 100644 (file)
@@ -302,10 +302,15 @@ class DataMapper
                             $this->fetchRelated($object, $propertyName, $row[$columnName])
                         );
                         break;
+                    case is_subclass_of($propertyData['type'], \DateTimeInterface::class):
+                            $propertyValue = $this->mapDateTime(
+                                $row[$columnName],
+                                $columnMap->getDateTimeStorageFormat(),
+                                $propertyData['type']
+                            );
+                        break;
                     default:
-                        if ($propertyData['type'] === 'DateTime' || in_array('DateTime', class_parents($propertyData['type']))) {
-                            $propertyValue = $this->mapDateTime($row[$columnName], $columnMap->getDateTimeStorageFormat(), $propertyData['type']);
-                        } elseif (TypeHandlingUtility::isCoreType($propertyData['type'])) {
+                        if (TypeHandlingUtility::isCoreType($propertyData['type'])) {
                             $propertyValue = $this->mapCoreType($propertyData['type'], $row[$columnName]);
                         } else {
                             $propertyValue = $this->mapObjectToClassProperty(
@@ -342,9 +347,9 @@ class DataMapper
      * @param int|string $value Unix timestamp or date/datetime/time value
      * @param string|null $storageFormat Storage format for native date/datetime/time fields
      * @param string|null $targetType The object class name to be created
-     * @return \DateTime
+     * @return \DateTimeInterface
      */
-    protected function mapDateTime($value, $storageFormat = null, $targetType = 'DateTime')
+    protected function mapDateTime($value, $storageFormat = null, $targetType = \DateTime::class)
     {
         $dateTimeTypes = QueryHelper::getDateTimeTypes();
 
@@ -739,7 +744,7 @@ class DataMapper
             $parameter = (int)$input;
         } elseif (is_int($input)) {
             $parameter = $input;
-        } elseif ($input instanceof \DateTime) {
+        } elseif ($input instanceof \DateTimeInterface) {
             if ($columnMap !== null && $columnMap->getDateTimeStorageFormat() !== null) {
                 $storageFormat = $columnMap->getDateTimeStorageFormat();
                 $timeZoneToStore = clone $input;
index fdcc68b..ea92126 100644 (file)
@@ -111,7 +111,7 @@ class DateTimeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\Abstra
      * @param string $targetType must be "DateTime"
      * @param array $convertedChildProperties not used currently
      * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration
-     * @return \DateTime|\TYPO3\CMS\Extbase\Error\Error
+     * @return \DateTimeInterface|\TYPO3\CMS\Extbase\Error\Error
      * @throws \TYPO3\CMS\Extbase\Property\Exception\TypeConverterException
      * @internal only to be used within Extbase, not part of TYPO3 Core API.
      */
@@ -159,7 +159,7 @@ class DateTimeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\Abstra
             return new \TYPO3\CMS\Extbase\Validation\Error('The date "%s" was not recognized (for format "%s").', 1307719788, [$dateAsString, $dateFormat]);
         }
         if (is_array($source)) {
-            $this->overrideTimeIfSpecified($date, $source);
+            $date = $this->overrideTimeIfSpecified($date, $source);
         }
         return $date;
     }
@@ -190,7 +190,7 @@ class DateTimeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\Abstra
         if ($configuration === null) {
             return self::DEFAULT_DATE_FORMAT;
         }
-        $dateFormat = $configuration->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter::class, self::CONFIGURATION_DATE_FORMAT);
+        $dateFormat = $configuration->getConfigurationValue(DateTimeConverter::class, self::CONFIGURATION_DATE_FORMAT);
         if ($dateFormat === null) {
             return self::DEFAULT_DATE_FORMAT;
         }
@@ -203,17 +203,18 @@ class DateTimeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\Abstra
     /**
      * Overrides hour, minute & second of the given date with the values in the $source array
      *
-     * @param \DateTime $date
+     * @param \DateTimeInterface $date
      * @param array $source
+     * @return \DateTimeInterface
      */
-    protected function overrideTimeIfSpecified(\DateTime $date, array $source)
+    protected function overrideTimeIfSpecified(\DateTimeInterface $date, array $source): \DateTimeInterface
     {
         if (!isset($source['hour']) && !isset($source['minute']) && !isset($source['second'])) {
-            return;
+            return $date;
         }
         $hour = isset($source['hour']) ? (int)$source['hour'] : 0;
         $minute = isset($source['minute']) ? (int)$source['minute'] : 0;
         $second = isset($source['second']) ? (int)$source['second'] : 0;
-        $date->setTime($hour, $minute, $second);
+        return $date->setTime($hour, $minute, $second);
     }
 }
index 5f4959c..edb7712 100644 (file)
@@ -20,7 +20,7 @@ class TypeHandlingUtility
     /**
      * A property type parse pattern.
      */
-    const PARSE_TYPE_PATTERN = '/^\\\\?(?P<type>integer|int|float|double|boolean|bool|string|DateTime|[A-Z][a-zA-Z0-9\\\\]+|object|resource|array|ArrayObject|SplObjectStorage|TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage)(?:<\\\\?(?P<elementType>[a-zA-Z0-9\\\\]+)>)?/';
+    const PARSE_TYPE_PATTERN = '/^\\\\?(?P<type>integer|int|float|double|boolean|bool|string|DateTimeImmutable|DateTime|[A-Z][a-zA-Z0-9\\\\]+|object|resource|array|ArrayObject|SplObjectStorage|TYPO3\\\\CMS\\\\Extbase\\\\Persistence\\\\ObjectStorage)(?:<\\\\?(?P<elementType>[a-zA-Z0-9\\\\]+)>)?/';
 
     /**
      * A type pattern to detect literal types.
diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Model/DateTimeImmutableExample.php b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Classes/Domain/Model/DateTimeImmutableExample.php
new file mode 100644 (file)
index 0000000..b691bb3
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+declare(strict_types = 1);
+namespace ExtbaseTeam\BlogExample\Domain\Model;
+
+use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
+
+class DateTimeImmutableExample extends AbstractEntity
+{
+
+    /**
+     * A datetimeImmutable stored in a text field
+     *
+     * @var \DateTimeImmutable
+     */
+    protected $datetimeImmutableText;
+
+    /**
+     * A datetime stored in an integer field
+     *
+     * @var \DateTimeImmutable
+     */
+    protected $datetimeImmutableInt;
+
+    /**
+     * A datetime stored in a datetime field
+     *
+     * @var \DateTimeImmutable
+     */
+    protected $datetimeImmutableDatetime;
+
+    /**
+     * @return \DateTimeImmutable
+     */
+    public function getDatetimeImmutableText(): \DateTimeImmutable
+    {
+        return $this->datetimeImmutableText;
+    }
+
+    /**
+     * @param \DateTimeImmutable $datetimeImmutableText
+     */
+    public function setDatetimeImmutableText(\DateTimeImmutable $datetimeImmutableText)
+    {
+        $this->datetimeImmutableText = $datetimeImmutableText;
+    }
+
+    /**
+     * @return \DateTimeImmutable
+     */
+    public function getDatetimeImmutableInt(): \DateTimeImmutable
+    {
+        return $this->datetimeImmutableInt;
+    }
+
+    /**
+     * @param \DateTimeImmutable $datetimeImmutableInt
+     */
+    public function setDatetimeImmutableInt(\DateTimeImmutable $datetimeImmutableInt)
+    {
+        $this->datetimeImmutableInt = $datetimeImmutableInt;
+    }
+
+    /**
+     * @return \DateTimeImmutable
+     */
+    public function getDatetimeImmutableDatetime(): \DateTimeImmutable
+    {
+        return $this->datetimeImmutableDatetime;
+    }
+
+    /**
+     * @param \DateTimeImmutable $datetimeImmutableDatetime
+     */
+    public function setDatetimeImmutableDatetime(\DateTimeImmutable $datetimeImmutableDatetime)
+    {
+        $this->datetimeImmutableDatetime = $datetimeImmutableDatetime;
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/TCA/tx_blogexample_domain_model_datetimeimmutableexample.php b/typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example/Configuration/TCA/tx_blogexample_domain_model_datetimeimmutableexample.php
new file mode 100644 (file)
index 0000000..26ba2af
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+return [
+    'ctrl' => [
+        'title' => 'DateTimeImmutable Example',
+        'label' => 'title',
+    ],
+    'columns' => [
+        'datetime_immutable_text' => [
+            'exclude' => 1,
+            'label' => 'eval=datetime, db=text',
+            'config' => [
+                'type' => 'input',
+                'renderType' => 'inputDateTime',
+                'eval' => 'datetime',
+            ]
+        ],
+        'datetime_immutable_int' => [
+            'exclude' => 1,
+            'label' => 'eval=datetime, db=int',
+            'config' => [
+                'type' => 'input',
+                'renderType' => 'inputDateTime',
+                'eval' => 'datetime',
+            ]
+        ],
+        'datetime_immutable_datetime' => [
+            'exclude' => 1,
+            'label' => 'eval=datetime, db=datetime',
+            'config' => [
+                'dbType' => 'datetime',
+                'type' => 'input',
+                'renderType' => 'inputDateTime',
+                'eval' => 'datetime',
+            ]
+        ]
+    ],
+    'types' => [
+        '1' => ['showitem' => 'datetime_text', 'datetime_int', 'datetime_datetime']
+    ],
+];
index 73b3721..dd965e2 100644 (file)
@@ -119,3 +119,11 @@ CREATE TABLE tx_blogexample_domain_model_info (
        name varchar(255) DEFAULT '' NOT NULL,
        post int(11) DEFAULT '0' NOT NULL
 );
+
+# Table structure for table 'tx_blogexample_domain_model_datetimeimmutableexample'
+#
+CREATE TABLE tx_blogexample_domain_model_datetimeimmutableexample (
+       datetime_immutable_int int(11) DEFAULT '0' NOT NULL,
+       datetime_immutable_text varchar(255) DEFAULT '' NOT NULL,
+       datetime_immutable_datetime datetime
+);
index 4fd934f..612efec 100644 (file)
@@ -3,6 +3,7 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence\Generic\Mapper;
 
 use ExtbaseTeam\BlogExample\Domain\Model\Comment;
 use ExtbaseTeam\BlogExample\Domain\Model\DateExample;
+use ExtbaseTeam\BlogExample\Domain\Model\DateTimeImmutableExample;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -60,6 +61,7 @@ class DataMapperTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalT
 
         $this->assertEquals($date->getTimestamp(), $existingComment->getDate()->getTimestamp());
     }
+
     /**
      * @test
      */
@@ -119,4 +121,64 @@ class DataMapperTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalT
 
         $this->assertEquals($example->getDatetimeDatetime()->getTimestamp(), $date->getTimestamp());
     }
+
+    /**
+     * @test
+     */
+    public function dateTimeImmutableIntIsHandledAsDateTime()
+    {
+        $subject = new DateTimeImmutableExample();
+        $date = new \DateTimeImmutable('2018-07-24T20:40:00');
+        $subject->setDatetimeImmutableInt($date);
+
+        $this->persistenceManager->add($subject);
+        $this->persistenceManager->persistAll();
+        $uid = $this->persistenceManager->getIdentifierByObject($subject);
+        $this->persistenceManager->clearState();
+
+        /** @var DateTimeImmutableExample $subject */
+        $subject = $this->persistenceManager->getObjectByIdentifier($uid, DateTimeImmutableExample::class);
+
+        $this->assertEquals($date, $subject->getDatetimeImmutableInt());
+    }
+
+    /**
+     * @test
+     */
+    public function dateTimeImmutableTextIsHandledAsDateTime()
+    {
+        $subject = new DateTimeImmutableExample();
+        $date = new \DateTimeImmutable('2018-07-24T20:40:00');
+        $subject->setDatetimeImmutableText($date);
+
+        $this->persistenceManager->add($subject);
+        $this->persistenceManager->persistAll();
+        $uid = $this->persistenceManager->getIdentifierByObject($subject);
+        $this->persistenceManager->clearState();
+
+        /** @var DateTimeImmutableExample $subject */
+        $subject = $this->persistenceManager->getObjectByIdentifier($uid, DateTimeImmutableExample::class);
+
+        $this->assertEquals($date, $subject->getDatetimeImmutableText());
+    }
+
+    /**
+     * @test
+     */
+    public function dateTimeImmutableDateTimeIsHandledAsDateTime()
+    {
+        $subject = new DateTimeImmutableExample();
+        $date = new \DateTimeImmutable('2018-07-24T20:40:00');
+        $subject->setDatetimeImmutableDatetime($date);
+
+        $this->persistenceManager->add($subject);
+        $this->persistenceManager->persistAll();
+        $uid = $this->persistenceManager->getIdentifierByObject($subject);
+        $this->persistenceManager->clearState();
+
+        /** @var DateTimeImmutableExample $subject */
+        $subject = $this->persistenceManager->getObjectByIdentifier($uid, DateTimeImmutableExample::class);
+
+        $this->assertSame($date->getTimestamp(), $subject->getDatetimeImmutableDatetime()->getTimestamp());
+    }
 }