diff --git a/UPGRADE-3.4.md b/UPGRADE-3.4.md index c5def31a96..33147c4cc9 100644 --- a/UPGRADE-3.4.md +++ b/UPGRADE-3.4.md @@ -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 --------------- diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index e5a69675a2..6c32bad70c 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -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 --------------- diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 8af1a324dc..62c52a897d 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -5,6 +5,8 @@ CHANGELOG ----- * added `DebugCommand` + * deprecated `ChoiceLoaderInterface` implementation in `TimezoneType` + * added options "input" and "regions" to `TimezoneType` 3.3.0 ----- diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php new file mode 100644 index 0000000000..7e133c2e9b --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeZoneToStringTransformer.php @@ -0,0 +1,82 @@ + + * + * 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 + */ +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); + } + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php index 196c7400bb..0a0ea56ef9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -13,7 +13,10 @@ namespace Symfony\Component\Form\Extension\Core\Type; 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; @@ -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} */ @@ -41,10 +56,20 @@ class TimezoneType extends AbstractType implements ChoiceLoaderInterface 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'); } /** @@ -65,21 +90,29 @@ class TimezoneType extends AbstractType implements ChoiceLoaderInterface /** * {@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)) { @@ -96,9 +129,13 @@ class TimezoneType extends AbstractType implements ChoiceLoaderInterface /** * {@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)) { @@ -116,13 +153,15 @@ class TimezoneType extends AbstractType implements ChoiceLoaderInterface /** * 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) { @@ -139,6 +178,6 @@ class TimezoneType extends AbstractType implements ChoiceLoaderInterface $timezones[$region][str_replace('_', ' ', $name)] = $timezone; } - return $timezones; + return 1 === count($timezones) ? reset($timezones) : $timezones; } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeZoneToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeZoneToStringTransformerTest.php new file mode 100644 index 0000000000..51442ecbf9 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeZoneToStringTransformerTest.php @@ -0,0 +1,56 @@ + + * + * 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')); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php index 8682efed69..51578bd6ad 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimezoneTypeTest.php @@ -33,4 +33,31 @@ class TimezoneTypeTest extends BaseTypeTest { 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); + } }