Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[WIP] [OptionsResolver] handle nested options#18134
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
[WIP] [OptionsResolver] handle nested options#18134
Uh oh!
There was an error while loading.Please reload this page.
Conversation
ad7cce8 tob486c62Compare92e1f61 to49588c6Compare| } | ||
| if ($this->isNested($option)) { | ||
| @trigger_error(sprintf('The "%s" method should not be used with nested options. A failed attempt occurred with the option "%" for values %s',__METHOD__,$option,$this->formatValues($allowedValues)),E_USER_WARNING); |
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 doesn't seem to be a deprecation notice, so exceptions should be used, 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.
That what I proposed in mycomment in the concerned commit.
I'm not sure how strict we should be here, this is just a proposition.
dd55fe5 to211a4d9Compareclosessymfony#4833.Allow to resolve nested options. * add `OptionsResolver::nested` * add `OptionsResolver::setNested()` * add `OptionsResolver::isNested()` * add `OptionsResolver::getNested()`
211a4d9 toe1aa1b6Compareclosessymfony#15524.Set allowed types for many options or for a set of nestedor extra options. * add `Options::NONE` * add `Options::ALL` * add `Options::DEFINED` * add `Options::NESTED` * add `Options::EXTRA` * add `OptionsResolver::allowedValuesForAll` * add `OptionsResolver::allowedTypesForAll` * add `OptionsResolver::resolveUndefined` * add `OptionsResolver::addAllowedValuesForAll()` * add `OptionsResolver::addAllowedTypesForAll()` * add `OptionsResolver::setPrototype()`
e1aa1b6 toccd5ce1CompareHeahDude commentedMar 14, 2016
Pushedb51cce4 to pass the parent options to each nested option normalizer. |
e8f1172 tob51cce4Comparebackbone87 commentedMar 22, 2016
Just an alternative approach for this feature, without caring about maintaining a tree:
|
backbone87 commentedMar 22, 2016
Just some more thoughts: Your approach of maintaining a tree makes the |
HeahDude commentedMar 22, 2016
@backbone87 thanks for the feedback.
It's not a bad idea. But for now I'd rather try to handle nested or extra options without adding too much overhead to the current behavior.
I don't agree with you on that, it just makes it more powerful IMHO, ref#3968. I've worked on that PR but haven't push any more updates for now, I'm waiting for more reviews on the current state. |
backbone87 commentedMar 22, 2016
Well that discussion was 4 years ago and the OptionsResolver grew quite a bit since thenand the requested features push it more and more towards the feature set of the Config component |
HeahDude commentedMar 22, 2016
But they still do not share goal or responsibility: one for loading resources and defining merging rules for domain configuration and the other for resolving class options even through inheritance. |
backbone87 commentedMar 22, 2016
"option resolution through inheritance" is a specific "defined merging rule" The main benefit of the The point of view is cleary personal preference, so like you already said: lets hear some other opinions on this topic. |
HeahDude commentedMar 22, 2016
I would just say I see a nuance between "domain configuration" and "component or classes configuration" which may not require external resource loading but internal definition. |
webmozart commentedMar 30, 2016
Thank you for working on this@HeahDude! I'm a bit uncomfortable about adding this functionality. First of all, nested options - so far - were not asked for by many people. Second, adding support for nested optionswill have a performance impact. However - just like PropertyAccess - OptionsResolver is a very low-level but frequently called component that impacts the performance of forms. We should avoid decreasing its performance for no benefit (for the majority of users). |
HeahDude commentedMar 30, 2016
@webmozart thanks for the review. It was my self challenge for the hack day. It looks more like a PoC/RFC as is, and would require a nice lifting and lot of test to be safely merged right now. I actually thought about the light weight aspect, but the idea is also to help some core form types. Since it remains a specific new method call, it should not impact performance that much while being flexible for specific use cases, Also it may be more safe to allow "locked" options as tried in#17161 since some options should never been overridden in some form type extensions or children in some core form types. I guess we have the time to polish it for later and make a decision when it's properly implemented. |
HeahDude commentedApr 9, 2016
Status: Needs Work |
senkal commentedAug 2, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
We have an use case where we need this functionality. |
fabpot commentedJul 6, 2017
@HeahDude Any news on this one? Still wanting to work on it? |
HeahDude commentedJul 6, 2017
Yes I think it's needed to support this case IMHO even in the core. I will start it again from scratch to get something simpler and be careful to not add too much overhead in the meantime if someone wants to give it a try, please do. Closing for now. |
…finition (yceruto)This PR was merged into the 4.2-dev branch.Discussion----------[OptionsResolver] Added support for nesting options definition| Q | A| ------------- | ---| Branch? | master| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | no| Tests pass? | yes| Fixed tickets |#4833| License | MIT| Doc PR |symfony/symfony-docs#9995I'd like to propose an alternative to#27251 and#18134 with a different approach.It would allow you to create a nested options system with required options, validation (type, value),normalization and more.<details> <summary><strong>Short documentation</strong></summary>**To define a nested option, you can pass a closure as the default value of the option with an `OptionsResolver` argument:**```php$resolver ->defaults([ 'connection' => 'default', 'database' => function (OptionsResolver $resolver) { $resolver ->setRequired(['dbname', 'host', ...]) ->setDefaults([ 'driver' => 'pdo_sqlite', 'port' => function (Options $options) { return 'pdo_mysql' === $options['driver'] ? 3306 : null, }, 'logging' => true, ]) ->setAllowedValues('driver', ['pdo_sqlite', 'pdo_mysql']) ->setAllowedTypes('port', 'int') ->setAllowedTypes('logging', 'bool') // ... }, ]);$resolver->resolve(array( 'database' => array( 'dbname' => 'demo', 'host' => 'localhost', 'driver' => 'pdo_mysql', ),));// returns: array(// 'connection' => 'default',// 'database' => array(// 'dbname' => 'demo',// 'host' => 'localhost',// 'driver' => 'pdo_mysql',// 'port' => 3306,// 'logging' => true,// ),//)```Based on this instance, you can define the options under ``database`` and its desired defaultvalue.**If the default value of a child option depend on another option defined in parent level,adds a second ``Options`` argument to the closure:**```php$resolver ->defaults([ 'profiling' => false, 'database' => function (OptionsResolver $resolver, Options $parent) { $resolver ->setDefault('logging', $parent['profiling']) ->setAllowedTypes('logging', 'bool'); }, ]);```**Access to nested options from lazy or normalize functions in parent level:**```php$resolver ->defaults([ 'version' => function (Options $options) { return $options['database']['server_version']; }, 'database' => function (OptionsResolver $resolver) { $resolver ->setDefault('server_version', 3.15) ->setAllowedTypes('server_version', 'numeric') // ... }, ]);```As condition, for nested options you must to pass an array of values to resolve it on runtime, otherwise an exception will be thrown:```php$resolver->resolve(); // OK$resolver->resolve(['database' => []]); // OK$resolver->resolve(['database' => null); // KO (Exception!)```</details>---Demo apphttps://github.com/yceruto/nested-optionsresolver-demoCommits-------d04e40b Added support for nested options definition
Summary
c2944acfixes[OptionsResolver] Support nested options #4833. Handle nested options resolving
OptionsResolver::nesteda private array of nestedOptionsResolverinstances by root names,OptionsResolver::setNested($option, array $defaults)defines an option as a new self and returns the nestedOptionsResolverinstance,OptionsResolver::isNested($option)returns whether an option name is a root name for a nestedOptionsResolverinstance,OptionsResolver::getNested()an array of root names representing nestedOptionsResolverinstances.*Allowed*methods are not supported with root name of nested options since they always have to be an array, so:f2ad947 trigger warning ? throw
BadMethodCallException? what do you think ?9e4e02afixes[OptionsResolver] array of instance allowed type #15524. Handle extra options resolving and
addAllowed*ForAllrules with constant flagsOptions::NONEconstant,Options::ALLconstant, matches defined and extra options,Options::DEFINEDconstant,Options::NESTEDconstant,Options::EXTRAconstant, matches any undefined options, helps dealing with "anonymous" options (using a numerical index)OptionsResolver::allowedValuesForAlla private array to validate all options even undefined but not root of nested options as they must always be an array,OptionsResolver::allowedTypesForAlla private array to validate all options (as previous),OptionsResolver::resolveUndefineda privateboolto allow extra options resolving,OptionsResolver::addAllowedValuesForAll($options, $allowedValues, $replace = false, $nested = Options::ALL), first argument may be an array of options names or any flag constant exceptOptions::NONE, if a root name of nested options is passed as/among first argument the method will be recursive because the fourth argument default isOptions::ALL, prevent it by passingOptions::NONEinstead,OptionsResolver::addAllowedTypesForAll($options, $allowedTypes, $replace, $nested), same as previous, note that passingOptions::ALLas first argument will not apply to root of nested options as they must always be arrays and on contrary will apply on extra options, passtrueas third argument to override previous allowed types,OptionsResolver::setPrototypes((string|string[]) $types, (mixed) $values, $options = Options::EXTRA), setsresolveUndefinedtotrueand validate any extra options with the any given type with its corresponding allowed values,OptionsResolver::allowExtraOptions($allow = true)The difference is that in the second case implicitly the flag
Options::EXTRAis passed so previous defaults will not be validate by prototypes rules, if that's not what you want passOptions::ALLas third argument.Note that
setNested()returns the nestedOptionsResolverinstance in this examples, you could call these methods on the$resolveras well but this not the same:ccd5ce1 Harden dependency of nested options with an internal class
NestedOptionsclass which extendsOptionsResolver,NestedOptions::rootthe parentOptionsResolverinstance,NestedOptions::rootNamethe root option name,NestedOptions::resolve()overrides to throw an exception is parentOptionsResolverinstance is not locked,OptionsResolver::isLocked()internal public method to check fromNestedOptionsif it can be resolved.Work in Progress (RFC)
OptionsResolver::setExtraNormalizer(function (Options $options, $extraName, $extraValue) { ... })to normalize any undefined options, basically a shortcut for the currently supportedsetNormalizer(Options::Extra, callable)but with handling of$extraNameOptionsResolver::prototypesprivate array holding allowed values by allowed types to validate extra options.