Follow-up to #3592820, prompted by tester feedback in comment #8. Two related changes to the Domain Config Switcher submodule.
1. Scope the editing selection to the form (URL query, not the tempstore)
Today the selected domain is stored in a per-user PrivateTempStore, so once you switch on one form, every other compatible configuration form keeps editing that domain until you clear it. That cross-form stickiness is a footgun: you can land on another form without realising you are still editing a different domain, and save to the wrong place.
Instead, carry the selection in a URL query argument (config_domain) so it is scoped to the form it was made on:
- The switcher reads the selection from the current request query argument and applies it with setEditingDomain() scoped to this form's configuration names.
- The Switch action redirects to the current form with the selection in the query. The GET reload discards unsaved edits and shows the selected domain's values, and the form's own POST keeps the query so Save still targets that domain.
- A fresh request to any other form has no selection, so it uses the negotiated domain. Navigating away drops the editing context automatically.
This is the URL-based behaviour suggested in the feedback. A form-state-only approach (rebuild without redirect) does not work cleanly: on a rebuild Drupal repopulates fields from the submitted input, so the form would display the previous domain's values unless the input were wiped.
2. Document that only #config_target forms are supported
The switcher only attaches to ConfigFormBase forms whose fields use the core #config_target API. It is intentionally not shown on forms that expose their configuration through getEditableConfigNames() instead - for example the Appearance / theme settings form - because those read their configuration in buildForm(), before hook_form_alter runs, so the switcher cannot retarget the values they display. Supporting them would require setting the editing context before the form is built, which is the global approach that broke routing and was deliberately abandoned.
The README Limitations section now states this explicitly and names the Appearance form as a known unsupported example.
Status
Implemented in the merge request for this issue.
Issue fork domain_extras-3592980
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
mably commentedComment #4
mably commentedComment #5
mably commentedComment #7
mably commentedComment #9
hanoiiThanks! I am not sure I understood #2 - there's no other "proper" way to provide this functionality in those forms as well. I am sure you would have done it if there is, but just if you can and wish to elaborate that would be great. Thanks.
Comment #10
mably commentedSure, happy to expand — the limitation is really about timing.
The switcher works by retargeting which domain's config a form reads and writes. It can only do that cleanly for forms built on core's
#config_targetAPI: there, the stored value is loaded into#default_valueby a deferred#processcallback that runs afterhook_form_alter(). The switcher runs inform_alter, so it sets the per-form domain context first, and the#processstep then reads the selected domain's values. Save goes back through the same target, so it writes to that domain too. The whole retarget stays scoped to that one form.Forms using the older
getEditableConfigNames()pattern read their config directly inbuildForm(), which runs beforeform_alter. By the time the switcher runs, the values are already baked in from whatever domain was active, and there is no declarative element-to-config-key map (the thing#config_targetgives us) to rewrite them after the fact.So is there another way? Mechanically yes: flip the active config domain for the whole request via the negotiator, and even
buildForm()-based forms would then show the selected domain's values. But that is exactly the request/session-wide behavior we deliberately moved away from in this issue (and #3592820) — it would leak the selection into every other config read in the request: other forms, blocks, rendered output. That cross-form stickiness is what this change set out to fix.#config_targetis the only seam that keeps the retarget scoped to a single form, which is why I limited it to those rather than reaching for a global override.If a specific form needs per-domain editing (Appearance being the obvious one), the clean path is to get it onto
#config_targetupstream — core is gradually converting forms to it — at which point the switcher picks it up automatically with no change needed here.Comment #11
hanoiibut can't the form_alter change the #default_value based on the selected domain (out of the request_ui) as it was the case with any form alter and then do some fiddling with the validate/submit handlers?
I see it being hacky and I understand core should leverage that, but core is slow. Anyway I don't want to burden you or become insistent, you are doing an amazing job. Just throwing out ideas just in case there's one you like, if not to implement it yourself to maybe willing to accept it if someone else does (like me :D). But if it's just not possible without doing it in the negotiator, I understand. I wouldn't want to go that route either.
Comment #12
mably commentedThanks, and no need to hold back, the ideas are welcome.
You are right that
form_altercan overwrite#default_valueafterbuildForm()has run. The real blocker is the mapping: to rewrite the values you need to know which form element maps to which config key, and forgetEditableConfigNames()forms that knowledge lives only inside the form's ownbuildForm()/submitForm(). There is no generic, declarative element-to-config-key map to read back out. That is exactly the gap#config_targetfills, which is why the switcher can drive those forms generically but not the older ones.So the read side is doable, but only by special-casing each form (hardcoding "this element is system.theme:default", and so on). That does not scale and is fragile across versions. The save side has the same mapping problem, plus the submit has to be redirected to the selected domain's override rather than the active one.
The only thing that handles arbitrary legacy forms generically is switching the config domain context for the whole request, i.e. the negotiator route, which brings back the cross-form leakage this issue set out to remove. We agree that is not the way.
Where I would gladly take a patch: an opt-in mapping declared per form or per config name, so a specific form like Appearance can be supported explicitly without a request-wide override. If you find a clean, form-scoped approach I have missed, open an MR and I will review it happily.