Problem/Motivation
When a config entity implementing EntityWithPluginCollectionInterface
is reacting on dependency removal, it should ask the plugins from the collections if they want to take some action when their dependencies are about to be removed.
This issue is adding a generic solution for the one fixed in #2562107: EntityDisplayBase should react on removal of its components dependencies.
Proposed resolution
- Add code in
ConfigEntityBase::onDependencyRemoval()
that tests if the entity implementsEntityWithPluginCollectionInterface
. If so, iterate through collections and if there are specific plugin dependencies being removed, call the plugin::onDependencyRemoval()
method to allow the plugin to react. - Add a new method
onDependencyRemoval()
in\Drupal\Component\Plugin\DependentPluginInterface
. - Add empty
onDependencyRemoval()
implementations in base classes implementingDependentPluginInterface
with a simplereturn FALSE;
. - Implement one use-case.
- Tests.
is reacting on dependency removal, it should ask the plugins from the collections if they want to take some action when their dependencies are about to be removed.
Remaining tasks
None.
User interface changes
None.
API changes
New method \Drupal\Component\Plugin\DependentPluginInterface::onDependencyRemoval()
.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#70 | reroll_diff_49-70.txt | 9.19 KB | ravi.shankar |
#70 | 2579743-70.patch | 23.9 KB | ravi.shankar |
#49 | 2579743-49.patch | 24.78 KB | Sam152 |
#49 | interdiff.txt | 9.73 KB | Sam152 |
Comments
Comment #2
claudiu.cristeaComment #3
tim.plunkettComment #4
claudiu.cristeaLet's try a first patch.
Comment #5
yched CreditAttribution: yched commentedRight, and most of the code currently in EntityDisplayBase::onDependencyRemoval() & the ::getPluginRemovedDependencies() helper can be moved straight up to ConfigEntityBase::onDependencyRemoval() for that.
But the tricky part is then how each specific ConfigEntity class can react to the plugin::onDependencyRemoval() result (whether "OK, I adapted, ask me for my new settings" or "screw it, I'm not changing myself"). AFAICT that logic is specific to the business logic and implementation of each "config entity with a plugin collection" : EntityDisplays do something, others will need something else.
So it seems ConfigEntityBase::onDependencyRemoval() can provide the general mechnism, but will need to defer a separate submethod to handle the result of plugin::onDependencyRemoval(). And that submethod will likely need to be defined in EntityWithPluginCollectionInterface (it only makes sense if the ConfigEntity is a EntityWithPluginCollectionInterface).
Then, same problem as below with adding a method with existing direct implementations, we'll likely need a new subinterface for that...
Problem is, there are no such base classes ? Most implementations of that interface implement it directly, and we can be sure that that is also the case in contrib/custom code out there, and adding that method would break them.
So I fear this means adding a new subinterface of DependentPluginInterface to receive that new method :-/
Comment #9
xjmThis has come up again in #2830581: Fix ContentModeration workflow type to calculate correct dependencies.
Bumping to major because this is really confusing and it's hard to realize you need to add such a method to your specific entity type interface, and screwing up your entity's dependency management can cause data integrity issues or data loss, so it would be good to find some solution or mitigation for this.
Comment #10
tim.plunkettThis would help resolve #1764380: Merge PluginSettingsInterface into ConfigurableInterface, as we need a replacement for \Drupal\Core\Field\PluginSettingsInterface::onDependencyRemoval()
Comment #11
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedHere is an initial version of this. I think the BC concerns are valid, so here is an interface which extends DependentPluginInterface.
I think the meaty tests will come from the implementations of onDependencyRemoval, which are up to the plugins to provide and test. The current test only verifies plugins that implement the interface have a chance to act. Most of the docs on the interface are copied from ConfigEntityInterface::onDependencyRemoval, but should still apply.
Comment #12
tim.plunkettGreat start!
First, due to #2851574: DependentPluginDefinitionInterface and DependentPluginDefinitionTrait should be in Drupal\Core not Drupal\Component we shouldn't repeat the mistakes of core, let's have this live in Drupal\Core.
Second, since PluginSettingsBase already provides implementations of these methods (due to PluginSettingsInterface as mentioned in #10), let's use this new interface there.
Third, the docs on the new RemovableDependentPluginInterface shouldn't mention entities at all. I think this is just because they were copy/pasted as you mentioned.
Finally, we need to untangle \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval(). However that may prove trickier than expected, since neither EntityFormDisplay or EntityViewDisplay use EntityWithPluginCollectionInterface correctly...
Here's a patch addressing my first two points.
Comment #13
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedIt seemed to make sense to mention their relationship to config entities and how removing dependencies will preserve those entities, but I guess that connection isn't explicit based on the interface.
Fixed the docs and resolved a @todo pointing to this issue. EntityDisplayBase looks like a hairy one, is that in scope for this issue?
Comment #15
claudiu.cristeaThe relevant part from @yched's comment:
EDIT: Short version:
Comment #16
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThanks for summarising that, I think step 1 and 3 are already done in #13.
From what I can tell, getPluginRemovedDependencies is used to detect if a plugin couldn't handle removing it's dependencies. This happens so that the display can just disable the component. I think it's pretty unique for a config entity to understand how to disable some part of itself based on a plugin having unresolved dependencies, it seems like the most most complex use case to the point where I don't know if it's worth using getPluginRemovedDependencies elsewhere? All removed dependencies can be passed to all plugins, I don't see a need for them to be filtered in advance.
Re: suggestion of untangling EntityDisplayBase::onDependencyRemoval in #12, not sure how to go about that. I don't know if it makes sense for a plugin to manage and remove dependent config, because each one isn't responsible for it's own isolated tree of config that it can deal with. A plugin can't move itself from 'content' to 'hidden'. Keen to here if @tim.plunkett has any thoughts on how to approach this.
NR for random fail.
Comment #17
claudiu.cristeaOn order to keep the clarity, I think we should not mix here any use-case.
Comment #18
claudiu.cristeaComment #19
claudiu.cristeaEDIT: Moved the comment to #21
Comment #21
claudiu.cristeaSorry, the patch from #19 was malformed. Interdiff to #13.
This is only a PoC.
Comment #23
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedIs there a concrete use case for onUnresolvedPluginDependencyRemoval? It seems like something unique to entity displays, based on their ability to disable fields.
Also, I think it makes sense to actually use the interface outside of a test when it's introduced. The deletions show an example of what the interface gives you free.
Comment #24
claudiu.cristeaThe new methods should return if the change was done or not. The tests are still not fixed.
If a plugin is not able to fix the removed dependencies, the host entity will be removed. In a lot of cases we want to avoid this. This provides a generic way to do something else than let the entity die. For example, some entities will want to remove the plugin from collection, others will decide to replace the plugin with a fallback plugin or will decide to disable themselves (like the entity display does).
Comment #25
claudiu.cristeaComment #27
claudiu.cristeaAgain wrong patch. Sorry!
Comment #29
claudiu.cristeaHere's the test fix.
Comment #30
claudiu.cristeaFixing test. Reverting PluginSettingsBase for now.
Comment #32
tim.plunkett#2830581: Fix ContentModeration workflow type to calculate correct dependencies is now in, so we should address that introduction of onDependencyRemoval() as well.
Comment #33
claudiu.cristeaHere's a try with ImageStyle and the effects plugin collection. But It needs a nice Kernel test.
Modified the EntityWithDependentPluginCollectionInterface::onPluginDependencyRemoval and ::onUnresolvedPluginDependencyRemoval signatures to take also the list of removed dependencies as argument.
Comment #34
claudiu.cristea@tim.plunkett, @Sam152, I think right now we need a partial (more architectural) review about the direction. Then we need to refresh the IS based on the conclusions.
Comment #36
claudiu.cristeaDammit!
Comment #38
claudiu.cristeahuh?
Comment #40
claudiu.cristeaComment #41
tim.plunkettThis is handled in ConfigEntityBase::preSave(), do we really need it again here?
Comment #42
claudiu.cristeaI missed that, thank you. I don't know :)
Comment #43
tim.plunkettI think we should switch to a TDD approach here, it will help avoid adding extra code. Let's define what the goals are, write test coverage for it, and then adjust the internals as needed.
Comment #44
claudiu.cristea@tim.plunkett, here's a proposal:
Comment #45
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedThis all looks pretty good, but I think the onPluginDependencyRemoval method needs to be removed from the picture. I don't think it does anything in the example in the patch and I don't see why anything it could conceivably do couldn't be part of the method for the individual plugin. The "let the config entity react" comment isn't really needed if the point was to refresh the config entity, as @tim.plunkett suggested that's the job of the collection.
NW for that + @todos.
Wouldn't this already happen because the image style fails to remove the module as a dependency and onUnresolvedPluginDependencyRemoval removes it?
Comment #46
claudiu.cristeaTrying to work on this weekend.
Comment #47
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedHi claudiu.cristea, are you still actively working on this?
Comment #48
claudiu.cristeaComment #49
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI've tried to jump start this up again by rerolling and implementing some of #45.
I don't really understand the test cases or the fails, mocking them for specific return values on a specific number of calls is hard to follow but will find some time to have a look at it all again.
Comment #50
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedComment #51
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedI think we also need to check the BC policy, I think interface additions when there is a suitable base class are allowed.
Comment #52
claudiu.cristeaThis is wrong for the reasons you can find by analysing and understanding how
EntityDisplayBase::onDependencyRemoval()
works. Actually, that is a particular case that we need to expand in a general case here. Also, I've explained that in #44.2 and 3.EDIT: The test
\Drupal\Tests\field_ui\Kernel\EntityDisplayTest::testComponentDependencies()
, that I wrote when fixingEntityDisplayBase
, is a case that reflect what we need to solve here. There's also a relevant comment in\Drupal\field_test\Plugin\Field\FieldWidget\TestFieldWidget::onDependencyRemoval()
.EDIT2: So, there are 2 kind of reactions that the entity should be provided. First, when dependency removal are resolved and second, when there are still left unresolved dependencies after the first step.
Comment #53
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commented#44 doesn't have an explanation.
I don't think the pattern of the config entity having specific awareness of the plugins private configuration (and thus changes when the plugin does) is a pattern that should be encouraged by the interfaces, no matter what is in EntityDisplayBase. Generally speaking, the plugin configuration should be self-contained (you know, pluggable...) and there shouldn't typically be much reason for a config entity to change when a plugin cleanly resolves a dependency removal. The plugin config should be encapsulated and irrelevant to the config entity.
Perhaps the patch should have a concrete example of where this method makes sense instead of shoehorning it into ImageStyle. You could remove that whole method from that entity and it would behave the same.
Maybe step 7 should be resolved before core APIs are written to cater for it:
Comment #54
claudiu.cristeaDiscussed with @tim.plunkett on IRC and we agreed for the next plan:
EntityDisplayBase
to properly use plugin collections.This IS will be updated accordingly. I will handle these tasks.
Comment #55
Sam152 CreditAttribution: Sam152 as a volunteer and at PreviousNext commentedIs there an issue for fixing EntityDisplayBase?
Comment #56
claudiu.cristea@Sam152, will be handled here.
Comment #67
smustgrave CreditAttribution: smustgrave at Mobomo commentedWonder if someone could provide an updated issue summary? Been about 5 years so a lot has changed.
Comment #68
andypostI bet it's about to kinda "behavior.detach()" to allow plugins manage destruction for example
Comment #69
andypostit still very handy to have this reaction, it moves forward action plugins at least
It still makes sense as separate interface
Comment #70
ravi.shankar CreditAttribution: ravi.shankar at OpenSense Labs commentedAdded reroll of patch #49 on Drupal 10.1.x. still needs work for comment #69.
Comment #72
Wim LeersRelated, with 162 followers: #3056633: It is not possible to uninstall a module that provides a filter plugin - Drupal\Component\Plugin\Exception\PluginNotFoundException: The "{filter}" plugin does not exist.