Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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

Conversation

@HeahDude
Copy link
Contributor

QA
Branchmaster
Bug fix?no
New feature?yes
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets#4833,#15524
LicenseMIT
Doc PRtodo
  • Gather feedback
  • Address a doc PR
  • Update CHANGELOG
  • Add a lot of tests :)

Summary

  • c2944acfixes[OptionsResolver] Support nested options #4833. Handle nested options resolving

    • addOptionsResolver::nested a private array of nestedOptionsResolver instances by root names,
    • addOptionsResolver::setNested($option, array $defaults) defines an option as a new self and returns the nestedOptionsResolver instance,
    • addOptionsResolver::isNested($option) returns whether an option name is a root name for a nestedOptionsResolver instance,
    • addOptionsResolver::getNested() an array of root names representing nestedOptionsResolver instances.
    $connexionResolver =$resolver->setNested('connexion',array('host' =>'localhost','port' =>80,);$connexionResolver->setRequired('user');$connexionResolver->setDefault('password',function (Options$connexion) {returnisset($nested['user']) ?'' :null;});$connexionResolver->setDefault('secure',false);$connexionResolver->setNormalizer('secure',function (Options$connexion,$secure) {return443 ===$nested['port'] ?:$secure;});$connexionResolver->setAllowedTypes('port','int');$connexionResolver->setAllowedTypes('user','string');$resolver->setRequired('connexion');$resolver->setNormalizer('connexion',function (Options$options,$resolvedConnexion) {if ('localhost' ===$resolvedConnexion['host']) {$resolvedConnexion['host'] =$option['param'];    }return$resolvedConnexion;});// In a sub classparent::configureOptions($resolver);$resolver->setDefault('user',$user);$connexionResolver =$resolver->setDefault('connexion',function (Options$options,$previousDefaultConnexion) {if ($options['user']instanceof UserInterfaceand'localhost' !==$previousDefaultConnexion['host'])$connexion['user'] =$options['user']->getUsername();        });$options =$resolver->resolve($array);

    *Allowed* methods are not supported with root name of nested options since they always have to be an array, so:

  • f2ad947 trigger warning ? throwBadMethodCallException ? what do you think ?

  • 9e4e02afixes[OptionsResolver] array of instance allowed type #15524. Handle extra options resolving andaddAllowed*ForAll rules with constant flags

    • addOptions::NONE constant,
    • addOptions::ALL constant, matches defined and extra options,
    • addOptions::DEFINED constant,
    • addOptions::NESTED constant,
    • addOptions::EXTRA constant, matches any undefined options, helps dealing with "anonymous" options (using a numerical index)
    • addOptionsResolver::allowedValuesForAll a private array to validate all options even undefined but not root of nested options as they must always be an array,
    • addOptionsResolver::allowedTypesForAll a private array to validate all options (as previous),
    • addOptionsResolver::resolveUndefined a privatebool to allow extra options resolving,
    • addOptionsResolver::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::NONE instead,
    • addOptionsResolver::addAllowedTypesForAll($options, $allowedTypes, $replace, $nested), same as previous, note that passingOptions::ALL as first argument will not apply to root of nested options as they must always be arrays and on contrary will apply on extra options, passtrue as third argument to override previous allowed types,
    • addOptionsResolver::setPrototypes((string|string[]) $types, (mixed) $values, $options = Options::EXTRA), setsresolveUndefined totrue and validate any extra options with the any given type with its corresponding allowed values,
    • addOptionsResolver::allowExtraOptions($allow = true)
    $resolver->setNested('attachments',array($attachments))    ->allowExtraOptions()    ->addAllowedTypesForAll(Options::ALL,'\Swift_Attachment');// Another way by implicitly allowing extra options:$resolver->setNested('emails')    ->setPrototypes('string',function ($email)use ($regex) {returnpreg_match($regex,$email);    });

    The difference is that in the second case implicitly the flagOptions::EXTRA is passed so previous defaults will not be validate by prototypes rules, if that's not what you want passOptions::ALL as third argument.
    Note thatsetNested() returns the nestedOptionsResolver instance in this examples, you could call these methods on the$resolver as well but this not the same:

    // Any undefined global options can only be strings$resolver    ->allowExtraOptions()    ->addAllowTypesForAll(Options::EXTRA,'string',true, Options::NONE);
  • ccd5ce1 Harden dependency of nested options with an internal class

    • addNestedOptions class which extendsOptionsResolver,
    • addNestedOptions::root the parentOptionsResolver instance,
    • addNestedOptions::rootName the root option name,
    • addNestedOptions::resolve() overrides to throw an exception is parentOptionsResolver instance is not locked,
    • addOptionsResolver::isLocked() internal public method to check fromNestedOptions if it can be resolved.

Work in Progress (RFC)

  • addOptionsResolver::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$extraName
  • add aOptionsResolver::prototypes private array holding allowed values by allowed types to validate extra options.
  • be more strict on NestedOptions array type and indexing when setting defaults. Currently the implementation does not distinguish nested defaults using a numerical index instead of names, which may cause problem when overriding them or to properly map validation/normalization.

backbone87 reacted with confused emojisstok, Zeichen32, and Koc reacted with heart emoji
@HeahDudeHeahDudeforce-pushed thefeature-options_resolver-nested_option branch fromad7cce8 tob486c62CompareMarch 12, 2016 17:19
@HeahDudeHeahDudeforce-pushed thefeature-options_resolver-nested_option branch from92e1f61 to49588c6CompareMarch 12, 2016 20:08
}

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);

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?

Copy link
ContributorAuthor

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.

@HeahDudeHeahDudeforce-pushed thefeature-options_resolver-nested_option branch 3 times, most recently fromdd55fe5 to211a4d9CompareMarch 13, 2016 17:20
closessymfony#4833.Allow to resolve nested options.  * add `OptionsResolver::nested`  * add `OptionsResolver::setNested()`  * add `OptionsResolver::isNested()`  * add `OptionsResolver::getNested()`
@HeahDudeHeahDudeforce-pushed thefeature-options_resolver-nested_option branch from211a4d9 toe1aa1b6CompareMarch 14, 2016 06:16
closessymfony#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()`
@HeahDudeHeahDudeforce-pushed thefeature-options_resolver-nested_option branch frome1aa1b6 toccd5ce1CompareMarch 14, 2016 06:25
@HeahDude
Copy link
ContributorAuthor

Pushedb51cce4 to pass the parent options to each nested option normalizer.

@backbone87
Copy link
Contributor

Just an alternative approach for this feature, without caring about maintaining a tree:

  • AcceptPropertyPaths as option names
  • Provide a wrapper forOptionsResolver that automatically converts option names toPropertyPaths
$resolver->setDefault(new PropertyPath('connexion[secure]'), false);$wrapped = new PropertyPathOptionsResolverDecorator($resolver);$wrapped->setDefault('connexion[secure]', false); // same as 2 lines above

@backbone87
Copy link
Contributor

Just some more thoughts: Your approach of maintaining a tree makes theOptionsResolver almost a clone ofConfiguration

@HeahDude
Copy link
ContributorAuthor

@backbone87 thanks for the feedback.

Accept PropertyPaths as option names
Provide a wrapper for OptionsResolver that automatically converts option names to PropertyPaths

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.

Just some more thoughts: Your approach of maintaining a tree makes the OptionsResolver almost a clone of Configuration

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.

ping@stof@Tobion@webmozart

@backbone87
Copy link
Contributor

I don't agree with you on that, it just makes it more powerful IMHO, ref#3968.

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
Copy link
ContributorAuthor

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
Copy link
Contributor

"option resolution through inheritance" is a specific "defined merging rule"
the "configuration domain" of the Form component areFormTypes

The main benefit of theOptionResolver is probably much better performance, but thats something that can be worked out in the Config component.

The point of view is cleary personal preference, so like you already said: lets hear some other opinions on this topic.

@HeahDude
Copy link
ContributorAuthor

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
Copy link
Contributor

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
Copy link
ContributorAuthor

@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,attr is a perfect example.

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
Copy link
ContributorAuthor

Status: Needs Work

@senkal
Copy link

senkal commentedAug 2, 2016
edited
Loading

We have an use case where we need this functionality.
For now, we just created some class later which provides logic we need, instead of using OptionResolver directly.
Maybe an option would be to move it to higher level(the decision if we want to use the potentially slower option nested resolver), than to change main OptionResolver code.
Maybe new thing, something like collection of those resolver, something similar to ValidatorConstraint/Allhttp://symfony.com/doc/current/reference/constraints/All.html?
Looks like, from what I have read, this is common request to handle just one more level.
Anyway, that wouldn't support nested arrays.
In the end, looks like, for majority not needed.
For very deeply nested arrays- writing your own solution is not that bad.

@fabpot
Copy link
Member

@HeahDude Any news on this one? Still wanting to work on it?

@HeahDude
Copy link
ContributorAuthor

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.

fabpot added a commit that referenced this pull requestOct 10, 2018
…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
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Projects

None yet

Milestone

3.4

Development

Successfully merging this pull request may close these issues.

[OptionsResolver] array of instance allowed type [OptionsResolver] Support nested options

9 participants

@HeahDude@backbone87@webmozart@senkal@fabpot@Ma27@javiereguiluz@nicolas-grekas@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp