Problem/Motivation
Config entities seem to not mirror the behaviour enforced by underlying configuration API.
The underlying configuration API behaviour is described as:
- Normally you would get the configuration as "immutable", so it can not saved & in fact will even throw an exception when you still try it (But it will contain corresponding config overrides if any are specified)
- If you want to change the configuration you need to get it as "editable", so you can save it(But it will not contain any overrides)
But for config entities this is not described as clearly. The only information you get is that for "admin"- routes (/admin/*) if config entities are used as route parameters, their values are ensured to be not-overriden (like e.g. routes that use an entity form); but only on those routes.
Config enties actually do not distinguish between a "immutable" & "editable" variants.
This allows for the following two code flows:
a) Loading an not-overriden version of the config entity, change it, save it:
- load/change: the entity contains not-overriden, possibly changed, values
- preSave: the not-overriden values can be compared agains the "original" version (the "original" does contain overriden values)
- save: the not-overriden values may be cast to match the configuration schema & are stored as configuration
- postSave: the not-overriden values (which may have been cast to match the schema) can be compared agains the "original" version (the "original" does contain overriden values)
b) Loading an overriden version of the config entity, change it, save it
- load/change: the entity contains overriden (possibly changed) values (the changed values might be the ones that were actually overriden or not)
- preSave: the overriden values can be compared agains the "original" version (the "original" does contain overriden values)
- save: the overriden values may be cast to match the configuration schema & are stored as configuration (At this point overriden values might be stored although they were not actively modified)
- postSave: the overriden values (which may have been cast to match the schema) can be compared agains the "original" version (the "original" does contain overriden values)
This shows on one hand that as @druken monkey described above the code in the preSave/postSave hooks/entity methods may be called with overriden or not overriden values. On the other hand it shows a bug that allows overriden values to accidentaly be leaked into the actual "editable" configuration.
Proposed resolution
- Introduce a flag to config entities like: isEditable / isImmutable / mayContainOverrides
- Per default load config entities as: !isEditable / isImmutable / mayContainOverrides
- Disallow saving entities in the ConfigEntity storage (by throwing an exception) which are: !isEditable / isImmutable / mayContainOverrides
- Maybe add a method on config entities / simply use the ConfigEntityStorage::loadOverrideFree to get instances that are flagged as: isEditable / !isImmutable / !mayContainOverrides
- Since all config saved would now be ensured as not-overriden, preSave hooks/entity methods should get an not-overriden original
- postSave hooks/entity methods would also get a not-overriden original
- If someone needs an overriden original in preSave, they could simply load an overriden version (as the changes are not yet persisted)
- If someone needs an overriden original in a postSave hook / method, they could could theoretically set it as a seperate Field like maybe 'overridenOriginal' in a corresponding preSave hook/ method so they cant it get's passed along to postSave hook/ method
- A generic 'overrideOriginal' solution would also be possible but would need to be implemented at the storage level (the "original" should be unset at the end, but because entity->postSave ist called before the entity hooks this could not be done in the config entity class)
This way the beghaviour would be more in line with the underlying configuration API & And preSave/postSave hooks/entity methods work with a more predictable context.
Remaining tasks
Introduce a new base class, interface & storage- Decide what to do with the existing config entity related classes (merge changes into them, deprecate them, change them to not allow config overrides ...)
API changes
- Introduces new Classes & an Interface / expands the existing ones (OverrideSupportingConfigEntityInterface,
OverrideSupportingConfigEntityBase&OverrideSupportingConfigEntityStorage)/li>
Data model changes
No changes, the overriden data only resides in the class and is not persisted.
Original report by drunken monkey
In the Search API (#2682369: Fix problems with overridden config entities) we've discovered problems when using config overrides with config entities, which I think could affect other modules as well, and might therefore be better solved in Core than in each of them separately.
The root cause of the problem is basically that an entity update will mix non-overridden and overridden entities in non-obvious ways.
Specifically, config entities are by default not overridden when loaded for admin routes, where most of the changes probably occur. But when saving such an entity, the original that is loaded is loaded with overrides, so when any pre-/post-save code tries to assess the changes done to the entity, it will inevitably end up with wrong results when comparing overridden properties.
Ideally, I think, both the "current" and the "original" entity used would show all the overridden values, so that really only those properties that effectively changed their value are actually detected as changes.
However, especially for the pre-save code, this is probably not that easy to do, and doing it just for post-save might just add more confusion.
So, this is more meant as the start of a discussion, or a request for other solutions.
Do you even agree that this is a problem? And, if so, how can we solve it?
| Comment | File | Size | Author |
|---|---|---|---|
| #44 | 2744057-nr-bot.txt | 16.45 KB | needs-review-queue-bot |
| #27 | immutable-config-override-support-restart--2744057-27.patch | 36.2 KB | Alumei |
| #3 | test-to-prove-overide-problem--2744057-3.patch | 5.18 KB | Alumei |
Comments
Comment #2
Alumei commentedProblem
I think the main problem is actually, that config entities do not mirror the behaviour enforced by underlying configuration API.
The underlying configuration API behaviour is described as:
Configuration API
But for config entities this is not described as clearly. The only information you get is that for "admin"- routes (/admin/*) if config entities are used as route parameters, their values are ensured to be not-overriden (like e.g. routes that use an entity form); but only on those routes.
Config enties actually do not distinguish between a "immutable" & "editable" variants.
This allows for the following two code flows:
a) Loading an not-overriden version of the config entity, change it, save it:
b) Loading an overriden version of the config entity, change it, save it
This shows on one hand that as @druken monkey described above the code in the preSave/postSave hooks/entity methods may be called with overriden or not overriden values. On the other hand i fear it shows a bug that allows overriden values to accidentaly be leaked into the actual "editable" configuration.
proposed Solution
The solution for this two problems i think is:
This way the beghaviour would be more in line with the underlying configuration API & And i thing preSave/postSave hooks/entity methods work with a more predictable context.
Comment #3
Alumei commentedI made this patch to prove my point.
I think this shows that the described inconsistencies are not only unpleasant but also point out a problematic bug.
Comment #5
Alumei commentedNow I think fixing this bug in the way proposed in #2 will actually also solve the inconstistency problems.
That's because per default the preSave/postSave hooks/entity methods would only be called on the 'not-overriden' values & therefore as pointed out before behave more predictable.
Comment #6
Alumei commentedI tried to implement the solution incremantally in 5 different ways as I am unsure what is acceptable within the existing BC policy:
Comment #12
Alumei commentedI tried to fix the problems in Version A and Version B (as repesentative for C,D & E) by improving the override detection.
The general idea is to set the respective immutability information when retrieving the configuration using
\Drupal\Core\Config\Config.The *2.. patches change the methods on
\Drupal\Core\Config\Configto only add the mutability information when beeing retrieved for config entities.Comment #13
Alumei commentedWrong interdiff ...
Comment #18
Alumei commentedTried to fix the fails by relocating the immutability info assignment back to the storage & using a new method containsOverrides() on \Drupal\Core\Config\Config to accuratly assign it.
Comment #20
Alumei commentedComment #22
Alumei commentedComment #23
Alumei commentedComment #27
Alumei commentedI had some time to think about it more and came to the following conclusion:
This problem only affects the active usage of config overrides which in turn seems to be an edge-case in and of it self. There fore it is propably best to go with an opt-in approach.
With the attached patch core would provide a special interface
OverrideSupportingConfigEntityInterface, a corresponding base classOverrideSupportingConfigEntityBase& strorage classOverrideSupportingConfigEntityStoragefor config entities extending their existing counterparts.Both
OverrideSupportingConfigEntityBase&OverrideSupportingConfigEntityStoragecontain checks that prevent overriden config values from beeing saved via config entities.What do you think?
Comment #28
Alumei commentedUpdated issue summary!
The previous patch is ment as a foundation, but what to do with it ...
If we would go with it, it would be a non-breaking api addition (i believe). Still the issue would remain that users of config overrides could have those overrides leak unexpectedly.
I see 5+ possible solutions for that:
Comment #29
Alumei commentedComment #35
joegl commentedIs this issue related?
Comment #42
geek-merlinRan into this.
Comment #44
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #46
drunken monkeyLinking #2910353: Prevent saving config entities when configuration overrides are applied, which I think would solve the same problem in a more radical, but therefore probably simpler way: By preventing overridden config entities from being updated at all.