DomainStorage currently depends on RequestStack for hostname inference, scheme detection, and path prefix matching. Entity storage handlers should be pure data layers with no request coupling.
Changes
- New
DomainResolverservice owning all request-dependent and hostname-normalization logic:getRequestHostname(),getRequestScheme(),getRequestPath(),normalizeHostname(),loadByHttpHost(),isRegisteredHttpHost(),resolveDomain(),matchByPathPrefix(),matchPathPrefix(). Holds theRequestStack,domain.www_prefix,domain.allow_non_ascii,domain.path_prefixparameters. - New
Drupal\domain\Utility\MachineName::fromString()helper replacesDomainStorage::createMachineName()static. DomainStoragepure data layer:RequestStack,ignoreWwwPrefix,containerproperties become@deprecated;prepareHostname()/createHostname()/createMachineName()/getDefaultScheme()become deprecated wrappers delegating todomain.resolverorMachineName::fromString().- Storage method names unchanged (
loadByHostname()/loadMultipleByHostname()) — docblocks clarified that they expect normalized hostnames. - Move
matchPathPrefix()fromDomainentity toDomainResolver, deprecating the entity method. DomainNegotiationContextgainsDOMAIN_MATCHED_NONE/EXACT/ALIASconstants as the canonical home (the constants conceptually belong on the negotiation outcome holder, not on the negotiator service). The constants onDomainNegotiatorInterfacebecome deprecated aliases referencing the new location — class-constant reference is fully transparent BC.DomainNegotiatorthin orchestrator:negotiateActiveDomain()collapses to a singledomainResolver->resolveDomain($context)call. Legacy entry points (setRequestDomain,setActiveDomain,negotiateActiveHostname,negotiateByPathPrefix,setHttpHost,getHttpHost,isRegisteredDomain) become deprecated wrappers with@trigger_error().- Switch read-only consumers to
DomainNegotiationContext:DomainCacheContext,DomainAccessManager,DomainAccessNodeHooks,DomainAccessEntityHooks,DomainConfigUiFormHooks,DomainSourceFormHooksdrop the fullDomainNegotiatorInterfacedependency in favor of the lighter outcome holder. Reduces service-graph dependencies and circular-wiring risk.DomainAliasHooksdrops itsDomainNegotiatorInterfaceimport entirely. Domain::preCreate()absorbs auto-fill logic (hostname/name/scheme/is_default) that used to live inDomainStorage::create().- Consistent variable naming:
$http_hostfor raw HTTP hostnames,$hostnamefor normalized (www-stripped). - Deprecation cycle: deprecated in domain:3.1.0, removed in domain:4.0.0.
Bug fixes in passing
$_SERVER['https'](lowercase, never set by anything) becomes$_SERVER['HTTPS']in the request-scheme detection path.is_defaultdefault for the first domain flips from(int)($default === FALSE)(always 0 becauseloadDefaultId()returns NULL, not FALSE) to(int) is_null($default)(correct: 1 when no default exists yet).
Test coverage
New DomainResolverTest kernel test covers each public method on DomainResolverInterface in isolation: getRequestHostname, getRequestScheme (http/https), getRequestPath (with and without an active request), normalizeHostname (www_prefix on/off), loadByHttpHost (known/unknown), isRegisteredHttpHost (known/unknown), resolveDomain (exact-match and default-fallback). 13 tests, 59 assertions. Closes the test-coverage gap for the resolver service. The integration paths (DomainHookTest, DomainPrefixTest, etc.) keep covering the full negotiator chain.
Updated callers
DomainNegotiator, DomainAliasHooks, DomainServerBlock, DomainPrefixPathProcessor, Domain entity, DomainForm, DomainRedirectResponse, Drush commands (domain + domain_alias), DomainAliasValidator, DomainAliasPatternConstraintValidator, six read-only consumers switched to DomainNegotiationContext, tests, docs.
Behavior changes
Strict superset for normal flows; deprecation warnings on legacy usage. Two notable changes:
DomainNegotiator::setRequestDomain($hostname, $reset)now ignores both arguments. Existing callers passing a specific hostname silently negotiate against the live request. The deprecation message explicitly calls this out and points at the migration path.is_defaultdefault for the first domain is now correctly1instead of0(incidental bug fix).
Coordination
Rebased onto post-merge 3.x. Recent merges (#3588155, #3588168, #3588169, #3588175, #3588176) all touched the alias-side of the pipeline; this issue's domain-side resolver/negotiator refactor doesn't conflict with any of them.
Out of scope (separate issues)
- Removing the legacy deprecated wrappers on
DomainNegotiator/DomainStorageand the deprecated constant aliases onDomainNegotiatorInterface. To be done indomain:4.0.0. - Updating any external modules that still depend on
DomainNegotiatorInterfacefor read-only access. They keep working through the deprecated path; they should migrate toDomainNegotiationContextat their convenience.
Supersedes #3583389.
Issue fork domain-3583423
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 #6
mably commentedSummary of changes
This MR introduces a new
DomainHelperservice that centralizes all request-dependent logic previously scattered acrossDomainStorage,DomainNegotiator, and theDomainentity class. The goal is to makeDomainStoragea pure data layer with noRequestStackdependency, which is how Drupal entity storage handlers are designed to work.What changed
New
DomainHelperservice (domain.helper) owns:hook_domain_request_alter)loadByHttpHost(),isRegisteredHttpHost(),resolveDomain()createMachineName()DomainStorageis now a pure data layer:RequestStackor$ignoreWwwPrefixdependenciesloadByHostname()/loadMultipleByHostname()expect normalized hostnames (documented in phpdoc)prepareHostname,createHostname,createMachineName,getDefaultScheme) delegate toDomainHelpervia BC wrappers with@trigger_error()DomainNegotiatoris now a thin orchestrator:setRequestDomain()reduced from ~40 lines to 4 lines of delegation toDomainHelper::resolveDomain()getContext()method on the interface returnsDomainNegotiationContextsetHttpHost,getHttpHost,negotiateActiveHostname,negotiateByPathPrefix,isRegisteredDomain) delegate toDomainHelperDomainAliasHooksno longer depends onDomainNegotiatorInterface:DomainNegotiationContext+DomainHelperhook_domain_request_alter. Now it reads from the lightweight context object and callsDomainHelperfor path matching only — it never triggers negotiation.Bug fix included:
$_SERVER['https'](lowercase) was silently broken in the oldDomainStorage::getDefaultScheme(). Fixed to$_SERVER['HTTPS'].Concrete benefits
DomainStoragecan now be tested without a request stack.DomainHelpercan be mocked independently for unit tests.DomainAliasHooksno longer holds a reference to the negotiator, so it cannot accidentally trigger re-negotiation during alter hooks.loadByHostname()expects normalized input,loadByHttpHost()accepts raw input. No more guessing whether a method normalizes internally.DomainHelper::resolveDomain()instead of being spread across negotiator and storage.BC layer
All deprecated methods have
@trigger_error()notices and delegate to the new service. Existing code calling deprecated methods will continue to work without changes. Default values forhostname,name, andschemeare now set inDomain::preCreate(), so$storage->create()with an empty array still works as before.Deprecation cycle: deprecated in domain:3.1.0, removed in domain:4.0.0.
28 files changed across domain, domain_alias, and their tests/docs.
Comment #7
mably commentedRenamed
DomainHelpertoDomainResolver— "Helper" was too vague for a class whose 9 out of 10 methods resolve HTTP requests to domain entities.DomainResolveraccurately describes its responsibility and follows Symfony/Drupal naming conventions (ArgumentResolver,ControllerResolver, etc.).Updated across 26 files: class name, service ID (
domain.helper→domain.resolver), all variables and docblocks, deprecation messages, and documentation (EN/FR/ES).Comment #8
mably commentedComment #9
mably commentedUpdate issue body to reflect the latest scope on !358: new DomainResolverTest kernel test (closes test-coverage gap for the resolver service); DOMAIN_MATCHED_* constants moved to DomainNegotiationContext as canonical home, with deprecated aliases on DomainNegotiatorInterface for transparent BC; Domain::preCreate() auto-fill move; two bug fixes in passing ($_SERVER[https] casing, is_default default).
Comment #10
mably commentedFix: replace stray > HTML entity with literal > in body.
Comment #12
mably commented