8000 [Form] Add intltimezone input to TimezoneType · symfony/symfony@e169dfb · GitHub
[go: up one dir, main page]

Skip to content

Commit e169dfb

Browse files
ro0NLfabpot
authored andcommitted
[Form] Add intltimezone input to TimezoneType
1 parent fba11b4 commit e169dfb

File tree

4 files changed

+206
-4
lines changed

4 files changed

+206
-4
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Core\DataTransformer;
13+
14+
use Symfony\Component\Form\DataTransformerInterface;
15+
use Symfony\Component\Form\Exception\TransformationFailedException;
16+
17+
/**
18+
* Transforms between a timezone identifier string and a IntlTimeZone object.
19+
*
20+
* @author Roland Franssen <franssen.roland@gmail.com>
21+
*/
22+
class IntlTimeZoneToStringTransformer implements DataTransformerInterface
23+
{
24+
private $multiple;
25+
26+
public function __construct(bool $multiple = false)
27+
{
28+
$this->multiple = $multiple;
29+
}
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function transform($intlTimeZone)
35+
{
36+
if (null === $intlTimeZone) {
37+
return;
38+
}
39+
40+
if ($this->multiple) {
41+
if (!\is_array($intlTimeZone)) {
42+
throw new TransformationFailedException('Expected an array of \IntlTimeZone objects.');
43+
}
44+
45+
return array_map([new self(), 'transform'], $intlTimeZone);
46+
}
47+
48+
if (!$intlTimeZone instanceof \IntlTimeZone) {
49+
throw new TransformationFailedException('Expected a \IntlTimeZone object.');
50+
}
51+
52+
return $intlTimeZone->getID();
53+
}
54+
55+
/**
56+
* {@inheritdoc}
57+
*/
58+
public function reverseTransform($value)
59+
{
60+
if (null === $value) {
61+
return;
62+
}
63+
64+
if ($this->multiple) {
65+
if (!\is_array($value)) {
66+
throw new TransformationFailedException('Expected an array of timezone identifier strings.');
67+
}
68+
69+
return array_map([new self(), 'reverseTransform'], $value);
70+
}
71+
72+
if (!\is_string($value)) {
73+
throw new TransformationFailedException('Expected a timezone identifier string.');
74+
}
75+
76+
$intlTimeZone = \IntlTimeZone::createTimeZone($value);
77+
78+
if ('Etc/Unknown' === $intlTimeZone->getID()) {
79+
throw new TransformationFailedException(sprintf('Unknown timezone identifier "%s".', $value));
80+
}
81+
82+
return $intlTimeZone;
83+
}
84+
}

src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
use Symfony\Component\Form\AbstractType;
1515
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
16+
use Symfony\Component\Form\Exception\LogicException;
1617
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer;
18+
use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer;
1719
use Symfony\Component\Form\FormBuilderInterface;
1820
use Symfony\Component\OptionsResolver\Options;
1921
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -27,6 +29,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
2729
{
2830
if ('datetimezone' === $options['input']) {
2931
$builder->addModelTransformer(new DateTimeZoneToStringTransformer($options['multiple']));
32+
} elseif ('intltimezone' === $options['input']) {
33+
$builder->addModelTransformer(new IntlTimeZoneToStringTransformer($options['multiple']));
3034
}
3135
}
3236

@@ -38,17 +42,25 @@ public function configureOptions(OptionsResolver $resolver)
3842
$resolver->setDefaults([
3943
'choice_loader' => function (Options $options) {
4044
$regions = $options->offsetGet('regions', false);
45+
$input = $options['input'];
4146

42-
return new CallbackChoiceLoader(function () use ($regions) {
43-
return self::getTimezones($regions);
47+
return new CallbackChoiceLoader(function () use ($regions, $input) {
48+
return self::getTimezones($regions, $input);
4449
});
4550
},
4651
'choice_translation_domain' => false,
4752
'input' => 'string',
4853
'regions' => \DateTimeZone::ALL,
4954
]);
5055

51-
$resolver->setAllowedValues('input', ['string', 'datetimezone']);
56+
$resolver->setAllowedValues('input', ['string', 'datetimezone', 'intltimezone']);
57+
$resolver->setNormalizer('input', function (Options $options, $value) {
58+
if ('intltimezone' === $value && !class_exists(\IntlTimeZone::class)) {
59+
throw new LogicException('Cannot use "intltimezone" input because the PHP intl extension is not available.');
60+
}
61+
62+
return $value;
63+
});
5264

5365
$resolver->setAllowedTypes('regions', 'int');
5466
$resolver->setDeprecated('regions', 'The option "%name%" is deprecated since Symfony 4.2.');
@@ -73,11 +85,15 @@ public function getBlockPrefix()
7385
/**
7486
* Returns a normalized array of timezone choices.
7587
*/
76-
private static function getTimezones(int $regions): array
88+
private static function getTimezones(int $regions, string $input): array
7789
{
7890
$timezones = [];
7991

8092
foreach (\DateTimeZone::listIdentifiers($regions) as $timezone) {
93+
if ('intltimezone' === $input && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($timezone)->getID()) {
94+
continue;
95+
}
96+
8197
$parts = explode('/', $timezone);
8298

8399
if (\count($parts) > 2) {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer;
16+
17+
/**
18+
* @requires extension intl
19+
*/
20+
class IntlTimeZoneToStringTransformerTest extends TestCase
21+
{
22+
public function testSingle()
23+
{
24+
$transformer = new IntlTimeZoneToStringTransformer();
25+
26+
$this->assertNull($transformer->transform(null));
27+
$this->assertNull($transformer->reverseTransform(null));
28+
29+
$this->assertSame('Europe/Amsterdam', $transformer->transform(\IntlTimeZone::createTimeZone('Europe/Amsterdam')));
30+
$this->assertEquals(\IntlTimeZone::createTimeZone('Europe/Amsterdam'), $transformer->reverseTransform('Europe/Amsterdam'));
31+
}
32+
33+
public function testMultiple()
34+
{
35+
$transformer = new IntlTimeZoneToStringTransformer(true);
36+
37+
$this->assertNull($transformer->transform(null));
38+
$this->assertNull($transformer->reverseTransform(null));
39+
40+
$this->assertSame(['Europe/Amsterdam'], $transformer->transform([\IntlTimeZone::createTimeZone('Europe/Amsterdam')]));
41+
$this->assertEquals([\IntlTimeZone::createTimeZone('Europe/Amsterdam')], $transformer->reverseTransform(['Europe/Amsterdam']));
42+
}
43+
44+
/**
45+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
46+
*/
47+
public function testInvalidTimezone()
48+
{
49+
(new IntlTimeZoneToStringTransformer())->transform(1);
50+
}
51+
52+
/**
53+
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
54+
*/
55+
public function testUnknownTimezone()
56+
{
57+
(new IntlTimeZoneToStringTransformer(true))->reverseTran F438 sform(['Foo/Bar']);
58+
}
59+
}

src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ public function testDateTimeZoneInput()
6565
$this->assertEquals([new \DateTimeZone('Europe/Amsterdam'), new \DateTimeZone('Europe/Paris')], $form->getData());
6666
}
6767

68+
public function testDateTimeZoneInputWithBc()
69+
{
70+
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'datetimezone']);
71+
$form->submit('Europe/Saratov');
72+
73+
$this->assertEquals(new \DateTimeZone('Europe/Saratov'), $form->getData());
74+
$this->assertContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
75+
}
76+
6877
/**
6978
* @group legacy
7079
* @expectedDeprecation The option "regions" is deprecated since Symfony 4.2.
@@ -76,4 +85,38 @@ public function testFilterByRegions()
7685

7786
$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Amsterdam'), $choices, '', false, false);
7887
}
88+
89+
/**
90+
* @requires extension intl
91+
*/
92+
public function testIntlTimeZoneInput()
93+
{
94+
$form = $this->factory->create(static::TESTED_TYPE, \IntlTimeZone::createTimeZone('America/New_York'), ['input' => 'intltimezone']);
95+
96+
$this->assertSame('America/New_York', $form->createView()->vars['value']);
97+
98+
$form->submit('Europe/Amsterdam');
99+
100+
$this->assertEquals(\IntlTimeZone::createTimeZone('Europe/Amsterdam'), $form->getData());
101+
102+
$form = $this->factory->create(static::TESTED_TYPE, [\IntlTimeZone::createTimeZone('America/New_York')], ['input' => 'intltimezone', 'multiple' => true]);
103+
104+
$this->assertSame(['America/New_York'], $form->createView()->vars['value']);
105+
106+
$form->submit(['Europe/Amsterdam', 'Europe/Paris']);
107+
108+
$this->assertEquals([\IntlTimeZone::createTimeZone('Europe/Amsterdam'), \IntlTimeZone::createTimeZone('Europe/Paris')], $form->getData());
109+
}
110+
111+
/**
112+
* @requires extension intl
113+
*/
114+
public function testIntlTimeZoneInputWithBc()
115+
{
116+
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'intltimezone']);
117+
$form->submit('Europe/Saratov');
118+
119+
$this->assertNull($form->getData());
120+
$this->assertNotContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
121+
}
79122
}

0 commit comments

Comments
 (0)
0