Issue #3586001 by mably: persist DomainNegotiationContext across kernel rebuilds
Problem
ModuleInstaller::install() and ::uninstall() rebuild the service
container mid-request. The rebuild replaces every stateful service
with a fresh instance, including DomainNegotiationContext which is
populated by DomainSubscriber::onKernelRequestDomain() at
kernel.request time. Nothing re-populates it after the rebuild, so
any post-install URL generation (e.g. batch_process()) asking
DomainConfigFactoryOverride for per-domain overrides sees an empty
context and returns no override.
Fix
Tag domain.negotiation_context with persist so
DrupalKernel::persistServices() transfers the same instance from the
old container to the new one across the rebuild.
The context is pure state with no dependencies, so it is safe to share
across the rebuild. Services that depend on it (DomainNegotiator,
DomainConfigFactoryOverride) pick up the persisted instance through
normal constructor injection in the rebuilt container. Simpler and
cheaper than re-running negotiation, and covers every rebuild trigger,
not just the two ModuleInstaller paths.
Additional fix exposed by the persist tag
DomainConfigOverrideEditable::save() had a latent bug: when a form
saves a subset of a config's keys, the schema-cast step
(updateExistingKeysInNestedArray($moduleOverrides, $data)) copies
$data (base + current form changes) back onto $moduleOverrides,
overwriting any previously-saved override values for keys not touched
by this form. Previously this never fired because the test process's
empty context caused addConfigurationsToCurrentDomain() to silently
register [null] as the overridable domain, so saves fell through to
base storage. With the context preserved, saves actually go through
the override path and the bug surfaces.
Fixed by initializing $data in
DomainConfigFactoryOverride::getOverrideEditable() with a deep merge
of base + existing domain data, so the cast-copy step sees the correct
override values.
Tests
Added kernel test DomainContextAfterKernelRebuildTest exercising
both install and uninstall paths in one method. Verified that the
test fails cleanly on the pre-fix tree (null \!== 'example_com') and
passes with the persist tag applied.
DomainConfigUiSavedConfigTest::testSavedConfig had an assertion that
relied on $config->get() returning the base value, which only worked
when the test process's context happened to be empty. Switched to
getOriginal('name', FALSE) so the assertion is robust regardless of
whether a domain is active on the test process.
Verified locally: all 76 domain + domain_config kernel tests pass,
and the two FunctionalJavascript tests (DomainConfigUiSavedConfigTest
and DomainConfigUIMultiFormTest) pass.