-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
Conversation
$this->regions = $options['regions']; | ||
|
||
if ('datetimezone' === $options['input']) { | ||
$builder->addModelTransformer(new CallbackTransformer(function ($timezone) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #23652
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 ;).
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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']; |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing dot.
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 |
Updated so that |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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']); | ||
}); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing dot.
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 👍
There was a problem hiding this 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.'); |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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`. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
}
}
There was a problem hiding this comment.
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.
?
There was a problem hiding this comment.
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 inTimezoneType
. 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) } }
There was a problem hiding this comment.
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 ;)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 ;)
There was a problem hiding this comment.
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 :)
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
small conflict to fix |
*/ | ||
private $choiceList; | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function buildForm(FormBuilderInterface $builder, array $options) | ||
{ | ||
if ('datetimezone' === $options['input']) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so.. strtolower? :)
There was a problem hiding this comment.
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 ;)
There was a problem hiding this comment.
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.
Should be good :) |
Thanks @ro0NL. |
…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
…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
…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
I want to use
\DateTimeZone
as a model format, with only european timezones in the form available. Hence the new options.