[Form] Add intltimezone input to TimezoneType

This commit is contained in:
Roland Franssen 2019-04-22 09:57:23 +02:00 committed by Fabien Potencier
parent fba11b4dc3
commit e169dfb968
4 changed files with 206 additions and 4 deletions

View File

@ -0,0 +1,84 @@
<?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 IntlTimeZone object.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class IntlTimeZoneToStringTransformer implements DataTransformerInterface
{
private $multiple;
public function __construct(bool $multiple = false)
{
$this->multiple = $multiple;
}
/**
* {@inheritdoc}
*/
public function transform($intlTimeZone)
{
if (null === $intlTimeZone) {
return;
}
if ($this->multiple) {
if (!\is_array($intlTimeZone)) {
throw new TransformationFailedException('Expected an array of \IntlTimeZone objects.');
}
return array_map([new self(), 'transform'], $intlTimeZone);
}
if (!$intlTimeZone instanceof \IntlTimeZone) {
throw new TransformationFailedException('Expected a \IntlTimeZone object.');
}
return $intlTimeZone->getID();
}
/**
* {@inheritdoc}
*/
public function reverseTransform($value)
{
if (null === $value) {
return;
}
if ($this->multiple) {
if (!\is_array($value)) {
throw new TransformationFailedException('Expected an array of timezone identifier strings.');
}
return array_map([new self(), 'reverseTransform'], $value);
}
if (!\is_string($value)) {
throw new TransformationFailedException('Expected a timezone identifier string.');
}
$intlTimeZone = \IntlTimeZone::createTimeZone($value);
if ('Etc/Unknown' === $intlTimeZone->getID()) {
throw new TransformationFailedException(sprintf('Unknown timezone identifier "%s".', $value));
}
return $intlTimeZone;
}
}

View File

@ -13,7 +13,9 @@ namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
use Symfony\Component\Form\Exception\LogicException;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeZoneToStringTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
@ -27,6 +29,8 @@ class TimezoneType extends AbstractType
{
if ('datetimezone' === $options['input']) {
$builder->addModelTransformer(new DateTimeZoneToStringTransformer($options['multiple']));
} elseif ('intltimezone' === $options['input']) {
$builder->addModelTransformer(new IntlTimeZoneToStringTransformer($options['multiple']));
}
}
@ -38,9 +42,10 @@ class TimezoneType extends AbstractType
$resolver->setDefaults([
'choice_loader' => function (Options $options) {
$regions = $options->offsetGet('regions', false);
$input = $options['input'];
return new CallbackChoiceLoader(function () use ($regions) {
return self::getTimezones($regions);
return new CallbackChoiceLoader(function () use ($regions, $input) {
return self::getTimezones($regions, $input);
});
},
'choice_translation_domain' => false,
@ -48,7 +53,14 @@ class TimezoneType extends AbstractType
'regions' => \DateTimeZone::ALL,
]);
$resolver->setAllowedValues('input', ['string', 'datetimezone']);
$resolver->setAllowedValues('input', ['string', 'datetimezone', 'intltimezone']);
$resolver->setNormalizer('input', function (Options $options, $value) {
if ('intltimezone' === $value && !class_exists(\IntlTimeZone::class)) {
throw new LogicException('Cannot use "intltimezone" input because the PHP intl extension is not available.');
}
return $value;
});
$resolver->setAllowedTypes('regions', 'int');
$resolver->setDeprecated('regions', 'The option "%name%" is deprecated since Symfony 4.2.');
@ -73,11 +85,15 @@ class TimezoneType extends AbstractType
/**
* Returns a normalized array of timezone choices.
*/
private static function getTimezones(int $regions): array
private static function getTimezones(int $regions, string $input): array
{
$timezones = [];
foreach (\DateTimeZone::listIdentifiers($regions) as $timezone) {
if ('intltimezone' === $input && 'Etc/Unknown' === \IntlTimeZone::createTimeZone($timezone)->getID()) {
continue;
}
$parts = explode('/', $timezone);
if (\count($parts) > 2) {

View File

@ -0,0 +1,59 @@
<?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 PHPUnit\Framework\TestCase;
use Symfony\Component\Form\Extension\Core\DataTransformer\IntlTimeZoneToStringTransformer;
/**
* @requires extension intl
*/
class IntlTimeZoneToStringTransformerTest extends TestCase
{
public function testSingle()
{
$transformer = new IntlTimeZoneToStringTransformer();
$this->assertNull($transformer->transform(null));
$this->assertNull($transformer->reverseTransform(null));
$this->assertSame('Europe/Amsterdam', $transformer->transform(\IntlTimeZone::createTimeZone('Europe/Amsterdam')));
$this->assertEquals(\IntlTimeZone::createTimeZone('Europe/Amsterdam'), $transformer->reverseTransform('Europe/Amsterdam'));
}
public function testMultiple()
{
$transformer = new IntlTimeZoneToStringTransformer(true);
$this->assertNull($transformer->transform(null));
$this->assertNull($transformer->reverseTransform(null));
$this->assertSame(['Europe/Amsterdam'], $transformer->transform([\IntlTimeZone::createTimeZone('Europe/Amsterdam')]));
$this->assertEquals([\IntlTimeZone::createTimeZone('Europe/Amsterdam')], $transformer->reverseTransform(['Europe/Amsterdam']));
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testInvalidTimezone()
{
(new IntlTimeZoneToStringTransformer())->transform(1);
}
/**
* @expectedException \Symfony\Component\Form\Exception\TransformationFailedException
*/
public function testUnknownTimezone()
{
(new IntlTimeZoneToStringTransformer(true))->reverseTransform(['Foo/Bar']);
}
}

View File

@ -65,6 +65,15 @@ class TimezoneTypeTest extends BaseTypeTest
$this->assertEquals([new \DateTimeZone('Europe/Amsterdam'), new \DateTimeZone('Europe/Paris')], $form->getData());
}
public function testDateTimeZoneInputWithBc()
{
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'datetimezone']);
$form->submit('Europe/Saratov');
$this->assertEquals(new \DateTimeZone('Europe/Saratov'), $form->getData());
$this->assertContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
}
/**
* @group legacy
* @expectedDeprecation The option "regions" is deprecated since Symfony 4.2.
@ -76,4 +85,38 @@ class TimezoneTypeTest extends BaseTypeTest
$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Amsterdam'), $choices, '', false, false);
}
/**
* @requires extension intl
*/
public function testIntlTimeZoneInput()
{
$form = $this->factory->create(static::TESTED_TYPE, \IntlTimeZone::createTimeZone('America/New_York'), ['input' => 'intltimezone']);
$this->assertSame('America/New_York', $form->createView()->vars['value']);
$form->submit('Europe/Amsterdam');
$this->assertEquals(\IntlTimeZone::createTimeZone('Europe/Amsterdam'), $form->getData());
$form = $this->factory->create(static::TESTED_TYPE, [\IntlTimeZone::createTimeZone('America/New_York')], ['input' => 'intltimezone', 'multiple' => true]);
$this->assertSame(['America/New_York'], $form->createView()->vars['value']);
$form->submit(['Europe/Amsterdam', 'Europe/Paris']);
$this->assertEquals([\IntlTimeZone::createTimeZone('Europe/Amsterdam'), \IntlTimeZone::createTimeZone('Europe/Paris')], $form->getData());
}
/**
* @requires extension intl
*/
public function testIntlTimeZoneInputWithBc()
{
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'intltimezone']);
$form->submit('Europe/Saratov');
$this->assertNull($form->getData());
$this->assertNotContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
}
}