8000 [Form] Add support for DateTimeImmutable by jameshalsall · Pull Request #19889 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Form] Add support for DateTimeImmutable #19889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,19 @@ abstract class BaseDateTimeTransformer implements DataTransformerInterface

protected $outputTimezone;

protected $immutable;

/**
* Constructor.
*
* @param string $inputTimezone The name of the input timezone
* @param string $outputTimezone The name of the output timezone
* @param bool $immutable Whether to use \DateTimeImmutable instead of \DateTime
*
* @throws UnexpectedTypeException if a timezone is not a string
* @throws InvalidArgumentException if a timezone is not valid
*/
public function __construct($inputTimezone = null, $outputTimezone = null)
public function __construct($inputTimezone = null, $outputTimezone = null, $immutable = false)
{
if (null !== $inputTimezone && !is_string($inputTimezone)) {
throw new UnexpectedTypeException($inputTimezone, 'string');
Expand All @@ -63,5 +66,12 @@ public function __construct($inputTimezone = null, $outputTimezone = null)
} catch (\Exception $e) {
throw new InvalidArgumentException(sprintf('Output timezone is invalid: %s.', $this->outputTimezone), $e->getCode(), $e);
}

$this->immutable = $immutable;
}

protected function getDateTimeClass()
{
return true === $this->immutable ? \DateTimeImmutable::class : \DateTime::class;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer
* @param string $outputTimezone The output timezone
* @param array $fields The date fields
* @param bool $pad Whether to use padding
* @param bool $immutable Whether to use \DateTimeImmutable instead of \DateTime
*
* @throws UnexpectedTypeException if a timezone is not a string
*/
public function __construct($inputTimezone = null, $outputTimezone = null, array $fields = null, $pad = false)
public function __construct($inputTimezone = null, $outputTimezone = null, array $fields = null, $pad = false, $immutable = false)
{
parent::__construct($inputTimezone, $outputTimezone);
parent::__construct($inputTimezone, $outputTimezone, $immutable);

if (null === $fields) {
$fields = array('year', 'month', 'day', 'hour', 'minute', 'second');
Expand Down Expand Up @@ -108,7 +109,7 @@ public function transform($dateTime)
*
* @param array $value Localized date
*
* @return \DateTime Normalized date
* @return \DateTimeInterface Normalized date
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the hint should be explicit here IMO: @return \DateTime|DateTimeImmutable since no other implementation is used.

Copy link
Contributor Author
@jameshalsall jameshalsall Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think leaving it as \DateTimeInterface is still semantically correct, and is more concise

*
* @throws TransformationFailedException If the given value is not an array,
* if the value could not be transformed
Expand Down Expand Up @@ -170,7 +171,8 @@ public function reverseTransform($value)
}

try {
$dateTime = new \DateTime(sprintf(
$dateTimeClass = $this->getDateTimeClass();
$dateTime = new $dateTimeClass(sprintf(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@return \DateTime line 111 should be changed to @return \DataTime|\DateTimeImmutable and also in other transformers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to @return \DateTimeInterface

'%s-%s-%s %s:%s:%s',
empty($value['year']) ? '1970' : $value['year'],
empty($value['month']) ? '1' : $value['month'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
* @param int $timeFormat The time format
* @param int $calendar One of the \IntlDateFormatter calendar constants
* @param string $pattern A pattern to pass to \IntlDateFormatter
* @param bool $immutable Whether to use \DateTimeImmutable instead of \DateTime
*
* @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string
*/
public function __construct($inputTimezone = null, $outputTimezone = null, $dateFormat = null, $timeFormat = null, $calendar = \IntlDateFormatter::GREGORIAN, $pattern = null)
public function __construct($inputTimezone = null, $outputTimezone = null, $dateFormat = null, $timeFormat = null, $calendar = \IntlDateFormatter::GREGORIAN, $pattern = null, $immutable = false)
{
parent::__construct($inputTimezone, $outputTimezone);
parent::__construct($inputTimezone, $outputTimezone, $immutable);

if (null === $dateFormat) {
$dateFormat = \IntlDateFormatter::MEDIUM;
Expand Down Expand Up @@ -101,7 +102,7 @@ public function transform($dateTime)
*
* @param string|array $value Localized date string/array
*
* @return \DateTime Normalized date
* @return \DateTimeInterface Normalized date
*
* @throws TransformationFailedException if the given value is not a string,
* if the date could not be parsed
Expand All @@ -126,14 +127,16 @@ public function reverseTransform($value)
throw new TransformationFailedException(intl_get_error_message());
}

$dateTimeClass = $this->getDateTimeClass();

try {
if ($dateOnly) {
// we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight
return new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone));
return new $dateTimeClass(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone));
}

// read timestamp into DateTime object - the formatter delivers a timestamp
$dateTime = new \DateTime(sprintf('@%s', $timestamp));
$dateTime = new $dateTimeClass(sprintf('@%s', $timestamp));
// set timezone separately, as it would be ignored if set via the constructor,
// see http://php.net/manual/en/datetime.construct.php
$dateTime->setTimezone(new \DateTimeZone($this->outputTimezone));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public function transform($dateTime)
*
* @param string $rfc3339 Formatted string
*
* @return \DateTime Normalized date
* @return \DateTimeInterface Normalized date
*
* @throws TransformationFailedException If the given value is not a string,
* if the value could not be transformed
Expand All @@ -68,8 +68,10 @@ public function reverseTransform($rfc3339)
return;
}

$dateTimeClass = $this->getDateTimeClass();

try {
$dateTime = new \DateTime($rfc3339);
$dateTime = new $dateTimeClass($rfc3339);
} catch (\Exception $e) {
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer
* @param string $outputTimezone The name of the output timezone
* @param string $format The date format
* @param bool $parseUsingPipe Whether to parse by appending a pipe "|" to the parse format
* @param bool $immutable Whether to use \DateTimeImmutable instead of \DateTime
*
* @throws UnexpectedTypeException if a timezone is not a string
*/
public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s', $parseUsingPipe = true)
public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s', $parseUsingPipe = true, $immutable = false)
{
parent::__construct($inputTimezone, $outputTimezone);
parent::__construct($inputTimezone, $outputTimezone, $immutable);

$this->generateFormat = $this->parseFormat = $format;
$this->parseUsingPipe = $parseUsingPipe || null === $parseUsingPipe;
Expand Down Expand Up @@ -115,7 +116,7 @@ public function transform($dateTime)
*
* @param string $value A value as produced by PHP's date() function
*
* @return \DateTime An instance of \DateTime
* @return \DateTimeInterface An instance of \DateTime
*
* @throws TransformationFailedException If the given value is not a string,
* or could not be transformed
Expand All @@ -130,10 +131,12 @@ public function reverseTransform($value)
throw new TransformationFailedException('Expected a string.');
}

$dateTimeClass = $this->getDateTimeClass();

$outputTz = new \DateTimeZone($this->outputTimezone);
$dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz);
$dateTime = $dateTimeClass::createFromFormat($this->parseFormat, $value, $outputTz);

$lastErrors = \DateTime::getLastErrors();
$lastErrors = $dateTimeClass::getLastErrors();

if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) {
throw new TransformationFailedException(
Expand Down
10 changes: 7 additions & 3 deletions src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$timeFormat = self::DEFAULT_TIME_FORMAT;
$calendar = \IntlDateFormatter::GREGORIAN;
$pattern = is_string($options['format']) ? $options['format'] : null;
$immutable = 'datetimeimmutable' === $options['input'];

if (!in_array($dateFormat, self::$acceptedFormats, true)) {
throw new InvalidOptionsException('The "date_format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
Expand All @@ -96,7 +97,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
if (self::HTML5_FORMAT === $pattern) {
$builder->addViewTransformer(new DateTimeToRfc3339Transformer(
$options['model_timezone'],
$options['view_timezone']
$options['view_timezone'],
$immutable
));
} else {
$builder->addViewTransformer(new DateTimeToLocalizedStringTransformer(
Expand All @@ -105,7 +107,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$dateFormat,
$timeFormat,
$calendar,
$pattern
$pattern,
$immutable
));
}
} else {
Expand Down Expand Up @@ -155,7 +158,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)

$builder
->addViewTransformer(new DataTransformerChain(array(
new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts),
new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, false, $immutable),
new ArrayToPartsTransformer(array(
'date' => $dateParts,
'time' => $timeParts,
Expand Down Expand Up @@ -255,6 +258,7 @@ public function configureOptions(OptionsResolver $resolver)

$resolver->setAllowedValues('input', array(
'datetime',
'datetimeimmutable',
'string',
'timestamp',
'array',
Expand Down
7 changes: 5 additions & 2 deletions src/Symfony/Component/Form/Extension/Core/Type/DateType.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$timeFormat = \IntlDateFormatter::NONE;
$calendar = \IntlDateFormatter::GREGORIAN;
$pattern = is_string($options['format']) ? $options['format'] : null;
$immutable = 'datetimeimmutable' === $options['input'];

if (!in_array($dateFormat, self::$acceptedFormats, true)) {
throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.');
Expand All @@ -67,7 +68,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$dateFormat,
$timeFormat,
$calendar,
$pattern
$pattern,
$immutable
));
} else {
$yearOptions = $monthOptions = $dayOptions = array(
Expand Down Expand Up @@ -113,7 +115,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
->add('month', self::$widgets[$options['widget']], $monthOptions)
->add('day', self::$widgets[$options['widget']], $dayOptions)
->addViewTransformer(new DateTimeToArrayTransformer(
$options['model_timezone'], $options['view_timezone'], array('year', 'month', 'day')
$options['model_timezone'], $options['view_timezone'], array('year', 'month', 'day'), false, $immutable
))
->setAttribute('formatter', $formatter)
;
Expand Down Expand Up @@ -254,6 +256,7 @@ public function configureOptions(OptionsResolver $resolver)

$resolver->setAllowedValues('input', array(
'datetime',
'datetimeimmutable',
'string',
'timestamp',
'array',
Expand Down
7 changes: 5 additions & 2 deletions src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$parts[] = 'second';
}

$immutable = 'datetimeimmutable' === $options['input'];

if ('single_text' === $options['widget']) {
$builder->addViewTransformer(new DateTimeToStringTransformer($options['model_timezone'], $options['view_timezone'], $format));
$builder->addViewTransformer(new DateTimeToStringTransformer($options['model_timezone'], $options['view_timezone'], $format, true, $immutable));
} else {
$hourOptions = $minuteOptions = $secondOptions = array(
'error_bubbling' => true,
Expand Down Expand Up @@ -117,7 +119,7 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$builder->add('second', self::$widgets[$options['widget']], $secondOptions);
}

$builder->addViewTransformer(new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, 'text' === $options['widget']));
$builder->addViewTransformer(new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, 'text' === $options['widget'], $immutable));
}

if ('string' === $options['input']) {
Expand Down Expand Up @@ -239,6 +241,7 @@ public function configureOptions(OptionsResolver $resolver)

$resolver->setAllowedValues('input', array(
'datetime',
'datetimeimmutable',
'string',
'timestamp',
'array',
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Form/Test/TypeTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected function setUp()
$this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
}

public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual)
public static function assertDateTimeEquals(\DateTimeInterface $expected, \DateTimeInterface $actual)
{
self::assertEquals($expected->format('c'), $actual->format('c'));
}
Expand Down
Loading
0