Add a Domain Render Context submodule to render out-of-band output in another domain's context

Adds a domain_render_context submodule: one service that runs a callback as if the request had come from another domain, so output built outside the request it belongs to (an email, a PDF, a queue item) carries that domain's links, configuration, path prefix and theme.

What it switches

  • The domain negotiation context: per-domain configuration overrides, the domain path prefix, and the target domain's own language negotiation configuration.
  • The scheme, host and port of the router request context: where UrlGenerator::generateFromRoute() reads the host of an absolute URL from. Switching the negotiation context alone leaves the host of the request doing the work, since Domain's own host rewrite (DomainPathProcessor) returns early unless a domain option is set on the individual URL.
  • The active theme, read from the render domain's own system.theme configuration, switched last so it goes through that domain's overrides whatever module wrote them (domain_config directly, or domain_theme_switch). A multi-domain site routinely gives each domain its own theme, and the active theme is negotiated once per request, so without this an email or a PDF renders with the templates, assets and logo of the domain doing the work. A mail theme applied inside the callback (Mail System, say) still wins, since it switches later. A theme that fails to initialize is logged and the current one kept.

Nothing else: no interface language, no session, no current user, and no request pushed onto the request stack.

API

  • inDomain($domain, callable $callback): restores the previous context on return and on an exception, and returns whatever the callback returns.
  • enter($domain): returns the closure that restores the previous context, for work that does not fit in one callback. Idempotent.

Accepts a domain entity or a machine name. An unknown machine name is logged and the current context kept, so a deleted domain never turns a notification into a fatal error.

Notes

  • Outbound only, documented in the README and the docs page: never call it while a page is being routed or rendered for the browser, since the negotiated domain also drives inbound path processing, routing and language negotiation for the page being served.
  • Uses only API available from Domain 3.0 through 4.x, and calls nothing deprecated for removal in 4.0 (DomainNegotiationContext, DomainNegotiatorInterface::getActiveDomain()).
  • When the context held no domain at all on entry (cron, the CLI), setDomain() cannot take NULL, so the restore re-negotiates from the current request rather than leaving the render domain's overrides active for the rest of the run. A nullable signature upstream would make that exact; noted as a follow-up in the issue.

Tests

10 kernel tests, 64 assertions: configuration and request origin switch, the active theme switches and is restored (both when a theme was already active and when none was), path prefix follows the switch with no per-URL option, restore on a normal return, on an exception, on nested calls, on an unknown domain, on a run that started with nothing negotiated, and an idempotent restore closure.

phpcs (Drupal, DrupalPractice, warnings on), phpstan and cspell clean.

Edited by Frank Mably

Merge request reports

Loading