Skip to content

Commit

Permalink
feature #23648 [Form] Add input + regions options to TimezoneType (ro…
Browse files Browse the repository at this point in the history
…0NL)

This PR was merged into the 3.4 branch.

Discussion
----------

[Form] Add input  + regions options to TimezoneType

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | symfony/symfony-docs#8223

I want to use `\DateTimeZone` as a model format, with only european timezones in the form available. Hence the new options.

Commits
-------

183307b [Form] Add input  + regions options to TimezoneType
  • Loading branch information
ogizanagi committed Sep 16, 2017
2 parents 40a3466 + 183307b commit 9208244
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 5 deletions.
32 changes: 32 additions & 0 deletions UPGRADE-3.4.md
Expand Up @@ -74,6 +74,38 @@ Finder
deprecated and will be removed in 4.0 as it used to fix a bug which existed
before version 5.5.23/5.6.7.

Form
----

* Deprecated `ChoiceLoaderInterface` implementation in `TimezoneType`. Use the "choice_loader" option instead.

Before:
```php
class MyTimezoneType extends TimezoneType
{
public function loadChoices()
{
// override the method
}
}
```

After:
```php
class MyTimezoneType extends AbstractType
{
public function. getParent()
{
return TimezoneType::class;
}

public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('choice_loader', ...); // override the option instead
}
}
```

FrameworkBundle
---------------

Expand Down
29 changes: 29 additions & 0 deletions UPGRADE-4.0.md
Expand Up @@ -282,6 +282,35 @@ Form
));
```

* Removed `ChoiceLoaderInterface` implementation in `TimezoneType`. Use the "choice_loader" option instead.

Before:
```php
class MyTimezoneType extends TimezoneType
{
public function loadChoices()
{
// override the method
}
}
```

After:
```php
class MyTimezoneType extends AbstractType
{
public function. getParent()
{
return TimezoneType::class;
}

public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('choice_loader', ...); // override the option instead
}
}
```

FrameworkBundle
---------------

Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Component/Form/CHANGELOG.md
Expand Up @@ -5,6 +5,8 @@ CHANGELOG
-----

* added `DebugCommand`
* deprecated `ChoiceLoaderInterface` implementation in `TimezoneType`
* added options "input" and "regions" to `TimezoneType`

3.3.0
-----
Expand Down
@@ -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);
}
}
}
49 changes: 44 additions & 5 deletions src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
Expand Up @@ -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;

Expand All @@ -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}
*/
Expand All @@ -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',
'regions' => \DateTimeZone::ALL,
));

$resolver->setAllowedValues('input', array('string', 'datetimezone'));

$resolver->setAllowedTypes('regions', 'int');
}

/**
Expand All @@ -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)) {
Expand All @@ -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)) {
Expand All @@ -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) {
Expand All @@ -139,6 +178,6 @@ private static function getTimezones()
$timezones[$region][str_replace('_', ' ', $name)] = $timezone;
}

return $timezones;
return 1 === count($timezones) ? reset($timezones) : $timezones;
}
}
@@ -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'));
}
}
Expand Up @@ -33,4 +33,31 @@ public function testSubmitNull($expected = null, $norm = null, $view = null)
{
parent::testSubmitNull($expected, $norm, '');
}

public function testDateTimeZoneInput()
{
$form = $this->factory->create(static::TESTED_TYPE, new \DateTimeZone('America/New_York'), array('input' => 'datetimezone'));

$this->assertSame('America/New_York', $form->createView()->vars['value']);

$form->submit('Europe/Amsterdam');

$this->assertEquals(new \DateTimeZone('Europe/Amsterdam'), $form->getData());

$form = $this->factory->create(static::TESTED_TYPE, array(new \DateTimeZone('America/New_York')), array('input' => 'datetimezone', 'multiple' => true));

$this->assertSame(array('America/New_York'), $form->createView()->vars['value']);

$form->submit(array('Europe/Amsterdam', 'Europe/Paris'));

$this->assertEquals(array(new \DateTimeZone('Europe/Amsterdam'), new \DateTimeZone('Europe/Paris')), $form->getData());
}

public function testFilterByRegions()
{
$choices = $this->factory->create(static::TESTED_TYPE, null, array('regions' => \DateTimeZone::EUROPE))
->createView()->vars['choices'];

$this->assertContains(new ChoiceView('Europe/Amsterdam', 'Europe/Amsterdam', 'Amsterdam'), $choices, '', false, false);
}
}

0 comments on commit 9208244

Please sign in to comment.