Problem/Motivation
This is a prerequisite for #2786841: Entity reference fields should add selection handler config dependencies and probably for #2776571: EntityAutocomplete should pass the original URI to the selection handler.
- DefaultSelection and ViewsSelection are duplicating a lot of code: protected properties, constructor, create() method.
- Selection handlers should have setters and getters for their configuration.
- Selection handlers should declare their handler default settings in a consistent way in order to get sane defaults. Right now DefaultSelection hardcodes this, see ::buildConfigurationForm(). Default configurations should be sane automatically.
- Anticipating #2786841: Entity reference fields should add selection handler config dependencies, selection handlers should implement calculateDependencies() even with no effect right now.
Proposed resolution
- Introduce a new base class for selection handlers - SelectionPluginBase:
abstract class SelectionPluginBase extends PluginBase implements SelectionInterface, ConfigurablePluginInterface { ... }
- Introduce a trait SelectionTrait to provide shared code between DefaultSelection and ViewsSelection:
trait SelectionTrait { ... }
- Make DefaultSelection and ViewsSelection extend the new SelectionPluginBase class and use SelectionTrait.
- Make Broken selection handler extend SelectionPluginBase .
- Move the common code from DefaultSelection and ViewsSelection into SelectionTrait.
- In SelectionPluginBase provide base implementations for: defaultConfiguration(), setConfiguration(), getConfiguration(), calculateDependencies(), buildConfigurationForm(), validateConfigurationForm(), submitConfigurationForm() and entityQueryAlter().
- SelectionPluginBase::setConfiguration() will take care to assure BC by creating a
handler_settings
level, same as the field config has. Basically all handler settings will live in the root level but will be synced also underhandler_settings
. Add a@todo
to remove the BChandler_settings
key in Drupal 9.0.x. - Implement ::defaultConfiguration() in all core selection handlers that need it.
- Cleanup code from DefaultSelection, UserSelection and ViewsSelection that assures sane-defaults because now the configuration is already sane.
- Remove empty instances of buildConfigurationForm(), validateConfigurationForm(), submitConfigurationForm() and entityQueryAlter() from implementations because we already provide them now in the base class.
Remaining tasks
Continue with #2786841: Entity reference fields should add selection handler config dependencies.
User interface changes
None.
API changes
This change will not introduce any BC break. All custom selectors based on DefaultSelection or ViewsSelection will continue to works as we provide base implementations for all new methods. Custom selections not extending DefaultSelection or ViewsSelection but implementing directly SelectionInterface will not be affected at all.
API changes:
- New base class for selection handlers SelectionPluginBase:
abstract class SelectionPluginBase extends PluginBase implements SelectionInterface, ConfigurablePluginInterface { ... }
- New trait SelectionTrait:
trait SelectionTrait { ... }
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#140 | add_a_base_class_for-2787873-140.patch | 54.6 KB | jibran |
Comments
Comment #2
claudiu.cristea.
Comment #3
claudiu.cristea.
Comment #4
claudiu.cristea.
Comment #6
claudiu.cristeaBoth tests passed in #2. I cannot explain why this was moved to NW. Back to Needs Review.
Comment #7
claudiu.cristea.
Comment #8
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedIn principle, this is a good thing to do. I also tried to introduce a base class for selection plugins but it was too late in the dev cycle to get it in before 8.0.0, so I'm obviously +1 :)
I don't think the generic plugin system should know or care about our (Drupal's) config dependency mechanism. And it's certainly not something to be introduced in a small ER issue like this one :)
Missing a . at the end of the sentence.
Shouldn't we put the parent's method call first like in the other handlers?
Comment #9
claudiu.cristea@amateescu, thank you for review.
#8.1.
Well, we are already doing this in other places of core. For example EntityDisplayBase (which is a config entity) collects dependencies from components (formatters, widgets). Then, in onDependencyRemoval(), asks each component for its own onDependencyRemoval(). In this way plugins are able to react themselves on a dependency removal for the dependencies that they were introduced.
I agree that this seems out of scope but I wanted to prepare #2786841: Entity reference fields should add selection handler config dependencies. I'm dropping now that interface and I will add it later, in #2786841: Entity reference fields should add selection handler config dependencies.
#8.2. Done.
#8.3. Hm. My understanding is that an implementation overrides the parent's settings. I agree that we don't have in this patch a case where a setting overlaps but I still believe that this is correct. I changed also the other implementations.
Comment #10
claudiu.cristeaUpdating the IS to reflect #8 and #9.
Comment #11
claudiu.cristeaComment #12
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedWe can save this discussion for the other issue, but I don't think adding that interface to the plugin system is going to fly, so we might as well just add this method to SelectionInterface.
Also, since this is a task, I think it will have to get in 8.3.x, no?
Comment #13
claudiu.cristeaHm... it's not a feature request. I don't know the policy.
OK, let's discuss this later, in the other issue.
Comment #14
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedOk, I'll let the committers decide if this is 8.2.x material or not :)
Comment #15
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI just remembered that there is also an existing issue for making DefaultSelection implement ConfigurablePluginInterface: #2636322: Entity reference selection plugins cannot be instantiated without configuration
And there is a problem with the config schema, see comments #6, #8, #9 and #10 over there. Not sure yet what' the best way to handle this.. :/
Comment #16
claudiu.cristea@amateescu, Oh, I missed that. Well, this is fixing that too but, yes, I forgot to verify the schema. I'll do this right now. Good point!
Comment #17
claudiu.cristeaComment #18
claudiu.cristeaOK. I fixed all core schemas. Now they are following the classes inheritance. I also adjusted their labels and docs (where case). We should be covered now but let's see the tests.
Comment #19
claudiu.cristeaOuch! The patch was wrong.
Comment #20
jibranIf we are changing the schema then we need a post update hook and upgrade path test.
Comment #21
claudiu.cristea@jibran, no, sorry, this patch is not changing the schema at all. It only reorganises the schema definitions to follow the class hierarchy but, at the end, all existing field handler settings are kept exactly as they are. Can you point me to the diff where you feel that we changed the schema?
EDIT: Also I'm keeping the same schema selectors, so if a contrib extended the existing schema, it will get exactly the same schema in those selectors.
Comment #22
dawehnerIts always nice to have these fallbacks!
Here is a question, why do we move all those dependencies into the base class, even nothing in this class actually uses it?
Comment #23
claudiu.cristeaThey are used in both main (1st tier) implementations: DefaultSelection & ViewsSelection, so it's about code sharing.
Comment #24
claudiu.cristeaYes, it make sense to add also the configuration form methods in SelectionPluginBase. In this way a custom selector that needs no configuration form (it's likely that such cases exists) will not be forced to implement ::buildConfigurationForm(). Not to add that this removes a lot of code duplication.
Also ::entityQueryAlter() is not so much used so added that too to the base class.
Comment #25
claudiu.cristeaAddressing #22. Indeed the injected services are not used in all child classes - for example Broken or a custom selector doesn't need them. Discussed with @dawehner on IRC to provide a patch where we have an intermediary base class that extends SelectionPluginBase and is dedicated only to DefaultSelector & ViewsSelector, supplying them with the injected services. Course, a custom selector that needs all those services, can extend that (BasicSelectionPluginBase). Others can extend directly from SelectionPluginBase.
Comment #26
dawehnerThis looks great, just a minor remark.
We could add a comment like "Extends the base selection plugin with providing a list of helpful services by default".
Comment #27
claudiu.cristeaThank you, @dawehner.
Addressing #26.
Comment #28
claudiu.cristeaUpdated IS to reflect #25.
Comment #29
dawehnerCool, thank you
Comment #30
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI don't really like this extra layer. IMO, traits should be used for code re-use, not class inheritance. But maybe that's just me...
We're losing this @todo.
Comment #31
dawehnerGreat point!
Comment #32
claudiu.cristea@amateescu
Hm. That's not actually a @todo, it contains no "call to action". To do what? Not a big deal to keep it but I felt it has no sense. What we expect from TermSelection::entityQueryAlter() and cannot be done so that we need to add a @todo?
Comment #33
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI would expect it to match what it was doing in ER 7.x :)
Comment #34
claudiu.cristeaOK, switched to trait.
@amateescu,
Unless I'm not reading correctly the code, when looking at D7 EntityReference_SelectionHandler_Generic_taxonomy_term::entityFieldQueryAlter(), I see that, in D8, the access tag is already added in DefaultSelection::buildEntityQuery(), in the next piece of code:
This is the reason why NodeSelection is missing too the entityQueryAlter() implementation.
Correct me if I'm wrong, I'll add the @todo back.
Comment #35
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedI also looked a bit at the D7 code and it's probably ok to remove that @todo. Here's a few more points:
Leftover :)
Conceptually, the configuration of a selection plugin is actually what we currently store in 'selection_settings' plus the 'target_type', because, for example, the 'handler' setting doesn't make sense as we already have an instantiated plugin/handler by the time we are able to call defaultConfiguration() on it.
This is also covered in #2636322-9: Entity reference selection plugins cannot be instantiated without configuration.
Where does this inherit from?
We need to describe the return value a bit.
inject -> injects
We also need to include the default sort direction and remove the default assignment from
\Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection::buildConfigurationForm()
.We also need to include a default for the 'role' filter and remove the default value assignment in
\Drupal\user\Plugin\EntityReferenceSelection\UserSelection::buildConfigurationForm()
.Comment #36
claudiu.cristeaThank you @amateescu,
#35 1, 2, 4-7: Fixed.
#35.3: That is implementing DependentPluginInterface (via ConfigurablePluginInterface).
Comment #37
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedHmm.. #35.2 is not fixed, the 'handler' config was just an example. Please read #2636322-9: Entity reference selection plugins cannot be instantiated without configuration :)
Comment #38
claudiu.cristea@amateescu, I understand now but I (partialy) disagree. The plugin should know for what target entity type he was instantiated. That piece of information is missed. #2786841: Entity reference fields should add selection handler config dependencies (following this issue) is an example why we need that information. So, in my understanding target_type is part of plugin configuration.
Alternatively, we can pass only handler_settings to plugin configuration but in that case we need to store the target_type as protected property and assure a getter & a setter (interface change!). We need that target_type.
Comment #39
claudiu.cristeaQuoting from #2636322-9: Entity reference selection plugins cannot be instantiated without configuration:
And this doesn't look so good. The config structure will be confusing. What if a custom handler asks for a top-level target_type after this issue is committed? This means we need to copy also target_type in top-level as you are suggesting for handler_settings, in order to not break the BC.
Having the top-level keys target_type & handler_settings is the reason why I introduced ::defaultHandlerSettings(). Plugin Configuration and Handler Settings are two different structures even the second is contained in the first.
Comment #40
claudiu.cristeaOK, implemented the new plugin config structure.
Comment #41
claudiu.cristeaComment #42
claudiu.cristeaComment #44
claudiu.cristeaSo, when handler_settings are copied over root level, the operations should NOT be array merge deep because we have to totally override the root values, not merge them.
I left a test case, where an option is passed in BC mode, not converted just to prove that the value takes precedence.
Comment #45
claudiu.cristeaAdd a unit test to check how plugin config is resolving.
Comment #46
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedDiscussed this at length during Drupalcon Dublin with @claudiu.cristea, and the resulting patch looks very good to me :)
Comment #47
catchIt's not clear to me what the implication of this is:
1. Is it possible to have field config that matches the new structure so the bc layer doesn't run?
2. If so why not convert core field types to the new structure via an upgrade path?
I realise setConfiguration() can theoretically come from somewhere that's not config, so we can't necessarily remove the bc layer from there, but we should be able to make it so it doesn't normally run (i.e. have an upgrade path site configuration and a preSave() to convert old default configuration to the new format on import).
If we did all that, we could @trigger_error() in the bc handling and have core tests pass even when deprecations are set to cause a fail - and that gives contrib/custom code a pointer how to update then.
Comment #48
claudiu.cristeaWe cannot provide an update path because we are talking here about plugin config (runtime) and that is not stored. We don't change the field settings -- that would have been something stored. We provide this BC layer for the case some existing code is instantiating the plugin with the old structured configuration.
Comment #50
claudiu.cristeaThis was broken by #2374301: Cannot predict the order of results in taxonomy autocompletion.
Comment #51
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedDon't we want to skip the 'handler_settings' part of the array?
Comment #52
claudiu.cristeaRight.
Comment #54
claudiu.cristeaWe were lucky with #2374301: Cannot predict the order of results in taxonomy autocompletion because this spotted a bug in our patch.
Changes:
NestedArray::mergeDeep()
in::setConfiguration()
because if someone is setting an array value with::setConfiguration()
it expects to retrieve the same value, not a merged one.handler_settings
should not override the same config stored on top level because if someone stored a config in top level, we assume that he/she is aware about the new API.handler_settings
in::defaultConfiguration()
by throwing an exception in::setConfiguration()
.EntityReferenceSelectionUnitTest
to account all these changes.Comment #55
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedLooks great!
Now that I look at this again, wouldn't it be better to set this in
TermSelection::defaultConfiguration()
?Comment #57
claudiu.cristea@amateescu++
Looks so nice, now :)
Thinking more, I had to revert back to merge deep when applying defaults. This is because of buildConfigurationForm(), where due to Ajax requests, it receives only a part of configuration. For example the form posts the sort > field but not sort > direction. Then, when saving the configuration, he should be able to add the missing part from defaults.
Comment #58
claudiu.cristeaCoding standards.
Comment #60
claudiu.cristeaSmall docs fix.
Comment #61
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedLooks great now!
Comment #62
jibranSurprisingly, DER 8.x-1.x branch is also green this patch applied to the core. Patch looks good. There is no BC break. +1 for RTBC.
Comment #64
claudiu.cristeaCI error. Back to RTBC.
Comment #66
claudiu.cristeaAgain CI error. Back to RTBC as per #61.
Comment #68
catchCan we move the bc handling out to a separate protected method, and also @trigger_error('...', E_USER_DEPRECATED) when it finds the old keys. That will help to isolate it as well as inform people when they're relying on it. Can also mark the actual protected method @deprecated and for 9.x removal then.
Comment #69
claudiu.cristea@catch, I got your point and I like the idea. Unfortunately I cannot externalize in a single protected method, so probably will not look 100% clean. Is this what you wanted? I'm setting back to RTBC to get @catch feedback on his requested change.
Comment #70
claudiu.cristeaTesting also that the deprecation message is not triggered.
Comment #71
claudiu.cristeaMore polishing.
@amateescu, I think the changes since #68 (see @catch request) need a review. Setting back to NR.
Comment #72
catch@claudiu.cristea that change looks great :) We don't have a standard way to test deprecation methods yet. I think it's fine to add a non-standard test but will ping alexpott since he's been thinking about that more.
Comment #73
claudiu.cristea@catch, yes, I was thinking that we probably need a trait that can be reused.
Comment #74
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedWe usually use "ensure" for stuff like this.
And the "Bc" part of the new method names looks a bit weird, but I also don't have any better suggestion right now :)
Comment #75
claudiu.cristea@catch, I can't find an issue for error assertions. I added #2834021: Provide error test assertions via a trait.
Comment #76
claudiu.cristea@amateescu, thank you.
Used 'ensure' and renamed. This looks much more better.
Comment #77
jibranI think this is ready.
Comment #79
claudiu.cristeaComment #80
claudiu.cristeaI'm adding an interdiff to previous RTBC version for a better review of changes requested by @catch in #68.
Comment #82
claudiu.cristeaComment #84
claudiu.cristeaComment #86
claudiu.cristeaComment #88
claudiu.cristeaComment #90
claudiu.cristeaReroll.
@catch, in #80 there's a relevant diff for the changes you required in #68.
Comment #92
claudiu.cristeaUnrelated failures.
Comment #94
claudiu.cristeaOnly removed the reference to the current Drupal version.
Comment #96
claudiu.cristeaReroll.
Comment #97
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedImproved the title a bit :)
Comment #99
claudiu.cristeaReroll because array() > []
Comment #101
claudiu.cristeaComment #103
claudiu.cristeaComment #104
alexpottWe need a change record for this.
The BC layer looks good and already complies with the policy apart from the fact it is missing a link to a change record.
Comment #105
claudiu.cristeaAdded CR https://www.drupal.org/node/2870971 and linked in code docs.
Comment #107
claudiu.cristeaFixed the unit test left after deprecation message has been changed.
Comment #108
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedThe change record looks very good and detailed, back to RTBC :)
Comment #109
claudiu.cristeaBecause #2488860: Bring phpunit bridge into drupal and use it for unit tests and simpletest to handle Deprecation is in the unit test added here, needs to be reworked.
Comment #110
claudiu.cristeaThe deprecation error is triggered in several places, so I added the
@group legacy
tag to the class.Comment #111
jibranThanks @claudiu.cristea
Comment #113
claudiu.cristeaIt doesn't like quotes?
Comment #114
jibranIt is green again so back to RTBC.
Comment #115
catchCommitted/pushed to 8.4.x, thanks! Sorry this took a while to get back to.
Comment #117
jibranCan it be considered for backporting to 8.3.x? It unblocks the bug #2786841: Entity reference fields should add selection handler config dependencies.
Comment #118
catchIt's an API addition and new deprecation, so normally we wouldn't backport it even to fix a major bug, we do make occasional exceptions for very serious issues where the disruption is unlikely, but some of those cases have broken patch releases too.
Either way we should get that issue fixed in 8.4.x first, then figure out if/how to backport both afterwards.
Comment #119
jibranThank you @catch for clearing that up.
Comment #120
BerdirHm, looks like this broke config schema definitions in custom/contrib, if it extended from entity_reference_selection. See #2881459: Build failing: Schema errors for field.field.node.paragraphed_content_demo.field_paragraphs_demo with the following errors: field.field.node.paragraphed_content_demo.field_paragraphs_demo:settings.handler_settings.target_bundles .
Should we do a follow-up to add the schema back with a @deprecated comment?
Comment #121
cilefen CreditAttribution: cilefen commented#2881874: Fatal DefaultSelection has colliding constructor definitions coming from traits on PHP 5.5.9
Comment #122
xjmI think we need to revert this rather than a followup. We can do so once the PHP 5.5 tests in #121 continue running to confirm or rule out that that issue is related to PHP 5.5 (versus a contrib module).
Comment #123
cilefen CreditAttribution: cilefen commentedI reproduced the fatal noticed in #2881874: Fatal DefaultSelection has colliding constructor definitions coming from traits on PHP 5.5.9.
Comment #125
cilefen CreditAttribution: cilefen commented#113 causes the following when using the node add form on PHP 5.5.9:
Comment #126
xjmWhat's very interesting is that I triggered a PHP 5.5 test run on QA before we reverted this and it passed, so something weird is going on, but we still need to figure out exactly what was causing the fatal and fix it.
Comment #127
Anonymous (not verified) CreditAttribution: Anonymous commentedThe problem is detected with 5.5.9, and Drupal 8 system requirements point to this version https://www.drupal.org/docs/8/system-requirements/web-server. But testing is performed with the version php-5.5.38 (https://dispatcher.drupalci.org/job/php5.5_mysql5.5/5091/parameters/). It seems this was done to reduce the number of random fails.
Comment #128
xjmThanks @vaplas. Apparently it also reduced the number of non-random fails. ;) I wonder what the best way to test 5.5.9 is? Maybe we should have a minimum supported version environment that does not run nightly but can be added to run a specific test.
Comment #129
jibranIs it NW just for #125? or does it include #120 as well?
Comment #130
jibranRe-rolled #113 with patch from #2881874-7: Fatal DefaultSelection has colliding constructor definitions coming from traits on PHP 5.5.9 and improved the function name hence two interdiffs. Please add @tetranz to credit list from #2881874: Fatal DefaultSelection has colliding constructor definitions coming from traits on PHP 5.5.9.
Comment #131
claudiu.cristeaLet's add a comment to explain why we're doing this.
Comment #132
jibranHow about now?
Comment #133
claudiu.cristeaLooks good.
The entire issue was reviewed and committed once, in #116, I'm reviewing only #130 and #132, so I'm able to RTBC (if gets green).
Comment #134
larowlannit:in case there are numeric keys, we should use the third argument here for type safety sake and avoid weird side-effects.
Are we sure we want a trait with a constructor? This is going to lead to some bad DX in my opinion.
Couldn't this be a named method like 'initialize()' or something that would make this less messy?
off topic: we really need that issue that converts _none to a constant to land
Comment #135
larowlannote: we could call the initialize/setup method in a constructor on the base class so we don't have the confusion
Comment #136
larowlanFor the record we have 119 traits in core.
None of them have constructors
Comment #137
claudiu.cristeaAre we gonna rework this issue? This was committed once after was in RTBC state from Dublin Con 2016 (september 2016) until late May 2017 (!!!) Such a complex work should not have been reverted, but the PHP 5.5.9 issue should have live in its own follow-up. Very disappointed :(
However:
#134.1: Numeric keys in the 1st level of the array is a malformed configuration. Probably that should be treated distinctively as check and throw an exception if somebody wants to set such setting. But it's not in the scope of this issue, shouldn't we create a followup to deny numeric keys in the 1st tier?
#134.2: Initially the code from trait was placed in a base class but we moved to trait, see #30, as is only code reusing. So, we are only reusing code here, isn't this the purpose of traits? I don't understand where's the DX loss? If an implementation wants another constructor, it will use the trait and extend/override the trait implementation.
Comment #138
Anonymous (not verified) CreditAttribution: Anonymous commentedI could not get the fall for php 5.5.9.
Environment (OpenServer):
Steps:
Drupal\Tests\node\Functional\NodeCreationTest
and add code to setUp():Comment #140
jibranFixed #34.
Comment #141
claudiu.cristeaThank you.
Comment #143
catchCommitted/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!
Comment #146
cilefen CreditAttribution: cilefen commented