Problem/Motivation

Url objects can now specify the domain as an option, see #3574800: Allow Url objects to specify the Domain as an option

However, the domain path alias is only applied when the URL is generated on the active domain

Steps to reproduce

  1. Create a node that is published on Domain One, but not Domain Two
  2. Assign a domain path alias to the node for Domain One
  3. Generate a URL for the node on Domain Two
  4. Notice the domain path alias is not applied

Proposed resolution

Apply domain path alias processing on outbound URLs with a domain option

Remaining tasks

  1. Write a merge request
  2. Review
  3. Commit

User interface changes

The domain path alias is applied to URLs with a Domain option

API changes

To be determined

Data model changes

To be determined

Command icon 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

idebr created an issue. See original summary.

mably made their first commit to this issue’s fork.

mably’s picture

Status: Active » Needs review

Problem

When code sets $url->setOption('domain', 'example_com') directly (without going through DomainSourcePathProcessor), domain path aliases are not applied. Core's PathProcessorAlias (priority 300) aliases the path for the active domain, and nobody re-aliases it for the target domain.

This is because domain_path currently resolves aliases only via hook_domain_source_alter, which is fired by DomainSourcePathProcessor (priority 90) — but that processor skips processing when $options['domain'] is already set.

Solution

New outbound path processor DomainPathOutboundProcessor at priority 85 (after DomainSourcePathProcessor at 90, before DomainPathProcessor at 80):

  1. Skips external URLs or paths without $options['domain']
  2. Reverse-resolves the current path back to the internal system path (undoing core's active-domain aliasing)
  3. Looks up the correct alias for the target domain via getAliasByPathAndDomain()

The processor is safe for double-processing: when hook_domain_source_alter has already replaced the path (domain_source flow), the reverse-resolve typically fails (returns the path unchanged), and the target-domain lookup also returns the same path. Net result: path stays unchanged.

Priority chain

Priority Processor Role
300 Core PathProcessorAlias Aliases path for active domain
90 DomainSourcePathProcessor Determines source domain, fires hook_domain_source_alter
85 DomainPathOutboundProcessor Re-aliases path for target domain
80 DomainPathProcessor Rewrites base_url to target domain

Changes

  • New: src/HttpKernel/DomainPathOutboundProcessor.php
  • Modified: domain_path.services.yml — registered the new service at priority 85
  • New: tests/src/Functional/DomainPathDomainOptionTest.php — 3 test methods covering alias resolution, fallback without alias, and non-canonical route exclusion

Depends on

#3574800 (domain module: DomainPathProcessor with the domain URL option)

idebr’s picture

Perhaps the following priority chain is preferable?

310 DomainSourcePathProcessor -> sets $options['domain'] if not set before

305 DomainPathOutboundProcessor
-> calls DomainPathAliasManager::getPathByAliasAndDomain() only if $options['domain'] is set
-> sets $options['alias'] = TRUE to prevent duplicate processing by PathProcessorAlias since it includes a fallback to the core path alias: https://git.drupalcode.org/project/drupal/-/blob/main/core/modules/path_...

hook_domain_source_alter is no longer needed

idebr’s picture

DomainPathOutboundProcessor::resolveDomain() is a good example of how allowing both strings and Domain objects in a variable leads to messy code :)

mably’s picture

Current architecture (this MR)

Priority Processor What it does
300 Core AliasPathProcessor Aliases path for active domain (via decorated DomainPathAliasManager)
90 DomainSourcePathProcessor Determines source domain, fires hook_domain_source_alter, sets options['domain']
85 DomainPathOutboundProcessor Reverse-resolves active-domain alias back to internal path, then re-aliases for target domain
80 DomainPathProcessor Rewrites base_url to target domain

The problem: core's AliasPathProcessor (priority 300) runs first and aliases the path for the active domain. Then our processor at 85 has to undo that (reverse-resolve with getPathByAlias) and redo it for the target domain. This reverse-then-forward dance works but is wasteful.

@idebr proposed architecture

Priority Processor What it does
310 DomainSourcePathProcessor Determines source domain, sets options['domain']
305 DomainPathOutboundProcessor If options['domain'] is set, aliases path for target domain, sets options['alias'] = TRUE
300 Core AliasPathProcessor Checks $options['alias'] — if TRUE, skips. Otherwise aliases normally
80 DomainPathProcessor Rewrites base_url to target domain

Key insight: by running before core's AliasPathProcessor (priority 305 > 300), you alias the path for the target domain first. Then you set $options['alias'] = TRUE, which is core's built-in mechanism to tell AliasPathProcessor "aliasing is already done, skip it" (AliasPathProcessor.php line 45). No reverse-resolve needed.

Also: hook_domain_source_alter would no longer be needed because DomainPathOutboundProcessor handles the aliasing directly — DomainPathFormHooks::domainSourceAlter() was only used to swap the alias for the target domain, which this processor now does.

Trade-offs

This approach is cleaner because:

  • No wasteful reverse-resolve + re-resolve
  • Uses core's own $options['alias'] skip mechanism
  • Removes the hook_domain_source_alter implementation

Considerations:

  • DomainSourcePathProcessor moving from 90 to 310 is a bigger change in the domain module — it needs to work correctly running before core aliasing instead of after
  • Any code relying on hook_domain_source_alter to modify the alias would break
  • The path passed to DomainSourcePathProcessor at 310 would be the internal path (e.g. /node/1) rather than an alias, which might actually be simpler

I'll rework the implementation to follow this approach — seems like a solid improvement.

mably’s picture

@idebr could you validate this issue please? https://www.drupal.org/project/domain/issues/3575227

idebr’s picture

I suggest naming the service DomainPathAliasProcessor: the core service is somewhat misnamed missing the 'Alias' part, and Outbound is an implementation detail that easily becomes outdated if the implementation changes

mably’s picture

Service renamed.

mably’s picture

Looks Domain Path 2.x will now require Domain 3.x.

Don't know what to think about that.

idebr’s picture

#11 An option is to tag 2.0.0 first to allow projects to update, and merge this issue in 2.1.x that requires drupal/domain:^3.0?

Core path alias processing can be skipped entirely if the $option['domain'] is set, since getPathByAliasAndDomain includes a fallback to Core path alias processing, see https://git.drupalcode.org/project/domain_path/-/blob/2.x/src/DomainPath...

mably’s picture

I'll go directly to 3.x to match domain module versioning.

Implemented your suggestion that should definitely improve performance.

mably’s picture

Version: 2.x-dev » 3.x-dev

Should be ready for merge.

Is it ok for you @idebr?

idebr’s picture

Status: Needs review » Reviewed & tested by the community

Looks good to me 👍

  • mably committed 0490ac62 on 3.x
    task: #3575163 Apply domain path alias processing on outbound URLs with...
mably’s picture

Status: Reviewed & tested by the community » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mably’s picture

Looks like our latest changes have introduced some regressions.

Investigating.

mably’s picture

@idebr ok, found the culprit, $options['language'] is not populated anymore after moving DomainSourcePathProcessor at priority 310.

The domain_source field can be translated, so it's currently totally broken.

How could we fix that?

May be we can get the current language by ourselves if not provided in $options['language'].

What do you think?

idebr’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.