diff --git a/UPGRADE-6.4.md b/UPGRADE-6.4.md index b0ac21e7cab95..90dde7a3849c0 100644 --- a/UPGRADE-6.4.md +++ b/UPGRADE-6.4.md @@ -8,6 +8,12 @@ DoctrineBridge * Deprecate not constructing `DoctrineDataCollector` with an instance of `DebugDataHolder` * Deprecate `DoctrineDataCollector::addLogger()`, use a `DebugDataHolder` instead +Form +---- + + * Deprecate using `DateTime` or `DateTimeImmutable` model data with a different timezone than configured with the + `model_timezone` option in `DateType`, `DateTimeType`, and `TimeType` + HttpFoundation -------------- diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 1ff39c9726070..22597f3b47215 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.4 +--- + + * Deprecate using `DateTime` or `DateTimeImmutable` model data with a different timezone than configured with the + `model_timezone` option in `DateType`, `DateTimeType`, and `TimeType` + 6.3 --- diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 32c58447cdfb2..9ec4c9cca4739 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -22,6 +22,8 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\ReversedTransformer; @@ -194,6 +196,21 @@ public function buildForm(FormBuilderInterface $builder, array $options) new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) )); } + + if (\in_array($options['input'], ['datetime', 'datetime_immutable'], true) && null !== $options['model_timezone']) { + $builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) use ($options): void { + $date = $event->getData(); + + if (!$date instanceof \DateTimeInterface) { + return; + } + + if ($date->getTimezone()->getName() !== $options['model_timezone']) { + trigger_deprecation('symfony/form', '6.4', sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is deprecated.', $date::class, $date->getTimezone()->getName(), $options['model_timezone'])); + // throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', $date::class, $date->getTimezone()->getName(), $options['model_timezone'])); + } + }); + } } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 80023affcb000..480afc315f2ad 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -19,6 +19,8 @@ use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\ReversedTransformer; @@ -178,6 +180,21 @@ class_exists(\IntlTimeZone::class, false) ? \IntlTimeZone::createDefault() : nul new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], ['year', 'month', 'day']) )); } + + if (\in_array($options['input'], ['datetime', 'datetime_immutable'], true) && null !== $options['model_timezone']) { + $builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) use ($options): void { + $date = $event->getData(); + + if (!$date instanceof \DateTimeInterface) { + return; + } + + if ($date->getTimezone()->getName() !== $options['model_timezone']) { + trigger_deprecation('symfony/form', '6.4', sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is deprecated.', $date::class, $date->getTimezone()->getName(), $options['model_timezone'])); + // throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', $date::class, $date->getTimezone()->getName(), $options['model_timezone'])); + } + }); + } } /** diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index c7d5276960831..623259f17a001 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -208,6 +208,21 @@ public function buildForm(FormBuilderInterface $builder, array $options) new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts, 'text' === $options['widget'], $options['reference_date']) )); } + + if (\in_array($options['input'], ['datetime', 'datetime_immutable'], true) && null !== $options['model_timezone']) { + $builder->addEventListener(FormEvents::POST_SET_DATA, static function (FormEvent $event) use ($options): void { + $date = $event->getData(); + + if (!$date instanceof \DateTimeInterface) { + return; + } + + if ($date->getTimezone()->getName() !== $options['model_timezone']) { + trigger_deprecation('symfony/form', '6.4', sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is deprecated.', $date::class, $date->getTimezone()->getName(), $options['model_timezone'])); + // throw new LogicException(sprintf('Using a "%s" instance with a timezone ("%s") not matching the configured model timezone "%s" is not supported.', $date::class, $date->getTimezone()->getName(), $options['model_timezone'])); + } + }); + } } /** diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index a2058596eeeee..71020a06b9b44 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -11,11 +11,14 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; class DateTimeTypeTest extends BaseTypeTestCase { + use ExpectDeprecationTrait; + public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\DateTimeType'; private $defaultLocale; @@ -154,7 +157,7 @@ public function testSubmitWithoutMinutes() 'with_minutes' => false, ]); - $form->setData(new \DateTime()); + $form->setData(new \DateTime('now', new \DateTimeZone('UTC'))); $input = [ 'date' => [ @@ -184,7 +187,7 @@ public function testSubmitWithSeconds() 'with_seconds' => true, ]); - $form->setData(new \DateTime()); + $form->setData(new \DateTime('now', new \DateTimeZone('UTC'))); $input = [ 'date' => [ @@ -748,6 +751,35 @@ public function testSubmitStringWithCustomInputFormat() $this->assertSame('14/01/2018 21:29:00 +00:00', $form->getData()); } + /** + * @group legacy + */ + public function testDateTimeInputTimezoneNotMatchingModelTimezone() + { + $this->expectDeprecation('Since symfony/form 6.4: Using a "DateTime" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is deprecated.'); + // $this->expectException(LogicException::class); + // $this->expectExceptionMessage('Using a "DateTime" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is not supported.'); + + $this->factory->create(static::TESTED_TYPE, new \DateTime('now', new \DateTimeZone('UTC')), [ + 'model_timezone' => 'Europe/Berlin', + ]); + } + + /** + * @group legacy + */ + public function testDateTimeImmutableInputTimezoneNotMatchingModelTimezone() + { + $this->expectDeprecation('Since symfony/form 6.4: Using a "DateTimeImmutable" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is deprecated.'); + // $this->expectException(LogicException::class); + // $this->expectExceptionMessage('Using a "DateTimeImmutable" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is not supported.'); + + $this->factory->create(static::TESTED_TYPE, new \DateTimeImmutable('now', new \DateTimeZone('UTC')), [ + 'input' => 'datetime_immutable', + 'model_timezone' => 'Europe/Berlin', + ]); + } + protected function getTestOptions(): array { return ['widget' => 'choice']; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index abda1a6c071f2..bb171ffe88735 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; @@ -19,6 +20,8 @@ class DateTypeTest extends BaseTypeTestCase { + use ExpectDeprecationTrait; + public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\DateType'; private $defaultTimezone; @@ -654,7 +657,7 @@ public function testIsSynchronizedReturnsTrueIfChoiceAndCompletelyEmpty() public function testIsSynchronizedReturnsTrueIfChoiceAndCompletelyFilled() { - $form = $this->factory->create(static::TESTED_TYPE, new \DateTime(), [ + $form = $this->factory->create(static::TESTED_TYPE, new \DateTime('now', new \DateTimeZone('UTC')), [ 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', 'widget' => 'choice', @@ -1112,6 +1115,35 @@ public function testSubmitStringWithCustomInputFormat() $this->assertSame('14/01/2018', $form->getData()); } + /** + * @group legacy + */ + public function testDateTimeInputTimezoneNotMatchingModelTimezone() + { + $this->expectDeprecation('Since symfony/form 6.4: Using a "DateTime" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is deprecated.'); + // $this->expectException(LogicException::class); + // $this->expectExceptionMessage('Using a "DateTime" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is not supported.'); + + $this->factory->create(static::TESTED_TYPE, new \DateTime('now', new \DateTimeZone('UTC')), [ + 'model_timezone' => 'Europe/Berlin', + ]); + } + + /** + * @group legacy + */ + public function testDateTimeImmutableInputTimezoneNotMatchingModelTimezone() + { + $this->expectDeprecation('Since symfony/form 6.4: Using a "DateTimeImmutable" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is deprecated.'); + // $this->expectException(LogicException::class); + // $this->expectExceptionMessage('Using a "DateTimeImmutable" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is not supported.'); + + $this->factory->create(static::TESTED_TYPE, new \DateTimeImmutable('now', new \DateTimeZone('UTC')), [ + 'input' => 'datetime_immutable', + 'model_timezone' => 'Europe/Berlin', + ]); + } + protected function getTestOptions(): array { return ['widget' => 'choice']; diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php index 4ed930bbd5381..3e8e42f4a8f7a 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\Exception\InvalidConfigurationException; use Symfony\Component\Form\Exception\LogicException; @@ -20,6 +21,8 @@ class TimeTypeTest extends BaseTypeTestCase { + use ExpectDeprecationTrait; + public const TESTED_TYPE = 'Symfony\Component\Form\Extension\Core\Type\TimeType'; public function testSubmitDateTime() @@ -1161,6 +1164,35 @@ public static function provideEmptyData() ]; } + /** + * @group legacy + */ + public function testDateTimeInputTimezoneNotMatchingModelTimezone() + { + $this->expectDeprecation('Since symfony/form 6.4: Using a "DateTime" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is deprecated.'); + // $this->expectException(LogicException::class); + // $this->expectExceptionMessage('Using a "DateTime" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is not supported.'); + + $this->factory->create(static::TESTED_TYPE, new \DateTime('now', new \DateTimeZone('UTC')), [ + 'model_timezone' => 'Europe/Berlin', + ]); + } + + /** + * @group legacy + */ + public function testDateTimeImmutableInputTimezoneNotMatchingModelTimezone() + { + $this->expectDeprecation('Since symfony/form 6.4: Using a "DateTimeImmutable" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is deprecated.'); + // $this->expectException(LogicException::class); + // $this->expectExceptionMessage('Using a "DateTimeImmutable" instance with a timezone ("UTC") not matching the configured model timezone "Europe/Berlin" is not supported.'); + + $this->factory->create(static::TESTED_TYPE, new \DateTimeImmutable('now', new \DateTimeZone('UTC')), [ + 'input' => 'datetime_immutable', + 'model_timezone' => 'Europe/Berlin', + ]); + } + protected function getTestOptions(): array { return ['widget' => 'choice'];