Problem/Motivation
In #2416109: Validate configuration dependencies before importing configuration we are considering validating config dependencies before import. The problem is there are many ways to get your active configuration to contain config entities that depend on config entities that do not exist. For example, if you install the standard profile and delete the article content type its corresponding RDF mapping config entity will not be deleted. Active configuration should never get into a state where config entities depend on other config entities that do not exist.
Proposed resolution
Fix dependencies when configuration is deleted. Fixing means either deleting or updating the configuration so the dependency no longer exists. The user will be informed what is going on on the confirmation form.
More screenshots are available in #53.
Remaining tasks
- Add tests
- Review
- Commit
User interface changes
New list on configuration entity delete confirm forms.
API changes
None.
Comment | File | Size | Author |
---|---|---|---|
#65 | 2416409.65.patch | 44.52 KB | alexpott |
#65 | 63-65-interdiff.txt | 2.03 KB | alexpott |
#63 | 2416409.63.patch | 44.61 KB | alexpott |
#63 | 62-63-interdiff.txt | 3.76 KB | alexpott |
#62 | 2416409.62.patch | 43.54 KB | alexpott |
Comments
Comment #1
alexpottHere's an implementation - let's see how much breaks.
Comment #3
alexpottGoing a step further and just deleting the dependencies if they can't be fixed.
Comment #5
BerdirI think we have to delete, yes.
I think it should be possible to delete a node type with fields for example. It is (almost, completely?) impossible to first delete all fields, view and form displays, translation settings, ... that depend on that.
Comment #6
alexpottSo the only test fails in #3 were due to testing a situation that should not occur and entity reference default values causing dependencies that can not be fixed. Patch attached fixes both of these things. It adds the ability for field type plugins to fix fields on dependency removal - I don't think we should be deleting a field if the default value of an ER field is removed.
Comment #7
catchTrying a re-title.
Comment #8
catchAlso this is at least major.
Comment #9
alexpottThe changes to entity reference are implicitly covered in EntityReferenceIntegrationTest but need an explicit test. We need testing of ConfigEntityBase::preDelete too.
Another thought is should we add something to ContentEntity::preDelete() as well. All content dependencies are soft and we implicitly guarantee your config will not crash your site if the content is missing - but things like an ER field could clean themselves up if the default value goes missing.
Comment #10
alexpottFields can use this code to have less custom code.
Comment #12
alexpottSo we have deletes happening inside of deletes which change the dependency chains :)
Comment #13
alexpottOMG... this actually works...
Just apply the patch - install standard and go and delete a content type! You'll see a list of affected configuration on the confirm form.
Comment #14
alexpottTalked about this with @catch on IRC - I've come round to thinking this is critical. Configuration in your active store that has missing dependencies is an indication of a broken site. It also makes reliable configuration synchronisation impossible.
Comment #15
catchAlso I think this should be tagged D8 upgrade path.
Thinking of situations like:
1. Delete a config entity with dependencies that this patch would affect, such as a node type.
2. Core adds an update that needs to resave a set of config entities (like form displays) that depend on the node type.
3. The form displays for the deleted node type can't be saved, so can't be updated.
We don't yet have patches doing #2, but a lot of that is because we're not writing hook_update_N() yet. This can happen just with clicking around the UI and no contrib modules.
Patch itself looks good to me so far.
Comment #16
Wim LeersIncredibly elegant patch! Mostly nits below.
Missing docblock.
synchronization… sorry!
This needs rephrasing; the grammar doesn't really work.
Should be only one blank line.
So how does the user know which will be deleted, which will be updated?
This is a bit strange.
80 cols.
The underscore can be removed.
Is it really only passed dependencies that are known to be dependencies? AFAICT this is passed every deleted dependency?
Missing
@inheritdoc
.I think the comment should be updated.
Comment #17
alexpottThanks for the review @Wim Leers
Also prevented the dependency stuff from appearing on content entity delete forms for now.
A potential elephant in the room is permissions - what happens if the user making the change does not have the permissions to make changes to or delete the dependent configuration entities?
Comment #18
alexpott@xjm and I talked about the potential elephant. If you have the permission to delete a node type (
administer content types
) and you don't have the ability to administer fields for that particular content type (administer node fields
) then deleting the node type will still delete the fields. I think the same rule applies here. If you have the permission to delete a config entity, then, in the process of doing the delete, you also have the permission to make the system handle the consequences.Comment #19
catchThat looks reasonable to me as well.
Comment #20
bojanz CreditAttribution: bojanz commentedNo longer applies after #2361775: Third party settings dependencies cause config entity deletion (minor conflicts in ConfigEntityBase and ConfigEntityInterface), here's a reroll.
EDIT: Tests failed due to "MySQL has gone away". Haven't seen that on our testbot before.
Comment #24
alexpottre #20 we now need to save if onDependency removal does something.
Comment #25
Wim Leers#17.5: sure!
"The in this"
Comment #26
alexpottFixed #25.
Re #17.5 turns out it is already possible and will significantly improve the performance of the code that loops around - see #2420107: Determine which config entities can be fixed and which will be deleted when a dependency is removed
Comment #30
alexpottComment #31
Gábor Hojtsy#2014955: Deleted bundles do not have their language configuration deleted implemented this one-off for language configuration and added a test. I think the fix itself needs to be removed here because it would happen automatically but the test should be kept to prove it still works.
Comment #32
Wim LeersAlex told me in IRC that he'd prefer to land #2420107: Determine which config entities can be fixed and which will be deleted when a dependency is removed first, so that #17.5 can be fully addressed in this patch. But this patch can still address #31 in the mean time, it'll just have to wait to solve #17.5, any other aspects are still up for review.
Comment #33
alexpottComment #34
alexpottEntityManager::loadEntityByConfigTarget()
Comment #35
alexpottComment #36
Wim Leers#34: AWESOME!!! :)
Comment #38
alexpottSo bundles do not have to be configuration entities... I'm a sad panda :(
Comment #39
larowlanduring a during a
as much as I don't agree with it, don't our traits have to have the Trait suffix? Same fashion as Interfaces?
traits can include other traits - I assume this is here to prevent complaints about missing methods by an IDE - any reason we can't embed the string translation trait direct in here?
Fairly sure this can be injected, happy to be wrong.
sweet
Is there any circumstances in which someone would sub-class this? If so should we use static instead?
Should we have static properties bleeding into non-static methods? That seems like it might open a world of pain.
start deleting one field config - flag is set - attempt to delete another one, but its skipped because the flag from the previous one?
Do we need more tests to go with the new functionality?
Comment #40
larowlanAlso #34 is the business - @alexpott++
Comment #41
Wim Leers#38: Shortcut Strikes Again. (Off the top of my head.)
Comment #42
alexpottre #41 nope not shortcut - it's the test entity type - it uses state for bundles.
re #39
Yes we need tests.
Comment #43
Berdir3. To expand on that, I had the same problem with my delete form trait. The problem is that you can not have the same trait multiple times in your class. So if you have Trait X and Z and class A extends from B and uses trait X, then you get php errors if both the parent class B and the trait X use Trait Z.
Comment #44
Wim Leers#43: thanks, that's helpful info! It also makes PHP traits less useful/less widely applicable :/
Comment #45
alexpottAdded tests of both the API and UI - found that the preDelete was not using the right method to find and fix/delete config entities :)
Comment #47
alexpottSo... KeyValueConfigEntityStorageTest swaps out config entity storage which makes the assumptions about how we can load all config from the configuration store incorrect. To be able to manage dependencies we need to be able to assess all config together. I think the expectations of being able to swap out the storage on the config entity level are realistic at this point. To quote @Berdir
I think we should repurpose #2393751: Document that KeyValueEntityStorage is not scalable beyond a few hundred entities to remove the ability to use a storage class that does not extend ConfigEntityStorage.
Comment #49
alexpottWhat we want is the url object :(
Comment #52
xjm(Just clarifying that the other issue is not, in fact, sentient.)
Not clear on this bit though:
Get "core" inconsistent with what? "Inconsistent" how? Maybe examples of how "core" becomes "inconsistent" would help? Presumably, deleting something and leaving around dependent things is one of the ways (based on the title of this issue). :)
Comment #53
xjmEmbedding all the screenshots. :) Making someone leave the issue to see the screenshots is, well, like not having an indoor bathroom. Sure, outhouses work, but...
Comment #54
xjmComment #55
alexpottComment #56
alexpottComment #57
alexpottComment #58
catchSeems like a good change to make regardless.
Comment #59
catchOnly found two very minor issues. The screenshots look great. I think the 'Field' deletion will be a bit jarring for people who are coming from Drupal 7's field instances (I had to do a double take) but that terminology is not introduced here.
Nit: why bother counting? The foreach would be a no-op on an empty array anyway. If we want to save the call to field definition then could still skip the count and just do normal bool condition here.
Is it not creating a dependency or cleaning up a previous dependency?
Comment #60
BerdirLooks great, some feedback below, mostly minor stuff, but the save() in field config, and the default value change might be incorrect?
I didn't read all the previous reviews/comments, ignore me if something has been discussed already.
The changed version isn't really more correct, it just violates other coding standards, why touch this here?
Should this call the parent or not? I think you prefer to not doing that unless there is a specific reason ;)
Do we have a coding standard for this?
entity forms also have the form() method, I don't really know when it is better to overwrite which, I guess this is fine..
Did you have to add this, or just to be explicit? Content entities have their own delete form class and won't work with this anyway, but I guess there could be other types of entities, theoretically ;)
"Implementations should not save the entity" ?
Shouldn't we only remove the specific item instead of resetting the complete array?
Contains ;)
Do we also need to update the @todo below, maybe just remove it?
Nice example why it might be good to call the parent, eventually we might add something to Entity::preDelete() ;)
Why not use write "Dependencies are tested in \class\name" ? :)
Comment #61
alexpottThanks @Berdir re #60
Comment #62
alexpottAfter discussing with @Berdir decided to key the array of config and content dependencies to make it easy to search. Added a test for
EntityReferenceItem::onDependencyRemoval()
.Address @catch's point in #59 too
Comment #63
alexpottFixed the test and improved it.
Comment #64
Berdirnot sure about coding standard for inline anonymous functions, should there be a space after ;?
weird that we need the reset her, why is that exactly? anything is reset after the drupalPostForm(), so it is the node type delete that somehow doesn't update the cache? but resaving the field should update that?
Comment #65
alexpottFixed both. The reset was not necessary - over thinking.
Comment #66
BerdirThanks. Nothing left to complain about, this looks great, awesome work, took us a long time to get there, was not sure we'd make it :)
Comment #69
catchCommitted/pushed to 8.0.x, thanks!
Comment #72
lokapujya#2657734: calculateDependencies() return value is double documented. FYI, in case anyone wants a different comment. Actually, we can use your opinion on what the wording should be.