task: #3583423 Introduce DomainResolver service, make DomainStorage a pure data layer

Summary

Introduces a new DomainResolver service that owns request-coupled logic, leaving DomainStorage as a pure entity-storage handler with no RequestStack dependency. DomainNegotiator becomes a thin orchestrator that delegates resolution to DomainResolver and keeps state on DomainNegotiationContext. Six consumer classes that only read the active domain are switched from injecting DomainNegotiatorInterface to the lighter DomainNegotiationContext. The DOMAIN_MATCHED_* match-type constants move to DomainNegotiationContext (the outcome holder) with deprecated aliases preserved on the interface for BC.

Issue: #3583423.

Why

DomainStorage previously had RequestStack injected just to compute the request hostname / scheme. That coupling made it awkward to test, and conflated "load domain entities" with "resolve the active request". The split:

  • DomainResolver (new) — owns getRequestHostname(), getRequestScheme(), getRequestPath(), normalizeHostname(), loadByHttpHost(), isRegisteredHttpHost(), resolveDomain(), matchByPathPrefix(), matchPathPrefix(). Holds the RequestStack, domain.www_prefix, domain.allow_non_ascii, domain.path_prefix parameters.
  • DomainStorage — keeps createInstance() legacy plumbing but requestStack, ignoreWwwPrefix, container properties become @deprecated. prepareHostname / createHostname / createMachineName / getDefaultScheme become deprecated wrappers that delegate to domain.resolver or to the new Drupal\domain\Utility\MachineName::fromString() helper.
  • DomainNegotiatorsetRequestDomain(), setActiveDomain(), negotiateActiveHostname(), negotiateByPathPrefix(), setHttpHost(), getHttpHost(), isRegisteredDomain() all become deprecated wrappers; negotiateActiveDomain() collapses to a single domainResolver->resolveDomain($context) call.
  • DomainNegotiationContext — gains the DOMAIN_MATCHED_NONE/EXACT/ALIAS constants as the canonical home (the constants conceptually belong on the outcome holder, not on the negotiator service interface).
  • Domain::preCreate() absorbs the auto-fill logic (hostname/name/scheme/is_default) that used to live in DomainStorage::create().
  • Bug fix in passing: $_SERVER['https'] (lowercase, never set by anything) becomes $_SERVER['HTTPS'] in DomainResolver::getRequestScheme().
  • Bug fix in passing: is_default default flips from (int)($default === FALSE) (always 0 because loadDefaultId() returns NULL, not FALSE) to (int) is_null($default) (correct: 1 when no default exists yet).

Commits

  • 6010947c task: Introduce DomainResolver service, make DomainStorage a pure data layer
    • Adds DomainResolver, DomainResolverInterface, Utility\MachineName. Deprecates the legacy DomainStorage / DomainNegotiator methods that did request-handling. Updates Domain::preCreate() for the auto-fill move.
  • 2cef5cb4 refactor: DomainResolver::resolveDomain() now mutates DomainNegotiationContext directly
    • Reshapes the resolver's main entry point from (string $hostname): ?DomainInterface to (DomainNegotiationContext $context): void. The negotiator's negotiateActiveDomain() becomes a thin delegation.
  • c91944a4 refactor: Switch consumers from DomainNegotiatorInterface to DomainNegotiationContext
    • Six classes that only need to read the active domain (or its id) are switched from the full DomainNegotiatorInterface to the lighter DomainNegotiationContext: DomainCacheContext, DomainAccessManager, DomainAccessNodeHooks, DomainAccessEntityHooks, DomainConfigUiFormHooks, DomainSourceFormHooks. Reduces service-graph dependencies and circular-wiring risk.
  • 37559068 fix: Trigger deprecation notice on legacy negotiator entry points
    • Adds the missing @trigger_error() calls to DomainNegotiator::setRequestDomain() and setActiveDomain() for parity with the other deprecated methods on the class. Spells out loudly in both the negotiator and DomainNegotiatorInterface that setRequestDomain()'s $hostname and $reset arguments are no longer honored — the underlying DomainResolver::resolveDomain() reads the live request hostname unconditionally. Anyone relying on the old behavior must push a Request onto request_stack (or call getActiveDomain(TRUE)) instead.
  • ff2ad861 test: Add DomainResolverTest kernel test
    • 13 kernel test methods covering each public method on DomainResolverInterface (getRequestHostname, getRequestScheme http/https, getRequestPath with and without an active request, normalizeHostname with and without the www_prefix container parameter, loadByHttpHost known/unknown, isRegisteredHttpHost known/unknown, resolveDomain exact-match and default-fallback). Closes the test-coverage gap for the resolver service.
  • 900080cd refactor: Move DOMAIN_MATCHED_* constants to DomainNegotiationContext
    • The match-type constants are conceptually a property of the negotiation outcome, not the negotiator service. Move the canonical home to DomainNegotiationContext; keep the constants on DomainNegotiatorInterface as deprecated aliases referencing the new location. Internal usages migrated; DomainAliasHooks drops its DomainNegotiatorInterface import entirely (it now depends only on DomainNegotiationContext). External code that reads DomainNegotiatorInterface::DOMAIN_MATCHED_* continues to work via the alias — class-constant reference is fully transparent BC.

Behavior changes

Strict superset for normal flows; deprecation warnings on legacy usage. The two notable changes:

  1. DomainNegotiator::setRequestDomain($hostname, $reset) ignores both arguments. Existing callers passing a specific hostname now silently negotiate against the live request. The deprecation message explicitly calls this out and points at the migration path. Verified via drush spot-check (#3583423/!358 (merged) commit 37559068) that the deprecation fires correctly.
  2. is_default default for the first domain is now correctly 1 instead of 0 (incidental bug fix).

Test plan

  • PHPCS / PHPStan level 2 / cspell clean on changed files.
  • All migrated kernel/functional tests pass locally (DomainHookTest, DomainPrefixLanguageTest, DomainPrefixTest, DomainAliasPrefixTest, DomainAliasHostnameTest, DomainAliasPatternResolverTest, DomainAccessUnpublishedGrantsTest, DomainSourceRouteMatcherTest, DomainSourceRouterProviderTest).
  • New DomainResolverTest (commit 5) — 13 tests, 59 assertions, OK.
  • Drush spot-check of all 7 deprecated methods on DomainNegotiator — every @trigger_error() fires correctly with the standard Drupal-format message and change-record link.
  • CI phpunit (D11 + previous-major D10) — pipeline running.

Coordination

Rebased onto post-merge 3.x at 73e2c6a3. The recent merges (#3588155, #3588168, #3588169, #3588175, #3588176) all touched the alias-side of the pipeline; this MR's domain-side resolver/negotiator refactor doesn't conflict with any of them. Clean rebase.

Out of scope (separate issues)

  • Removing the legacy deprecated wrappers on DomainNegotiator / DomainStorage and the deprecated constant aliases on DomainNegotiatorInterface. To be done in domain:4.0.0.
  • Updating any external modules that still depend on DomainNegotiatorInterface for read-only access. They keep working through the deprecated path; they should migrate to DomainNegotiationContext at their convenience.
Edited by Frank Mably

Merge request reports

Loading