-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Form] Add input + regions options to TimezoneType #23648
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Form\Extension\Core\DataTransformer; | ||
|
||
use Symfony\Component\Form\DataTransformerInterface; | ||
use Symfony\Component\Form\Exception\TransformationFailedException; | ||
|
||
/** | ||
* Transforms between a timezone identifier string and a DateTimeZone object. | ||
* | ||
* @author Roland Franssen <franssen.roland@gmai.com> | ||
*/ | ||
class DateTimeZoneToStringTransformer implements DataTransformerInterface | ||
{ | ||
private $multiple; | ||
|
||
public function __construct($multiple = false) | ||
{ | ||
$this->multiple = $multiple; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function transform($dateTimeZone) | ||
{ | ||
if (null === $dateTimeZone) { | ||
return; | ||
} | ||
|
||
if ($this->multiple) { | ||
if (!is_array($dateTimeZone)) { | ||
throw new TransformationFailedException('Expected an array.'); | ||
} | ||
|
||
return array_map(array(new self(), 'transform'), $dateTimeZone); | ||
} | ||
|
||
if (!$dateTimeZone instanceof \DateTimeZone) { | ||
throw new TransformationFailedException('Expected a \DateTimeZone.'); | ||
} | ||
|
||
return $dateTimeZone->getName(); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function reverseTransform($value) | ||
{ | ||
if (null === $value) { | ||
return; | ||
} | ||
|
||
if ($this->multiple) { | ||
if (!is_array($value)) { | ||
throw new TransformationFailedException('Expected an array.'); | ||
} | ||
|
||
return array_map(array(new self(), 'reverseTransform'), $value); | ||
} | ||
|
||
if (!is_string($value)) { | ||
throw new TransformationFailedException('Expected a string.'); | ||
} | ||
|
||
try { | ||
return new \DateTimeZone($value); | ||
} catch (\Exception $e) { | ||
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,10 @@ | |
|
||
use Symfony\Component\Form\AbstractType; | ||
use Symfony\Component\Form\ChoiceList\ArrayChoiceList; | ||
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader; | ||
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; | ||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer; | ||
use Symfony\Component\Form\FormBuilderInterface; | ||
use Symfony\Component\OptionsResolver\Options; | ||
use Symfony\Component\OptionsResolver\OptionsResolver; | ||
|
||
|
@@ -25,9 +28,21 @@ class TimezoneType extends AbstractType implements ChoiceLoaderInterface | |
* The choices are generated from the ICU function \DateTimeZone::listIdentifiers(). | ||
* | ||
* @var ArrayChoiceList | ||
* | ||
* @deprecated since version 3.4, to be removed in 4.0 | ||
*/ | ||
private $choiceList; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function buildForm(FormBuilderInterface $builder, array $options) | ||
{ | ||
if ('datetimezone' === $options['input']) { | ||
$builder->addModelTransformer(new DateTimeZoneToStringTransformer($options['multiple'])); | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
|
@@ -41,10 +56,20 @@ public function configureOptions(OptionsResolver $resolver) | |
return null; | ||
} | ||
|
||
return $this; | ||
$regions = $options['regions']; | ||
|
||
return new CallbackChoiceLoader(function () use ($regions) { | ||
return self::getTimezones($regions); | ||
}); | ||
}, | ||
'choice_translation_domain' => false, | ||
'input' => 'string', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something else is troubling me. With this new implementation can we still use this choice field as multiple? I guess we should handle an array of timezones too to be fully BC. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. Will look into that 👍 |
||
'regions' => \DateTimeZone::ALL, | ||
)); | ||
|
||
$resolver->setAllowedValues('input', array('string', 'datetimezone')); | ||
|
||
$resolver->setAllowedTypes('regions', 'int'); | ||
} | ||
|
||
/** | ||
|
@@ -65,21 +90,29 @@ public function getBlockPrefix() | |
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @deprecated since version 3.4, to be removed in 4.0 | ||
*/ | ||
public function loadChoiceList($value = null) | ||
{ | ||
@trigger_error(sprintf('Method "%s" is deprecated since version 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); | ||
|
||
if (null !== $this->choiceList) { | ||
return $this->choiceList; | ||
} | ||
|
||
return $this->choiceList = new ArrayChoiceList(self::getTimezones(), $value); | ||
return $this->choiceList = new ArrayChoiceList(self::getTimezones(\DateTimeZone::ALL), $value); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @deprecated since version 3.4, to be removed in 4.0 | ||
*/ | ||
public function loadChoicesForValues(array $values, $value = null) | ||
{ | ||
@trigger_error(sprintf('Method "%s" is deprecated since version 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); | ||
|
||
// Optimize | ||
$values = array_filter($values); | ||
if (empty($values)) { | ||
|
@@ -96,9 +129,13 @@ public function loadChoicesForValues(array $values, $value = null) | |
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @deprecated since version 3.4, to be removed in 4.0 | ||
*/ | ||
public function loadValuesForChoices(array $choices, $value = null) | ||
{ | ||
@trigger_error(sprintf('Method "%s" is deprecated since version 3.4 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); | ||
|
||
// Optimize | ||
$choices = array_filter($choices); | ||
if (empty($choices)) { | ||
|
@@ -116,13 +153,15 @@ public function loadValuesForChoices(array $choices, $value = null) | |
/** | ||
* Returns a normalized array of timezone choices. | ||
* | ||
* @param int $regions | ||
* | ||
* @return array The timezone choices | ||
*/ | ||
private static function getTimezones() | ||
private static function getTimezones($regions) | ||
{ | ||
$timezones = array(); | ||
|
||
foreach (\DateTimeZone::listIdentifiers() as $timezone) { | ||
foreach (\DateTimeZone::listIdentifiers($regions) as $timezone) { | ||
$parts = explode('/', $timezone); | ||
|
||
if (count($parts) > 2) { | ||
|
@@ -139,6 +178,6 @@ private static function getTimezones() | |
$timezones[$region][str_replace('_', ' ', $name)] = $timezone; | ||
} | ||
|
||
return $timezones; | ||
return 1 === count($timezones) ? reset($timezones) : $timezones; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be weird.. :P There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes sorry about that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But we need an array all the time right? Why this change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright! Thanks for clarifying. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; | ||
|
||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer; | ||
use PHPUnit\Framework\TestCase; | ||
|
||
class DateTimeZoneToStringTransformerTest extends TestCase | ||
{ | ||
public function testSingle() | ||
{ | ||
$transformer = new DateTimeZoneToStringTransformer(); | ||
|
||
$this->assertNull($transformer->transform(null)); | ||
$this->assertNull($transformer->reverseTransform(null)); | ||
|
||
$this->assertSame('Europe/Amsterdam', $transformer->transform(new \DateTimeZone('Europe/Amsterdam'))); | ||
$this->assertEquals(new \DateTimeZone('Europe/Amsterdam'), $transformer->reverseTransform('Europe/Amsterdam')); | ||
} | ||
|
||
public function testMultiple() | ||
{ | ||
$transformer = new DateTimeZoneToStringTransformer(true); | ||
|
||
$this->assertNull($transformer->transform(null)); | ||
$this->assertNull($transformer->reverseTransform(null)); | ||
|
||
$this->assertSame(array('Europe/Amsterdam'), $transformer->transform(array(new \DateTimeZone('Europe/Amsterdam')))); | ||
$this->assertEquals(array(new \DateTimeZone('Europe/Amsterdam')), $transformer->reverseTransform(array('Europe/Amsterdam'))); | ||
} | ||
|
||
/** | ||
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException | ||
*/ | ||
public function testInvalidTimezone() | ||
{ | ||
(new DateTimeZoneToStringTransformer())->transform(1); | ||
} | ||
|
||
/** | ||
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException | ||
*/ | ||
public function testUnknownTimezone() | ||
{ | ||
(new DateTimeZoneToStringTransformer(true))->reverseTransform(array('Foo/Bar')); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we name the option value
DateTimeZone
so people can use['input' => \DateTimeZone::class]
? This avoids typos and makes things more explicit as the value directly maps to a class.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this suggestion and we could also apply it for other
Date*Type
having this input option.But I'd keep
datetimezone
as alias for consistency, as I'm not convinced deprecating old option values for other types would be worth it and such aliases are handful too (for configuration based form generation like in EasyAdmin for instance).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so.. strtolower? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
strtolowering the whole FQCN won't help you much ;)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i meant
if ('datetimezone' === strtolower($options['input'])) {
..then again im not sure using class-ish values for
input
makes sense (compared todata_class
). It kinda implies it supports any class name, which it doesnt.