Skip to content

Commit

Permalink
feature #31195 [Form] Add intltimezone input to TimezoneType (ro0NL)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 4.3-dev branch (closes #31195).

Discussion
----------

[Form] Add intltimezone input to TimezoneType

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | no
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | #22302 (ref #28836)
| License       | MIT
| Doc PR        | symfony/symfony-docs#11463

cc @rvanlaak

Commits
-------

e169dfb [Form] Add intltimezone input to TimezoneType
  • Loading branch information
fabpot committed Apr 27, 2019
2 parents 1725a3c + e169dfb commit 73d303a
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
24 changes: 20 additions & 4 deletions src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@

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;
Expand All @@ -27,6 +29,8 @@ public function buildForm(FormBuilderInterface $builder, array $options)
{
if ('datetimezone' === $options['input']) {
$builder->addModelTransformer(new DateTimeZoneToStringTransformer($options['multiple']));
} elseif ('intltimezone' === $options['input']) {
$builder->addModelTransformer(new IntlTimeZoneToStringTransformer($options['multiple']));
}
}

Expand All @@ -38,17 +42,25 @@ public function configureOptions(OptionsResolver $resolver)
$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,
'input' => 'string',
'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.');
Expand All @@ -73,11 +85,15 @@ public function getBlockPrefix()
/**
* 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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']);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ public function testDateTimeZoneInput()
$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.
Expand All @@ -76,4 +85,38 @@ public function testFilterByRegions()

$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());
}
}

0 comments on commit 73d303a

Please sign in to comment.