-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[OptionsResolver] Merged Options class into OptionsResolver #12156
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
I'm not really happy seeing this being static, what's the argument against $options->resolve($defaults)? |
@iltar having to instantiate a resolver to resolver simple defaults makes it much slower, and also more complex to use. The static call is an enhanced version of And as you can see, the usage of an Options instance allows to provide extension points for the definition of the defaults (which is what the component aims to solve for forms) |
@stof in both examples provided, the argument passed to // Basic option resolving
$options = $options->resolve(array(
'choices' = array(),
));
// More advanced option configuration
$defaults = new Options();
$defaults['choices'] = array();
$defaults->setRequired('em');
$options = $options->resolve($defaults);
// Form component example (3.0)
public function setDefaultOptions(Options $options) {
$options['choices'] = array();
$options->setRequired('em');
} |
@iltar the basic usage in @webmozart's comment does not require you to build an object. And |
@stof so if I understand it correctly, the idea is to also allow an array. In that case, this is a good solution. I'm still not in favor of static methods in my code, but I supposed this will be behind the scenes. What will be the case when someone implements their own Options implementation? |
So does this also make it possible to use nested options as described in #4833? :) |
I finished the PR now. |
I'm currently adapting the PR so that we can close #10616. I'm also preparing the implementation of #4833 in 2.7+ without breaking BC. I need some feedback to complete that. In 2.6, I changed The simple syntax without any objects, designed for high performance ( public function __construct(array $options = array())
{
Options::validateRequired($options, 'em');
Options::validateTypes($options, array(
'em' => 'Doctrine\ORM\EntityManager',
));
$this->options = Options::resolve($options, array(
'class' => null,
'query_builder' => null,
));
} And the object-based syntax with the flexibility that we know of the OptionsResolver (lazy options, normalization...): public function __construct(array $options = array())
{
$defaults = new Options();
$defaults['class'] = null;
$defaults['query_builder'] = null;
$defaults->setRequired('em');
$defaults->setAllowedTypes(array(
'em' => 'Doctrine\ORM\EntityManager',
));
$this->options = Options::resolve($options, $defaults);
} My question to you is: Would any of you actually use the first mentioned, static API? Or should we remove it and make public function __construct(array $options = array())
{
$defaults = new Options();
// ...
$this->options = $defaults->resolve($options);
} |
The static method syntax was introduced in 2.6 so it can be removed now (or at any time before the first RC version.) |
By the way, I'm all for removing the static API. |
👍 for removing static API |
same, +1 for removing static api |
Less is more, and I'm usually against static. I would definitely use the last example. |
Good, thanks for the feedback! |
891b387
to
c9b0471
Compare
I updated the PR and the above description now. |
static::validateNames($options, $defaults->options, true); | ||
// If an option is a closure that should be evaluated lazily, store it | ||
// in the "lazy" property. | ||
if (is_callable($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.
I see issue here:
php -r "var_dump(is_callable('array_map'));"
// bool(true)
Besides it is not possible to have closure as an option.
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 callable/closure set to the option is not evaluated if its first parameter is not type hinted with Options
. Look inside the if statement.
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.
Oops, I missed that. Sorry for noise.
The documentation is adapted now: https://github.com/webmozart/symfony-docs/blob/issue11705/components/options_resolver.rst |
With the new methods introduced, it would probably be a good time to fix #10585. So its not wrong again. |
Also what I always find irritating is the magic of defining lazy options.
and
which is non-obvious. Maybe there should be different methods for defining plain options and lazy avaluated options? |
Also I think Options:resolve should not clone. The OptionsResolver should only clone the options when calling resolve. The semantics are different. It makes sense that one can use one resolver to call resolve multiple times. But for options it makes things so conplicated and confusing. E.g. it's not possible to call the following which is strange from a users perspective. Or am I missing something?
|
@Tobion The "problem" about using a separate method for lazy options is that it then isn't obvious anymore how $options['foo'] = 'bar';
$options->setLazy('foo', function (Options $options) {
return 'dynamic';
});
$options['foo'] = 'baz'; I personally don't find it very clear what the value of the 'foo' option is at the different points in time. With: $options['foo'] = 'bar';
$options['foo'] = function (Options $options) {
return 'dynamic';
};
$options['foo'] = 'baz'; it's clear that we are always performing the same set operation. Obviously I'm biased here. About $options = new Options();
// ...
$resolvedOptions = $options->resolve($passedOptions);
echo $resolvedOptions['foo']; From the user POV, it seems confusing to me if I'll look into fixing #10585 tomorrow, thanks for mentioning that. |
Why should it be a bc break if you do |
It basically forbids these use-cases. But I'm not sure if there are use-cases for options that depend on the definition. |
OptionsResolver is a low-level utility which is instantiated in the same class that it is used in most of the time. Interfaces, on the other hand, are contracts helping different classes/components to communicate. What's the point in having an interface for a class which is not supposed to be shared?
That's good, because these methods are deprecated. The methods can be called, but they shouldn't.
That's a possibility. However this also makes the definition of lazy options unnecessarily longer (and that definition is the sole purpose of the interface's existence): $resolver->setDefault('choices', function (OptionsInterface $options) {
}); I'd rather make Options an empty class if you insist on the "Interface" suffix. |
ec3e26f
to
f1db155
Compare
*/ | ||
public function setOptional(array $optionNames) | ||
{ | ||
$this->setDefined($optionNames); |
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.
return $this->setDefined($optionNames);
?
686a170
to
fb9e7e0
Compare
The PR description, documentation, CHANGELOG and UPGRADE-2.6 files are up to date now. |
$options = $resolver->resolve($options); | ||
``` | ||
|
||
Before: |
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
Please just merge it before it gets reworked again ;) |
Apart from this #12156 (comment) typo 👍 |
👍 |
…r (webmozart) This PR was merged into the 2.6-dev branch. Discussion ---------- [OptionsResolver] Merged Options class into OptionsResolver | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | yes | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #4500, #9174, #10585, #10202, #11020, makes #7979+#10616 obsolete | License | MIT | Doc PR | symfony/symfony-docs#4159 #11716 was reverted as preparation of this PR (453882c). The Options class was merged into OptionsResolver. This made it possible to fix several tickets, as indicated above. **Usage** See [the updated documentation](https://github.com/webmozart/symfony-docs/blob/issue11705/components/options_resolver.rst). **Bug Fixes** Previously, the options weren't validated before the normalizers were called. As a result, the normalizers had to perform validation again: ```php $resolver->setAllowedTypes(array( 'choices' => 'array', )); $resolver->setNormalizers(array( 'choices' => function (Options $options, $value) { array_merge($options['choices'], ...); }, )); // fatal error $resolver->resolve(array('choices' => 'foobar')); ``` This is fixed now. **BC Breaks** The `array` type hint was removed from `setRequired()`, `setAllowedValues()`, `addAllowedValues()`, `setAllowedTypes()` and `addAllowedTypes()`. If anybody implemented `OptionsResolverInterface`, they must adapt their code. The Options class was turned into an interface extending ArrayAccess and Countable. Anybody instantiating Options directly should instantiate OptionsResolver instead. Anybody using any of the methods available in Options (`get()`, `has()`) should use the ArrayAccess interface instead. Normalizers are not called anymore for undefined options (#9174). People need to set a default value if they want a normalizer to be executed independent of the options passed to `resolve()`. **Deprecations** OptionsResolverInterface was deprecated and will be removed in 3.0. OptionsResolver instances are not supposed to be shared between classes, hence an interface does not make sense. Several other methods were deprecated. See the CHANGELOG and UPGRADE-2.6 files for information. **Todo** - [x] Fix PHPDoc - [x] Adapt CHANGELOG/UPGRADE - [x] Adapt documentation - [x] Deprecate OptionsResolver[Interface] Commits ------- 642c119 [OptionsResolver] Merged Options class into OptionsResolver
Just a small detail but, what will happen with Based on the PR I noticed that it should be 100% BC for forms, but I think this should be well documented as BC break for 3.0. |
Yes. |
…umentation to describe the 2.6 API (webmozart, peterrehm) This PR was merged into the master branch. Discussion ---------- [WCM][OptionsResolver] Adjusted the OptionsResolver documentation to describe the 2.6 API | Q | A | ------------- | --- | Doc fix? | no | New docs? | yes (symfony/symfony#11716, symfony/symfony#12156) | Applies to | 2.6+ | Fixed tickets | - Commits ------- 575c320 Updated OptionsResolver documentation: removed static methods fab825d Merge pull request #1 from peterrehm/patch-9 f202fb8 Removed duplicate use statements ae66893 Added missing mailer class and use statements 43ae1d8 [OptionsResolver] Adjusted the OptionsResolver documentation to describe the 2.6 API
`OptionsResolverInterface` has been deprecated in Symfony 2.6 in favor of `OptionsResolver` (see symfony/symfony#12156)
@webmozart Form types are a good example where you pass the option resolver around to other classes, to configure it. So maybe we should not deprecate the interface ? |
The Options class was merged into OptionsResolver. This made it possible to fix several tickets, as indicated above.
Usage
See the updated documentation.
Bug Fixes
Previously, the options weren't validated before the normalizers were called. As a result, the normalizers had to perform validation again:
This is fixed now.
BC Breaks
The
array
type hint was removed fromsetRequired()
,setAllowedValues()
,addAllowedValues()
,setAllowedTypes()
andaddAllowedTypes()
. If anybody implementedOptionsResolverInterface
, they must adapt their code.The Options class was turned into an interface extending ArrayAccess and Countable. Anybody instantiating Options directly should instantiate OptionsResolver instead. Anybody using any of the methods available in Options (
get()
,has()
) should use the ArrayAccess interface instead.Normalizers are not called anymore for undefined options (#9174). People need to set a default value if they want a normalizer to be executed independent of the options passed to
resolve()
.Deprecations
OptionsResolverInterface was deprecated and will be removed in 3.0. OptionsResolver instances are not supposed to be shared between classes, hence an interface does not make sense.
Several other methods were deprecated. See the CHANGELOG and UPGRADE-2.6 files for information.
Todo