Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[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

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

merged 1 commit into from
Sep 16, 2017

Conversation

ro0NL
Copy link
Contributor

@ro0NL ro0NL commented Jul 24, 2017

Q A
Branch? 3.4
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? yes
Tests pass? yes
Fixed tickets #...
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.

$this->regions = $options['regions'];

if ('datetimezone' === $options['input']) {
$builder->addModelTransformer(new CallbackTransformer(function ($timezone) {
Copy link
Member

@yceruto yceruto Jul 24, 2017

Choose a reason for hiding this comment

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

I think creating an individual class for this model transformer it's the common implementation (i.e. into core) and both will be easier to maintain. CallbackTransformer it's commonly used in tests to simplify.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -72,7 +116,7 @@ public function loadChoiceList($value = null)
return $this->choiceList;
}

return $this->choiceList = new ArrayChoiceList($this->getTimezones(), $value);
return $this->choiceList = new ArrayChoiceList($this->getTimezones($this->regions), $value);
Copy link
Contributor

@ogizanagi ogizanagi Jul 24, 2017

Choose a reason for hiding this comment

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

This is an issue. The choice list is cached and reused. By doing this, you'll always get the same list when using the type multiple times (in a single form, nested forms or multiple forms) .

@HeahDude could probably give you more inputs than me for about this right now, because I think it was something he had to consider when working on #18334.
You should also avoid storing options in the form type's properties, as form types are instantiated once, it acts like a global state for the type and all its usages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right :) if we can solve it simply thats nice, otherwise lets leave it out. I can validate it additionally as well, yet it's nice to have.

Btw shouldnt this be self::getTimezones()?

Copy link
Contributor

Choose a reason for hiding this comment

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

Btw shouldnt this be self::getTimezones()?

Right :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See #23652

Copy link
Member

@yceruto yceruto Jul 24, 2017

Choose a reason for hiding this comment

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

You should also avoid storing options in the form type's properties, as form types are instantiated once, it acts like a global state for the type and all its usages.

An option could be return a new default choice loader instance instead of the current one return $this;? e.g.:

'choice_loader' => function (Options $options) {
    // ...

    return new CallbackChoiceLoader(function () use ($options) {
        return self::getTimezones($options['region']);
    });
},

But also, it involves remove the actual ChoiceLoaderInterface implementation and maybe I'm missing something...

Copy link
Contributor

Choose a reason for hiding this comment

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

@yceruto is right, we now need a dynamic TimezonesChoiceLoader implementation taking the option in its constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@HeahDude what about the ChoiceLoaderInterface implem. Can we simply drop it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just moved I'd say ;).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It basically has not purpose anymore.. not sure we should preserve its api in any way (i.e. forward to the callback loader or so).

@@ -72,7 +116,7 @@ public function loadChoiceList($value = null)
return $this->choiceList;
}

return $this->choiceList = new ArrayChoiceList($this->getTimezones(), $value);
return $this->choiceList = new ArrayChoiceList($this->getTimezones($this->regions), $value);
Copy link
Contributor

Choose a reason for hiding this comment

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

@yceruto is right, we now need a dynamic TimezonesChoiceLoader implementation taking the option in its constructor.

$this->regions = $options['regions'];

if ('datetimezone' === $options['input']) {
$builder->addModelTransformer(new CallbackTransformer(function ($timezone) {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->regions = $options['regions'];
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be removed.

try {
return new \DateTimeZone($value);
} catch (\Exception $e) {
throw new TransformationFailedException('Expected a known timezone identifier');
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing dot.

@ro0NL
Copy link
Contributor Author

ro0NL commented Jul 25, 2017

Not sure, but we might consider favoring datetimezone as default input format; so it's consistent with datetime type.

Also i need the next step basically; integrate timezone type with datetime. A with_timezone option or so.. that would save me a few steps :)

@ro0NL
Copy link
Contributor Author

ro0NL commented Jul 25, 2017

Updated so that ChoiceLoaderInterface implem is deprecated and uses a CallbackLoader directly. Let me know if this is OK.

Copy link
Contributor

@HeahDude HeahDude left a comment

Choose a reason for hiding this comment

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

Looks good!

use Symfony\Component\Form\Exception\TransformationFailedException;

/**
* Transforms between a date string and a DateTimeZone object.
Copy link
Contributor

Choose a reason for hiding this comment

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

"between a timezone identifier string and a DateTimeZone object"

return $this;
return new CallbackChoiceLoader(function () use ($options) {
return self::getTimezones($options['regions']);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I would add $regions = $options['regions'] and pass that only variable to the use statement.

@@ -139,6 +170,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.

3.4.0
-----

* deprecated `ChoiceLoaderInterface` implementation in `TimezoneType`
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing dot.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well.. it's consistent with the current format :)

Shall add the new options here as well 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

right and not where I commented.

UPGRADE-3.4.md Outdated
Form
----

* Deprecated `ChoiceLoaderInterface` implementation in `TimezoneType`.
Copy link
Contributor

Choose a reason for hiding this comment

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

You should also document the new options here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

New options are just new features, so not required for any upgrade step. I tend to doc them in Form/CHANGELOG.

},
'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 👍

Copy link
Contributor

@ogizanagi ogizanagi left a comment

Choose a reason for hiding this comment

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

It would be great to also test the transformer separately :)

try {
return new \DateTimeZone($value);
} catch (\Exception $e) {
throw new TransformationFailedException('Expected a known timezone identifier.');
Copy link
Contributor

Choose a reason for hiding this comment

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

- throw new TransformationFailedException('Expected a known timezone identifier.');
+ throw new TransformationFailedException('Expected a known timezone identifier.', $e->getCode(), $e);

(same for every exception caught IMHO. Eventually, you don't even need to set a custom message, but just re-use $e->getMessage() 😄 . See DateIntervalToArrayTransformer for instance)

}

return array_map(function ($value) {
if (!is_string($value)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You could avoid duplicating this logic below by adding using a private method... or either by reusing a DateTimeZoneToStringTransformer without the $multiple flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah.. wasnt sure :P resuing new self sounds nice.

Copy link
Contributor

@ogizanagi ogizanagi left a comment

Choose a reason for hiding this comment

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

Nice :)

Failure unrelated.

UPGRADE-4.0.md Outdated
@@ -228,6 +228,8 @@ Form
));
```

* Removed `ChoiceLoaderInterface` implementation in `TimezoneType`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we need this in the UPGRADE.md file. Unless there is a new code path to show.

Copy link
Contributor

@HeahDude HeahDude Jul 25, 2017

Choose a reason for hiding this comment

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

The upgrade path is:

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

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

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

Copy link
Contributor Author

@ro0NL ro0NL Jul 25, 2017

Choose a reason for hiding this comment

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

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

?

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 both. Let's do both? 😄

  • Removed ChoiceLoaderInterface implementation in TimezoneType. Use the "choice_loader" option instead:

    Before:

    class MyTimezoneType extends TimezoneType
    {
        public function loadChoices()
        {
            // override the method
        }
    }

    After:

    class MyTimezoneType extends AbstractType
    {
        public function. getParent()
        {
            return TimezoneType::class;
        }
    
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefault('choice_loader', ...); // override the option instead)
        }
    }

Copy link
Member

Choose a reason for hiding this comment

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

+1 for Before/After code also ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After would be

class MyTimezoneType extends AbstractType implements ChoiceLoaderInterface
// ...
$resolver->setDefault('choice_loader', $this);

right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Not necessarily. I think @HeahDude 's suggestion is enough to understand.

Copy link
Contributor Author

@ro0NL ro0NL Jul 26, 2017

Choose a reason for hiding this comment

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

btw, while at it, is it worth to do this deprecation for other types? LanguageType and co. could do the same approach =/

It seems a way simpler implem. (CallbackChoiceLoader) compared to ArrayChoiceLoader.

}

return array_map(function ($dateTimeZone) {
return (new self())->transform($dateTimeZone);
Copy link
Contributor

Choose a reason for hiding this comment

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

An object is constructed at each iteration :/. What about:

$transformer = new self();

return array_map(function ($dateTimeZone) use ($transformer) {
    return $transformer->transform($dateTimeZone);
// ...


return array_map(function ($dateTimeZone) use ($transformer) {
return $transformer->transform($dateTimeZone);
}, $dateTimeZone);
Copy link
Member

@yceruto yceruto Jul 25, 2017

Choose a reason for hiding this comment

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

Minor simplification:

- $transformer = new self();

- return array_map(function ($dateTimeZone) use ($transformer) {
-     return $transformer->transform($dateTimeZone);
- }, $dateTimeZone);
+ return array_map(array(new self(), 'transform'), $dateTimeZone);

However, the actual is fine to me ;)

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 update? Kinda like the one-liner :)

ogizanagi added a commit that referenced this pull request Jul 25, 2017
This PR was merged into the 3.2 branch.

Discussion
----------

[Form] Static call TimezoneType::getTimezones

| Q             | A
| ------------- | ---
| Branch?       | 3.2
| Bug fix?      | no
| New feature?  | no <!-- don't forget updating src/**/CHANGELOG.md files -->
| BC breaks?    | no
| Deprecations? | no <!-- don't forget updating UPGRADE-*.md files -->
| Tests pass?   | yes
| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!--highly recommended for new features-->

Spotted in #23648

Commits
-------

fe48ab1 [Form] Static call TimezoneType::getTimezones
@nicolas-grekas
Copy link
Member

small conflict to fix

*/
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.

@ro0NL
Copy link
Contributor Author

ro0NL commented Sep 16, 2017

Should be good :)

@ogizanagi
Copy link
Contributor

Thanks @ro0NL.

@ogizanagi ogizanagi merged commit 183307b into symfony:3.4 Sep 16, 2017
ogizanagi added a commit that referenced this pull request Sep 16, 2017
…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
@ro0NL ro0NL deleted the form/timezone branch September 16, 2017 10:53
ogizanagi added a commit that referenced this pull request Sep 17, 2017
…ntation in TimezoneType (ogizanagi)

This PR was merged into the 4.0-dev branch.

Discussion
----------

[Form] Remove deprecated ChoiceLoaderInterface implementation in TimezoneType

| Q             | A
| ------------- | ---
| Branch?       | master <!-- see comment below -->
| Bug fix?      | no
| New feature?  | yes <!-- don't forget updating src/**/CHANGELOG.md files -->
| BC breaks?    | yes
| Deprecations? | no <!-- don't forget updating UPGRADE-*.md files -->
| Tests pass?   | yes
| Fixed tickets | #23648 <!-- #-prefixed issue number(s), if any -->
| License       | MIT
| Doc PR        | N/A

Commits
-------

47993a8 [Form] Remove deprecated ChoiceLoaderInterface implementation in TimezoneType
This was referenced Oct 18, 2017
javiereguiluz added a commit to symfony/symfony-docs that referenced this pull request Jan 3, 2018
…L, javiereguiluz)

This PR was merged into the 3.4 branch.

Discussion
----------

Documentation input+regions options in TimezoneType

See symfony/symfony#23648

Commits
-------

99f5f3e Minor typo
d6611eb Update timezone.rst
de490ca Update timezone.rst
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants