Updated: Comment #90
Problem/Motivation
Entity field classes should be able to implement methods that declare a list of possible and settable values for the field. This is field specific information and should live directly on the Field class. We want to get rid of the procedural style hook_options_list().
Proposed resolution
Add an AllowedValuesInterface with methods to get all possible options for a field and all settable options for a field. Implementations could be dependent on the current user, so we need to pass that to the methods. Example: the settable options of a workflow state field vary per user, admins can use more options than editors. And we need one method to retrieve the plain values, and one to get a formated options array of values with labels. ==> 4 methods.
Remaining tasks
Provide feedback on the latest provided patch.
User interface changes
none.
API changes
hook_options_list() is removed/deprecated, methods on Field classes are added with AllowedValuesInterface
Related Issues
#1696648: [META] Untie content entity validation from form validation
Original report by fago
As fields have hook_options_list and d7 entity api module had 'options list' callbacks, we want add something similar to property definitions.
We could just follow hook_options_list() what supports opt-groups and generate the flattened allowed values out of it. A requirement that came up in the d7 entity.module was being able to differentiate between 'view' and 'edit' options, e.g. possible already set values and possible values to set. Then, we want to integrate this with validation - so maybe this is better postponed until we know how validation works exactly.
Comment | File | Size | Author |
---|---|---|---|
#145 | options_list-1758622-145.patch | 657 bytes | smiletrl |
#135 | field-options-1758622-135.patch | 41.82 KB | klausi |
#135 | field-options-1758622-135-interdiff.txt | 3.36 KB | klausi |
#134 | field-options-1758622-134.patch | 41.77 KB | klausi |
#134 | field-options-1758622-134-interdiff.txt | 1.92 KB | klausi |
Comments
Comment #1
fagoComment #2
fagoMoving to core queue.
Comment #3
fagoSome thoughts on this:
- We should make this general, i.e. apply it to the TypedData API. Such that we can get the allowed-values for a single property (text formats?) as well as for a whole field. Then, we can leverage this for validation also.
- I'd suggest adding a method for that. Not sure whether it should be built-in and be NULL if there are no allowed-values or have its own interface?
Comment #4
das-peter CreditAttribution: das-peter commentedComment #5
fagoWe'll need this to have something to validate against.
Comment #6
das-peter CreditAttribution: das-peter commentedHere's the first patch for that, finally ;)
Comment #8
BerdirDescription a bit terse, is this an indexed array? Then it should probably be a "list of values"?
You probably want to leave out the type hint yet, we have it in entity access and it's a mess.
No point in adding it before we have a global interface for $user.
If you add it, it should be with use.
@see should be at the bottom.
Hm, first column? I would expect that allowed values only works non-complex data, otherwise the value would be an array of somthing?
Missing @param for the user.
This will at least need more documentation to make the difference clear because both have a user context now.
I know you discussed use cases for it, but for the sake of this interface, it might make sense to leave the user context out of the available ones.
Can't we derive this based on the allowed values and available options?
A bit weird due to the possible nesting, but...
@inheritdoc
No, but you don't need to worry about reverting it, TestBase takes care of this.
Long function calls are always tricky and we don't have strict coding standards but we usually try to write them on a single line. U usually use variables to shorten them.
And if you're doing assertEqual/assertText, I think the assertion message can be left out in most cases.
Inject the filter format storage controller or just the filter formats. But that's unfortunate if you don't actually need it.
Extending from an actual test is not something we usually do, expect you actually want to re-run those test functions in here, but then a common base clase would make more sense. Why not EntityUnitTestBase or whatever that's called?
Was confused about these todo's, but you copied it from TextPlainUnitTest.
You don't seem to be using display stuff.
I think this class does too much abstraction, there's no need for it, the API is actually considerably more complicated than simply using it directly. I simplified it in my clean-up TestEntity issue.
Comment #9
das-peter CreditAttribution: das-peter commented@berdir Thanks for the review!
Yes, that's a valid point. I'm not sure how to document this, either. We probable should outline an use case and try to break it down into an understandable documentation.
As discussed I've removed the @todo now - we have to refactor such code globally anyway.
Oh, that was not supposed to be in the patch - removed it.
The tests were mainly failing because the text module now depends on the filter module and thus unit tests have to declare that explicitly.
Comment #11
fagoUpdated patch. Sry, I forgot doing an interdiff.
Comment #13
fagore-rolled
Comment #15
das-peter CreditAttribution: das-peter commentedAnother bunch of fixes.
Comment #17
das-peter CreditAttribution: das-peter commentedDamn, I checked the wrong
FieldAccessTest
- note to myself "We've namespaces now!"Comment #18
BerdirAs discussed, let's use a different example.
Interface is not namespaces. How will that change when TypedDataInterface goes away?
This shouldn't be CamelCase.
Not sure if the message makes sense, as it's not really about the number of choices but being valid or not?
Let's also add at least one case here where we check the returned constraint (property path, correct one, ...)
As mentioned, not sure about the $account argument to available options, according to the current documentation and example implementation, it seems useless. As we don't need it yet, I'd suggest to leave it out and re-add when we do have a use case/discuss in a separate issue.
Comment #19
fagoYes, as discussed I think a workflow state field is a better example. Allowed options probably depend on the existing state, while available options are all states.
Yep, I think re-working validation will be the most trickiest part. I think we still need to differentiate between Typed objects and the plain value then, difference being that in stead of having a TypedDataInterface we just say it must be a classed object. So the type-hint goes to object then I guess.
@min-max:
The constraint has options for the number of items you may select which we inherit from the parent. So that's already there, we just have to fix the messages to use drupal style replacement characters.
I agree checking the property path should be fine. There is no way to get the constraint which caused the violation in symfony validator though.
Comment #20
das-peter CreditAttribution: das-peter commentedHere's an updated patch. I hope got all necessary changes.
Comment #21
fagooh, I forgot to answer this one:
It's not useless, I can give you a use-case you'll need it even in core: Give me the options list for an entity reference. Obviously, this options list would have to be access filtered, for which you'll need the user. I guess it works already that way by just using the global user.
@patch: Looks good to me!
Comment #22
fago#20: d8-allowed-values-interface-1758622-20.patch queued for re-testing.
Comment #23
fagoComment #24
Berdir#20: d8-allowed-values-interface-1758622-20.patch queued for re-testing.
Comment #25
BerdirThe entity validation issue is in, so let's integrate it with that and also add basic test coverage. Can we already use the text field that's attached to entity_test and it's format for this?
Comment #26
PanchoTwo nitpicks:
The naming of the interface and is a bit irritating, since it defines both allowed and available values/options methods. Even more, allowed is the subset of available, so maybe find something more generic as interface name?
We probably don't want to use format_plural() here, because it's calling t(), and building plurals without taking the locale into consideration doesn't make much sense, but does this piped string notation have any precedent in Core?
Comment #27
fagoNope, but that's something which symfony validator already does and is supported in Drupal as well - as we implement the translator interface there. That is, this runs correctly through format_plural() - which takes locale into account?
Comment #28
fagoCan you recommend better names then? Afaik, allowed values is the term commonly used for that, so I think it's a reasonable choice.
Comment #29
msonnabaum CreditAttribution: msonnabaum commentedLooking at the interface a bit, it appears to me that this interface is much more about options than it is values. The only idea I can see here that's unique is that it has methods to return a constrained set of options, so that should be reflected in the name.
I'd also rework the methods a bit like so:
It was very confusing to me at first differentiating between allowed and available. When I read getAvailableOptions, I think that means that I'm getting a set of currently available options, and that availability could be influenced by the current user or some type of state.
I also like the idea of treating the constrained version as a special case, which makes it easier to understand. getOptions simply returns all the options whereas getAvailableOptions tells me that it is a subset.
It's a little odd that they both have the Values methods as well. If the FilterFormats example is typical, I'd say we should remove the Values methods and always return an array of objects. If the caller needs the label, they can call the ->label method on the object.
Comment #30
das-peter CreditAttribution: das-peter commentedI think the reason to have
getValues()
is thatgetOptions()
could return a structured (nested) array to be used in a<select>
html field. And this isn't very usable if you just want a "pure" list of values to work with.Comment #31
das-peter CreditAttribution: das-peter commentedHere's a simple re-roll from which I'll continue.
Comment #32
fagoSort of. I think getAllowedValues can always be influenced by the current user or some internal state. But getAvailableOptions is about a list of options that might be in existing objects. E.g. consider a view which displays the options in an exposed filter select list. It would have to show all options that exist in all of the objects, whereas the allowed options tell you what options you can actually set.
Actually, the "all options that exist in all of the objects" would fit more into a definition object - but we do not have that separation by now...
True, however we currently do no support this as of the docs of getOptions(). I've been thinking this as well, but turns out field API does not support this by now. Still, I thought that having a separate getAllowedValues() makes it more explicit that this is used for constraint purposes also.
So maybe we should add support for nested $option structures right now, so the differentiation makes more sense from the beginning?
Comment #33
fagocross-post
Comment #34
fago@ConstrainableOptionsInterface
So what I dislike a bit about going with "options" primary is that
- it might get confused with "options" in terms of "settings", "preferences" in general. E.g. symfony uses it that way: http://symfony.com/doc/current/components/options_resolver.html (and we do for validation constraint options).
- options does not tell me it's validated. ConstrainableOptions makes that up, but "AllowedValues" is more straight-forward to me here and partly already used by field API.
@label(): We also have situations where there is no object we could get the label from, e.g. field API types like list_integer allow you to configure a list like
key1|label1
key2|label2
Comment #35
das-peter CreditAttribution: das-peter commentedHere we go:
EntityValidationTest
. No need for further integration it worked out of the box like a charm :)FilterAPITest
getAllowedValues()
/getAllowedOptions()
togetValues()
/getOptions()
AllowedValuesInterface
As soon as we've a final agreement on the naming I'll update the patch.
Comment #37
das-peter CreditAttribution: das-peter commentedAdded dependency to the filter module in the failing test.
Comment #39
das-peter CreditAttribution: das-peter commentedComment #40
Berdir#37: d8-allowed-values-interface-1758622-37.patch queued for re-testing.
Comment #42
BerdirRe-roll.
Comment #44
BerdirThat filter api test became a DUBT test overnight :)
Updated the test, the interdiff is a bit big because I also removed a conditional if, no need to do that in a test.
The methods on the interface look fine to me, but we still need some documentation improvements there.
Comment #46
Berdir#44: d8-allowed-values-interface-1758622-44.patch queued for re-testing.
Comment #47
fagoIndeed, AvailableValues isn't very descriptive. I think "PossibleValues" describes it better, so I updates docs+patch to go with that. Please check the interface whether that works for you.
Also, I updated the patch to work with 8.x HEAD again. Then, field-types as data types went in so I converted options.module to rely on the interface instead of the fake-hook-callback + made it work via the legacy-field support.
Comment #49
fago#47: d8_options.patch queued for re-testing.
Comment #51
BerdirDoc improvements look good.
Is the options hook just for backwards compatibility? is there nothing in the options callback documentation that we still need?
Comment #52
fagoThis is the only part of the docs I was not sure about. Referencing to this function at the interface does not make much sense and filtering probably depends on the caller?
The rest of the docs are reflected, i.e. I moved parts to the AllowedValuesInterface.
Comment #53
fagoRe-rolling this one.
Comment #54
fagoUpdated patch attached.
It contains:
- move to annotations
- docu improvements in regard of invoking the legacy-hook as requested by berdir
- fixes for the FilterFormatAccess test - this has been a bit tricky as they way this works if a filter format value becomes invalid (format gets deleted) is that users cannot edit it any more, but values are kept. However, with this patch this issues validation errors - so I had to enhance field API's errorElement() a bit to allow ignoring this error. This also shows we should do #2027059: Improve the documentation of WidgetBase::errorElement() for mapping violation property paths to form elements.
Comment #55
fagoNoticed I forgot to remove left-over use statement from previously.
Comment #57
fagoThis reveals a left-over from #2002102: Move TypedData primitive types to interfaces - attached patch fixes the @todo.
Comment #58
BerdirLet's make this an AccountInterface now that we have it.
Comment #59
fagooh, yes.
Comment #60
fagoForgot to merge in latest 8.x changes before diffing.
Comment #61
klausishould be "An array of allowed values."
The rest looks pretty good, as far as I can tell.
Comment #62
PanchoSome more nitpicks:
add: "and possible values."
The essence of this interface (reflected in the naming) are the allowed values. Therefore the explanation should be the other way around:
"While possible values specify which values existing data might have, allowed values define the values that are allowed to be set by a user."
Also, we're now switching forth and back between "values" to "options". At this point we shouldn't:
"values" not "options". Also comma (,) after "scenario".
Better: "in an editing context"
Now that we explained the difference between "allowed" and "possible", we need to say that both are provided by this interface. Now we also want to explain the difference between "values" and "options", for example:
"For convenience, lists of both allowed and possible values are also provided as structured options arrays that can be used in an Options widget such as a select box or checkboxes.
@see \Drupal\options\Plugin\field\widget\OptionsWidgetBase"
Comment #63
fagothanks, I updated the patch based on the feedback.
Comment #64
amateescu CreditAttribution: amateescu commentedMissing space here.
This requires you to think a bit about what it's doing (traversing typed data objects is my guess) so it might use a comment..
Spacing looks wrong.
Extra empty line.
This is duplicated 3 times, I think it warrants a TextWidgetBase abstract, but not necessarily in this patch :)
Other than that, the patch and functionality looks awesome!
Comment #65
BerdirThis looks great to me, apart from the few parts that I don't fully understand, related to that filter format validation stuff :)
I tested this with a use case that I have for this interface and it works great.
Setting to needs work to address @amateescu's feedback and it needs a re-roll anyway.
Comment #66
klausiFixed the white space issues.
Did not introduce TextWidgetBase, that can be a follow-up.
Code is in the field-options-1758622 branch in fago's entity sandbox.
Comment #67
amateescu CreditAttribution: amateescu commentedThanks :)
Comment #68
PanchoAt least shouldn't be duplicated three times. TextareaWithSummaryWidget::errorElement() can inherit from the parent TextareaWidget::errorElement().
Comment #69
PanchoBulls***. Forget patch #68.
This one here should work, but it might not be worth it.
Comment #70
PanchoSo in any case #66 remains RTBC.
In the meantime I created #2032745: Create a TextWidgetBase as a followup.
Comment #71
YesCT CreditAttribution: YesCT commentedThis issue was RTBC and passing tests on July 1, the beginning of API freeze.
Comment #72
YesCT CreditAttribution: YesCT commentedtag did not stick. trying again.
Comment #73
PanchoPatch removes hook_options_list().
If we can just deprecate it instead, this could be tagged 'Backwards compatible API change' and would be still acceptable.
Comment #74
PanchoOh, we don't completely remove the hook, it's still invoked by BC methods in ConfigEntityReferenceItemBase (and LegacyConfigFieldItem). Would that be enough for backwards compatibility, or do we need to continue invoking it from OptionsWidgetBase and keep documentation in options.api.php?
Actually, it's not completely clear to me what exactly belongs to the frozen API and what doesn't. Are remainders of the D7 API always frozen if not explicitely marked deprecated, even in unusual in-between states mixing old and new API features? And what happens to API functions that become obsolete while the remaining conversions are completed?
Some enlightment would be nice!
[edit:] Or wouldn't the API freeze only come into effect after beta1 has been rolled out? Without a beta1, module developers won't start porting their modules anyway. In that case another "deprecate what will soon be removed" week would be awesome.
Comment #75
amateescu CreditAttribution: amateescu commented@Pancho, we don't really care, this was rtbc on July 1st :)
Comment #76
PanchoIf that's enough, then fine... :)
Comment #76.0
PanchoUpdated issue summary.
Comment #76.1
klausiissue summary template
Comment #77
klausiUpdate the issue summary to make committer feedback easier. Please improve it as you see fit!
Comment #78
Dries CreditAttribution: Dries commentedThis interface is rather awkward. I don't understand why we return two sets of options/values. Furthermore, I'm confused about which of the two functions returns the superset; getValues() or getPossibleValues().
Comment #79
klausiOk, so let's try to make it more obvious. I agree that getValues() is pretty bad and does not tell me anything.
Proposal:
getAllPossibleValues(): Returns an array of all possible values which existing and future data might have.
getAllPossibleOptions(): Returns an array of all possible values with labels for display.
getAllowedValues(): Returns an array of allowed values that can be used in the current context (might depend on the user, the current field value etc.). Subset of getAllPossibleValues().
getAllowedOptions(): Returns an array of allowed values for display. Subset of getAllPossibleOptions().
Let me know if you like that, then I'll roll a patch. Or make a counter proposal :-)
Comment #80
klausiDiscussed this with fago in IRC, turns out I got it all wrong.
There is no method to retrieve a super set of all possible values that ever exist. Is that a problem for us? The possible values are also dependent on the user.
We discussed:
getAllowedEditValues() + getAllAllowedValues()
getViewableValues() + getEditableValues() + getAllValues()
getAllValues() with $op as we had it in entity.module d7 (options list callback)
getAllValues(), getSetableValues() with optional $account ?
CRUD as $op parameter?
Comment #81
klausiThinking more about it, new proposal:
getAllPossibleValues(): Returns an array of all possible values which existing and future data might have (superset of all values). If the optional $account parameter is passed, then the array is filtered to values viewable by the user.
getAllPossibleOptions(): same as getAllPossibleValues(), except that it returns labels and/or groups for the values.
getSetableValues(): Returns an array of values that are allowed to be set in the current context (might depend on the user, the current field value etc.).
getSetableOptions(): same as getSetableValues(), except that it returns labels and/or groups for the values.
The most important change is to use "Setable" instead of "Allowed", which is a bit more clear I think.
I don't think we need CRUD since only create and update is relevant here. The implementation of getSetableValues() can determine from its own field context which operation is meant (create if the field value is not set, update if there is already a field value).
Comment #82
PanchoYes, "Settable" would be both more descriptive and more correct than "Allowed".
Finally, preexisting values also remain allowed for existing data.
Note though that it is "Settable", not "Setable".
Secondly, if the other list of options is supposed to be the superset of "Settable" and "Preexisting" values, then it might be both clearer and more flexible to have methods for all of "Settable" values, "Preexisting" values, and (possibly) their superset.
Finally, for search filters we don't need values that might be settable, yet are currently not used. We usually only want the preexisting ones.
Thirdly, what would then be the use case of a superset?
"Conceivable" might be worth a thought, but a concrete use case in mind would probably lead us to a better name for the superset.
Comment #83
fagoI don't think we need to separate the superset from "pre-existing", as for search filters etc. you don't want only real pre-existing values but all possible values, thus you already want the super-set (possibly filtered for the current user) there. Searching only for real-pre-existing values is faceted search, which needs to be directly database/search server driven to be efficient anyway.
This I think we should go with
- both having an optional user $account argument, which can be used to limit the values/options only to the one the user has access to (if passed only).
Comment #84
klausiOk, implemented that accordingly.
Note that the filter API test case fails locally for me, but I have no idea why.
Comment #86
yched CreditAttribution: yched commentedNot a blocker (and no better proposal for now), but the "ignore errors" part in texteare widget smells wrong. It shouldn't be up to the widget to decide that a reported error should be discarded ? The validation level should not report the error in the first place ?
Also, not fully convinced by the signature changes in OptionWidgetBase (added $delta defaulting to 0), seems half baked. Maybe pass a FieldItem directly, rather than a Field that gets by default silently resolved to the first item ?
Again shouldn't block commit IMO, sounds fixable post commit without api change.
Comment #87
klausiSome test fails seem unrelated, fixed one method call.
the filter API test case will still fail.
Comment #89
klausiTracked down the filter API test fail and fixed one remaining old getOptions() name.
Comment #89.0
klausiadded comment updated number
Comment #90
klausiUpdated the issue summary to reflect the new "settable" terminology.
Anyone up for RTBC or other feedback?
Comment #91
fagoI think this should mention what's the default behaviour also. It's a bit repetitive as it is already noted generally above, but as often the $account = NULL default is used differently I think this is worth making clear.
The comment is wrong - there is always a class we use to check validation criteria.
Should this check allowed values for the currently logged in user or do not filter based on the user account? I think not including magic access checks to the global users is a sane default, but how could I pass a user here?
I feel like the whole validation process could need an optional user we validate the data for - so that goes beyond this issue. Should we open a follow-up for that?
This defaults to the current user but that's not what the interface specified.
Well, I think the validation level is right in that the data is not valid any more. What's strange here is that we allow an entity having out-dated, invalid data to pass validation and continue saving it - but that's existing behaviour.
So the existing behaviour is that this invalid parts cannot be edited, but pass validation. It's the widget who decides that this invalid value cannot be edited but is kept, so I think it's also ok to have the widget decide to ignore this validation violation. Also in the code there, field API already by-passes validation for widgets with #access FALSE (generally), so I think this is just continuing with that behaviour on a more fine-granular level as it allows to ignore validation fails from parts of the widget having #access FALSE. And it must be done by the widget itself, because when an item property fails validation we cannot know whether this property is edited as part of a combined widget or not.
Well, if you have a multi-value select widget you can go with only one item and you'll have to assume options for each field item are the same. For that case, I think having the field item be optional and just using any should be fine, as it should not matter anyway.
But, if you use a widget per field item we can go for an individual item's field options - it might matter here. But yes, I agree we should better pass the whole $item instead of just the delta in this case.
Comment #92
fagoComment #93
klausiFixed remarks from #91. Not touching $delta vs. full $item in OptionWidgetBase.
Comment #94
fago#93: field-options-1758622-93.patch queued for re-testing.
Comment #95
fagoThanks, the improvements are good + I think the updated terminology should be clearer, so setting to RTBC again.
Comment #96
PanchoMuch better now!
However, there still seem to be some inconsistencies:
1)
"values settable by the account" is clear, but what are "values viewable by the account" meant to be?
It's not used in any implementation, and in all case I can think of, it wouldn't be usable, so we might want to leave the parameter out. But maybe I simply didn't get the right idea.
2)
What exactly does "all" refer to?
In getAllPossibleValues() (and -Options) it is part of the function name, while in getSettableValues() (and -Options) it is not.
However, according to the docs "all" refers to not being filtered by $account. The inconsistency is confusing.
Resolution would depend on the decision on 1.
3)
+ * Returns an array of settable values with lables for display.
Typo: "lables"
Comment #97
klausiFixed the typo.
getAllPossibleValues() can be used for example in a Views exposed filter selectbox to filter entries in some table. Depending on the acting user you might want to display a different set of selectable values in the selectbox that the user can see for filtering. So those values are "viewable" by the user. I think we want to keep the user parameter to keep the interface flexible enough for future use and contrib.
The name getValues() is bad because it is too broad and does not tell us anything about its meaning. getAllValues() is also bad because you don't really get all values as soon as you pass a user account object as parameter. getPossibleValues() is harder to differentiate from getSettableValues(). So getAllPossibleValues() is the closest that we could come up with to reflect the meaning. Feel free to suggest a better name.
Comment #98
fagoAgreed - there are definitely cases where users are not even allowed to see an option, think of an ER & entity view access.
I'm not sure getAllPossibleValues() is really that much better/easier to differentiate from getSettableValues() and Pancho has a valid point here with "all" not being "all" once you filter based on user access. getPossibleValues() + getSettableValues() would work for me.
Comment #99
klausiNew suggestion that came up in IRC discussing with fago (IRC log attached):
getOptions() + getOptionValues() for the settable values, because we want to use this as default, so that people don't accidentally use the possible values in places where they actually mean settable values.
getPossibleOptions() + getPossibleOptionValues() for the viewable/possible values. We are avoiding "all" here, because it is a filter operation if you pass a user object.
Now we did enough bike shedding so that we are back to the patch in #69, except that getValues() is now getOptionValues(). We can improve the docs though with the discussion we had in between.
Before I roll any more patches I need consensus on the names. Please +1 this suggestion or propose concrete alternatives.
Comment #100
PanchoSo I renamed getAllPossibleValues() to getPossibleValues(), and getAllPossibleOptions() to getPossibleOptions().
Finally, I improved the docs quite a bit, explaining what the options arrays can be used for.
'Multi-dimensional' should be 'two-dimensional' because cascading optgroups aren't allowed. Maybe 'two-level' would be even better, in any case it would be easier to understand, and finally, depending on how you see an array, a flat array is already two-dimensional.
Also removed abbreviations such as 'e.g.' and 'i.e.'.
IMHO, there's not much missing to be RTBC again.
Comment #101
PanchoSorry for misnaming the interdiff. Maybe someone can stop it being tested?
Comment #103
PanchoOops. Erroneously killed a brace.
This one should do what the one in #100 was advertised to do.
Note also that I crossed #99, so this patch only does what the consensus was before #99.
Further renames could go on top of this one here.
[edit:] Re #99:
As @klausi said, what you're proposing from your IRC chat is close to what we already had in #66, which @Dries didn't like too much as stated in #78. I'd be fine with that, though, and I'm not sure we can do any better.
Comment #104
Panchowould be yet another alternative, which might be both easier to differentiate and remember, while still giving a clue what kind of options these are. getNewValueOptions() also looks more like the default options set for entering new data.
However, I agree we should settle for either of these proposals now. For a final decision we should maybe assign to @Dries who had expressed his doubts in #78.
Comment #105
effulgentsia CreditAttribution: effulgentsia commentedThanks for the work since #78 on clarifying and documenting the difference between
possible
andsettable
. I especially like the Workflow example used in the docs for the interface.However, do we have any use cases in core for
possible
? We don't appear to in the patch. The filter format example in the patch doesn't really serve as the use case, because in core, we *don't* let the user view which formats are possible. We only ever let them see the ones they can set.If core has no use case for
possible
, why include it in this interface? Perhaps it should be a separate interface? Also, I'm a bit confused on why getPossibleValues() is an instance-level method rather than a static method. It seems like it shouldn't depend on the current data in the item, or should it? However, it would need to depend on the field definition, so it can't really be a static method without also receiving the definition as a parameter. This confusion is partially what's causing me to think that maybe it should be a separate interface worked out in contrib rather than being added to core without more clarity around that.Comment #106
fagoProblem is, when core does not define the interface for possible values, there won't be any "officially" shared interfaces modules could rely on. I think what would happening is that people implement the Settable-variant of core and are done for it, meaning the cases where such a differentiation is needed won't be handled properly - e.g. pluggable widgets available for views-exposed-filters or a condition plugin which needs to configure the value possibly set won't be possible without such an established part of the interface.
Then, I think we also have one example in core: user roles. Initially we used as the documentation example, but we replaced it with workflow states as it makes a better, simpler example. So when you edit a user, the settable roles won't include the anonymous user role, but all possible options should include it (not so fancy for the exposed views form until we have more anonymous accounts, but still needed for the condition case).
Another example we've in core is the views-based ER-selection widget, which could probably need a flag to differentiate between possible and settable values. Without the differentiation in core we won't ever be able to handle that.
Yes, I've been thinking about that as well. getPossibleValues() would probably fit better on a non-existing definition class, but we don't want people to have to provide multiple classes. Making getPossibleValues() static is indeed an interesting idea. When we started with this a while ago, the plan to handle additional metadata context was to still instantiate empty objects and have fields checking that context of empty objects for getting additional metadat (e.g. a field inside an empty entity of type X) - for what we still needed instances, however meanwhile our approach shifted to using class based definition objects with interfaces, so you can have entity-type-aware definitions (field instances) open the door to make this static. Still, we don't have the class based definitions in typed data - but I think we should still tackle that for a better integration of fields+typed data. (yes, this needs more discussion). update: see #2047229: Make use of classes for entity field and data definitions
I still like the idea of just having getOptionValues() instead of getSettableValues(), but I'm not keen on bikesheding this further.
Comment #107
effulgentsia CreditAttribution: effulgentsia commentedThanks. #106 makes sense. Given that, I like getSettable*() and getPossible*(). Another option I can think of is to unify into a single getAllowed*(), but add a parameter for something like $ignore_current_data. The advantage of that would be that for many simple implementors, there's no difference between the two and they could ignore that parameter instead of needing to implement multiple methods. But then we'd need to bikeshed that parameter name. Assigning to Dries for feedback in general, now that we have more info on use cases identified in the patch and in #106, but feel free to keep discussing in the meanwhile if you want to.
Comment #108
Dries CreditAttribution: Dries commentedI'm comfortable with the new interface. It's better than what we had before but I'm still not 100% about it, yet I don't have good suggestions to make it better. I also don't want to hold this up further as we have a lot of other things to work on. We can always change it later if we come up with a better approach. So, green light from me! :)
Comment #109
klausiI don't think we should change this later, because it is an interface and that should rarely change.
The $ignore_current_data parameter proposed in #107 does not seem to be a good idea IMHO. On method invocation that is just an anonymous boolean flag which makes the code harder to read and less explicit:
I'm also ok with the current getSettable*() and getPossible*(), so are effulgentsia, Pancho, fago and Dries. I think we can move on now, so setting to RTBC. Feel free to change the status back if you disagree.
Comment #110
alexpottCommitter feedback obtained in #108...
Looks like this refactoring has taken place :) ... but now it's
_account
Should be
_account
Also needs a reroll...
Comment #111
klausiRerolled + fixed _account in the request object.
Comment #112
klausiFixing tags, hopefully.
Comment #114
klausiThis should fix at least the fatal errors, but OptionsFieldTest for example still fails. It looks like a change to the field settings is not reflected in the entity form that is retrieved later.
Comment #116
klausiFixed the "provider" vs. "module" settings change in the entity field API. Also fixed the OptionsFieldTest by reinitializing the outdated field object.
Comment #118
klausi#116: field-options-1758622-116.patch queued for re-testing.
Comment #120
klausiRerolled, not other changes.
Comment #121
fagoThanks klausi for keeping up with HEAD and re-rolling - I had a look at the changes and the whole patch again. -> This looks good, so let's move on.
Comment #122
webchickTagging per Dries in #108.
Comment #123
alexpottNeeds a reroll
Comment #124
BerdirRe-roll, one small change already got in in the getType() issue, left with a stupid use conflict because this unecessarly used the Sring class, see interdiff :)
Comment #125
yched CreditAttribution: yched commentedSorry, can we hold this for a sec ?
I now I'm terribly late to this issue, but I'm reviewing now and have a couple remarks
Comment #126
yched CreditAttribution: yched commentedWon't this be the same implementation everywhere ?
Providing those in a base class is probably useless since most classes that might want to use it have more interesting classes to extend.
It makes me wonder if we really need those methods in the interface,though. We could just provide the "getPossibleValues()" with labels, and let the callers do array_keys if that's all they're interested in ?
No biggie though, feel free to ignore me if this has been discussed already.
Doesn't seem to be true, since FilterFormat is the only class that extends AllowedValuesInterface right now ?
OptionsWidgetBase simply assumes the existence of a getSettableOptions() method. For field types currently using option widgets, getSettableOptions() is implemented by ConfigEntityReferenceItemBase / LegacyConfigFieldItem, which both provide only this method but not the full AllowedValuesInterface - maybe they should ?
The callers of getOptions() now directly receive $items as a Field object, so no need for this dance.
They should pass that $items object to the getoptions() method.
Also, I don't think the $delta param is needed. A FieldItem object doesn't know its own delta, so the getSettableOptions() method will not return a different list of options whether it's called on the first item or the second - and this would be sick if they did IMO. Just call getSettableOptions() on $items[0].
Same for getSelectedOptions() - plus, getSelectedOption*s*($items, $delta) really makes no sense :-)
Actually, probably getOptions() should rather directly receive the FieldItem object, let the caller figure out which. Makes more sense for the method IMO.
Then $this->entity can be removed in WidgetBase, it's only used in getOptions() to pass to hook_options_list_alter(), but passing $items->getparent() or $item->getParent()->getParent() is good enough.
Shameless plug to #2061331: Add getEntity() helper in FieldInterface / FieldItemInterface, BTW...(getEntity() as shorhand for getParent()->getParent())
The phpdoc for hook_options_list_alter() still refers to hook_options_list()
+ code still calls hook_options_list(), with a "@todo: Convert all legacy callback implementations to methods".
Do we have a followup for this ?
+ Minor stuff while we're here:
filter_formats() already returns formats keyed by id(), so this could be a one liner with array_map() & a closure.
Same for getSettableOptions()
- Double whiteline in EntityValidationTest
- TextareaWidget: missing empty line after last method
Comment #127
klausiNo, getPossibleValues() and getPossibleOptions() will not be the same implementation everywhere, because getPossibleOptions() can also provide option groups (think about a selectbox offering grouped options). In this example there are no groups, so the values are simple the keys of the options. See the @return doc block on AllowedValuesInterface.
OptionsWidgetBase is not a field type, so this does not necessarily have to implement to AllowedValuesInterface. ConfigEntityReferenceItemBase / LegacyConfigFieldItem can be handled in a follow-up I think.
Removed $delta, good idea about passing the individual item directly!
Not removing $this->entity, since that is still used in geOptions().
I don't think we have a follow-up yet for completely removing hook_options_list().
Comment #128
yched CreditAttribution: yched commentedThanks @klausi.
Ack for getPossibleOptions() / optgroups.
Sure, I was not advocating for OptionsWidgetBase to implement AllowedValuesInterface.
Followup for ConfigEntityReferenceItemBase + hook_options_list() : why not, but we should open those then.
geOptions() doesn't need it anymore, the entity is in the $item:
$entity = $item->getParent()->getParent();
(can be reduced to$item->getEntity()
after #2061331: Add getEntity() helper in FieldInterface / FieldItemInterface is in)Much cleaner than fetching it from the outside and hoping it matches.
Minor:
Lacks full namespace
Comment #129
yched CreditAttribution: yched commented[side note: I can make the above changes myself in the sandbox if that's fine with you - don't wan't to do it without permission though :-) ]
Comment #130
klausiyched: permission granted, of course!
You can pull from the field-options-1758622 branch in https://drupal.org/sandbox/fago/1497344
Comment #131
yched CreditAttribution: yched commentedDone
(didn't push yet, don't seem to have commit rights ATM)[edit: thks @klausi :-)]Comment #132
klausiOk, so yched seems to be happy now, the testbot is happy and the rest of us have been happy before + the minor changes look good ==> back to RTBC.
And here is the follow-up issue: #2069739: AllowedValuesInterface for ConfigEntityReferenceItemBase / LegacyConfigFieldItem.
Comment #133
yched CreditAttribution: yched commentedMarked #2015689: Convert field type to typed data plugin for options module as postponed on this.
Comment #134
klausiPatch does not apply anymore, rerolled.
Comment #135
klausialexpott pointed out that we should use the current_user service. Also moved a variable around to be more coherent in the code.
Comment #136
klausiSince #2064181: Filter format access bypass on POST/PATCH is postponed on this (which is a critical security issue) this is critical as well.
Comment #137
alexpottCommitted 5277135 and pushed to 8.x. Thanks!
Fixed some stuff during the commit... "rightaway" is not a word and is unnecessary anyway.
Comment #138
tstoecklerI think actually the previous way was correct. This way, it seems as though the function could return TRUE as well.
Comment #139
tim.plunkettfalse is not a valid typehint. bool is.
Comment #140
klausi"false" is a valid type hint according to our coding standards and should be used if TRUE is never returned: https://drupal.org/coding-standards/docs#types
Comment #141
BerdirAs discussed in IRC already, according to our standard, false is valid and would have been correct there: https://drupal.org/coding-standards/docs#types
But it doesn't matter that much :)
Comment #142
klausiFollow-up: #2075827: Fix $this->container->set('current_user', $user); in FilterAPITest
Comment #143
klausiChange notice draft created: https://drupal.org/node/2075873
Comment #144
BerdirLooks ok to me, removed an unecessary isset() arround $account. Looking at the example, wouldn't it be supposed to fall back to the global user is non given and not deny access? Wondering if we shouldn't make it required, that's much more in line with how $account->hasPermission() works, as opposed to user_access(), which has a built-in default.
Would be a API change but none implements this yet, so...
Comment #145
smiletrl CreditAttribution: smiletrl commentedI think this patch missed something:)
Comment #146
yched CreditAttribution: yched commentedIndeed...
Comment #147
webchickComment #148
smiletrl CreditAttribution: smiletrl commented@webchick, it's more of a convention to add this parameter $account to avoid the possible parameter confusing imho, because all the four methods depend on the paramter $account, at least literally. So, it's quite wierd that one call to one of these four functions suddenly doesn't need the paramter anymore.
And yes,
getPossibleOptions($account)
in this context doesn't actually use the parameter $account in its function body. So, it won't have any real bad effect to simply call$this->getPossibleOptions()
directly.Comment #149
smiletrl CreditAttribution: smiletrl commentedHere's another fix for this patch at #2015689-44: Convert field type to typed data plugin for options module .
Comment #150
effulgentsia CreditAttribution: effulgentsia commentedAgreed with #148. This doesn't need tests, because the change is to a specific implementation that doesn't depend on $account, but passing the parameter through is cleaner coding style.
Comment #151
webchickOh, cool. Thanks for the clarification.
Committed and pushed to 8.x. Thanks!
Comment #152
yched CreditAttribution: yched commentedRestoring title for posterity
Comment #153
effulgentsia CreditAttribution: effulgentsia commentedRestoring priority and tags for posterity.
Comment #154.0
(not verified) CreditAttribution: commentedupdated "allowed" with "settable"