Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Form] Add input + regions options to TimezoneType #23648

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions 32 UPGRADE-3.4.md
Original file line number Diff line number Diff line change
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 29 UPGRADE-4.0.md
Original file line number Diff line number Diff line change
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 2 src/Symfony/Component/Form/CHANGELOG.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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 49 src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php
Original file line number Diff line number Diff line change
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']) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we name the option value DateTimeZone so people can use ['input' => \DateTimeZone::class]? This avoids typos and makes things more explicit as the value directly maps to a class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this suggestion and we could also apply it for other Date*Type having this input option.
But I'd keep datetimezone as alias for consistency, as I'm not convinced deprecating old option values for other types would be worth it and such aliases are handful too (for configuration based form generation like in EasyAdmin for instance).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so.. strtolower? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strtolowering the whole FQCN won't help you much ;)

Copy link
Contributor Author

@ro0NL ro0NL Aug 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i meant if ('datetimezone' === strtolower($options['input'])) {..

then again im not sure using class-ish values for input makes sense (compared to data_class). It kinda implies it supports any class name, which it doesnt.

$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',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something else is troubling me. With this new implementation can we still use this choice field as multiple? I guess we should handle an array of timezones too to be fully BC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Will look into that 👍

'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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 === count($timezones) ? $timezones : reset($timezones)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be weird.. :P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes sorry about that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we need an array all the time right? Why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We get a flat array (1 region) or a nested one (multiple regions). Let me explain with a picture :)

image

It's just cosmetic sugar. IMHO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright! Thanks for clarifying.

}
}
Original file line number Diff line number Diff line change
@@ -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'));
}
}
Original file line number Diff line number Diff line change
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);
}
}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.