Skip to content

Commit

Permalink
feature #31294 [Form] Add intl/choice_translation_locale option to Ti…
Browse files Browse the repository at this point in the history
…mezoneType (ro0NL)

This PR was merged into the 4.3 branch.

Discussion
----------

[Form] Add intl/choice_translation_locale option 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 | #28836
| License       | MIT
| Doc PR        | symfony/symfony-docs#11503

final step :)

for now i think any form of grouping is a user concern (i.e. by GMT offset or area name); see #31293 + #31295

having a special built in `group_by' => 'gmt_offset'` util would be nice, and can be done in the future.

includes #31434

Commits
-------

001b930 [Form] Add intl/choice_translation_locale option to TimezoneType
  • Loading branch information
fabpot committed May 13, 2019
2 parents 3a17701 + 001b930 commit a326acc
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 6 deletions.
55 changes: 49 additions & 6 deletions src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
Expand Up @@ -13,10 +13,12 @@

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader;
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\Intl\Timezones;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

Expand All @@ -40,19 +42,41 @@ public function buildForm(FormBuilderInterface $builder, array $options)
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'intl' => false,
'choice_loader' => function (Options $options) {
$regions = $options->offsetGet('regions', false);
$input = $options['input'];

if ($options['intl']) {
$choiceTranslationLocale = $options['choice_translation_locale'];

return new IntlCallbackChoiceLoader(function () use ($input, $choiceTranslationLocale) {
return self::getIntlTimezones($input, $choiceTranslationLocale);
});
}

$regions = $options->offsetGet('regions', false);

return new CallbackChoiceLoader(function () use ($regions, $input) {
return self::getTimezones($regions, $input);
return self::getPhpTimezones($regions, $input);
});
},
'choice_translation_domain' => false,
'choice_translation_locale' => null,
'input' => 'string',
'regions' => \DateTimeZone::ALL,
]);

$resolver->setAllowedTypes('intl', ['bool']);

$resolver->setAllowedTypes('choice_translation_locale', ['null', 'string']);
$resolver->setNormalizer('choice_translation_locale', function (Options $options, $value) {
if (null !== $value && !$options['intl']) {
throw new LogicException('The "choice_translation_locale" option can only be used if the "intl" option is set to true.');
}

return $value;
});

$resolver->setAllowedValues('input', ['string', 'datetimezone', 'intltimezone']);
$resolver->setNormalizer('input', function (Options $options, $value) {
if ('intltimezone' === $value && !class_exists(\IntlTimeZone::class)) {
Expand All @@ -64,6 +88,13 @@ public function configureOptions(OptionsResolver $resolver)

$resolver->setAllowedTypes('regions', 'int');
$resolver->setDeprecated('regions', 'The option "%name%" is deprecated since Symfony 4.2.');
$resolver->setNormalizer('regions', function (Options $options, $value) {
if ($options['intl'] && \DateTimeZone::ALL !== (\DateTimeZone::ALL & $value)) {
throw new LogicException('The "regions" option can only be used if the "intl" option is set to false.');
}

return $value;
});
}

/**
Expand All @@ -82,10 +113,7 @@ public function getBlockPrefix()
return 'timezone';
}

/**
* Returns a normalized array of timezone choices.
*/
private static function getTimezones(int $regions, string $input): array
private static function getPhpTimezones(int $regions, string $input): array
{
$timezones = [];

Expand All @@ -99,4 +127,19 @@ private static function getTimezones(int $regions, string $input): array

return $timezones;
}

private static function getIntlTimezones(string $input, string $locale = null): array
{
$timezones = array_flip(Timezones::getNames($locale));

if ('intltimezone' === $input) {
foreach ($timezones as $name => $timezone) {
if ('Etc/Unknown' === \IntlTimeZone::createTimeZone($timezone)->getID()) {
unset($timezones[$name]);
}
}
}

return $timezones;
}
}
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Form\Tests\Extension\Core\Type;

use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Intl\Util\IntlTestHelper;

class TimezoneTypeTest extends BaseTypeTest
{
Expand Down Expand Up @@ -83,6 +84,17 @@ public function testFilterByRegions()
$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Europe / Amsterdam'), $choices, '', false, false);
}

/**
* @group legacy
* @expectedDeprecation The option "regions" is deprecated since Symfony 4.2.
* @expectedException \Symfony\Component\Form\Exception\LogicException
* @expectedExceptionMessage The "regions" option can only be used if the "intl" option is set to false.
*/
public function testFilterByRegionsWithIntl()
{
$this->factory->create(static::TESTED_TYPE, null, ['regions' => \DateTimeZone::EUROPE, 'intl' => true]);
}

/**
* @requires extension intl
*/
Expand Down Expand Up @@ -116,4 +128,54 @@ public function testIntlTimeZoneInputWithBc()
$this->assertNull($form->getData());
$this->assertNotContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
}

/**
* @requires extension intl
*/
public function testIntlTimeZoneInputWithBcAndIntl()
{
$form = $this->factory->create(static::TESTED_TYPE, null, ['input' => 'intltimezone', 'intl' => true]);
$form->submit('Europe/Saratov');

$this->assertNull($form->getData());
$this->assertNotContains('Europe/Saratov', $form->getConfig()->getAttribute('choice_list')->getValues());
}

public function testTimezonesAreSelectableWithIntl()
{
IntlTestHelper::requireIntl($this, false);

$choices = $this->factory->create(static::TESTED_TYPE, null, ['intl' => true])
->createView()->vars['choices'];

$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Central European Time (Amsterdam)'), $choices, '', false, false);
$this->assertContains(new ChoiceView('Etc/UTC', 'Etc/UTC', 'Coordinated Universal Time'), $choices, '', false, false);
}

/**
* @requires extension intl
*/
public function testChoiceTranslationLocaleOptionWithIntl()
{
$choices = $this->factory
->create(static::TESTED_TYPE, null, [
'intl' => true,
'choice_translation_locale' => 'uk',
])
->createView()->vars['choices'];

$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'за центральноєвропейським часом (Амстердам)'), $choices, '', false, false);
$this->assertContains(new ChoiceView('Etc/UTC', 'Etc/UTC', 'за всесвітнім координованим часом'), $choices, '', false, false);
}

/**
* @expectedException \Symfony\Component\Form\Exception\LogicException
* @expectedExceptionMessage The "choice_translation_locale" option can only be used if the "intl" option is set to true.
*/
public function testChoiceTranslationLocaleOptionWithoutIntl()
{
$this->factory->create(static::TESTED_TYPE, null, [
'choice_translation_locale' => 'uk',
]);
}
}

0 comments on commit a326acc

Please sign in to comment.