Two latent bugs in DomainAliasStorage's alias-pattern matching pipeline, surfaced while auditing MR !358 (issue #3583423). Both were originally bundled in that MR but they are unrelated to the resolver refactor and have been split out for review.

1. DomainAliasStorage::sort() never returns -1

The uasort comparator used by getPatterns() is broken:

public function sort($a, $b) {
  if ((substr_count($a, '*') > substr_count($b, '*')) || (strlen($a) < strlen($b))) {
    return 1;
  }
  return 0;
}

A valid PHP comparator must be able to return any of -1, 0, or 1. This implementation only ever returns 0 or 1, so uasort sees "a == b" or "a > b" but never "a < b". Patterns that should have moved up never moved. The docblock claims the list ends up sorted by specificity, but it does not.

Replace with a spaceship-operator comparator:

public function sort($a, $b) {
  return substr_count($a, '*') <=> substr_count($b, '*')
    ?: strlen($b) <=> strlen($a);
}

Behavior change: the pattern lists returned by getPatterns() (and any other caller of this comparator) now actually sort by specificity — fewer wildcards first, longer string as tiebreaker — which is what the original code claimed to do.

2. DomainAliasStorage::buildPortPatterns() emits patterns in the wrong priority order

The previous implementation interleaved per-pattern variants:

foreach ($patterns as $index => $pattern) {
  $new_patterns[] = $pattern . ':*';                          // wildcard port
  if (default_port) $new_patterns[] = $pattern;               // bare hostname
  if ($port) $new_patterns[] = $pattern . ':' . $port;        // explicit
}

For pattern list [p1, p2, p3] on a default port that produced [p1:*, p1, p2:*, p2, p3:*, p3]. loadByHostname() walks this list in order and returns the first DB hit, so p1:* shadowed p1 — even though a bare-hostname alias is more specific than a wildcard-port one. Practical effect: an alias defined as staging.example.com would lose to staging.example.com:* on the same hostname, despite the request having no explicit port.

Reorder so hostname-only patterns are emitted first (in pattern-list order), then port-bearing variants:

$is_default_port = is_null($port) || intval($port) === 80 || intval($port) === 443;
$new_patterns = $is_default_port ? $patterns : [];
foreach ($patterns as $pattern) {
  if (!is_null($port)) {
    $new_patterns[] = $pattern . ':' . $port;
  }
  $new_patterns[] = $pattern . ':*';
}

The new order on a default port becomes [p1, p2, p3, p1:port?, p1:*, p2:port?, p2:*, …], matching the documented intent: more specific aliases take precedence over wildcard-port ones.

MR

Branch ready locally with the two fixes as separate commits. Will push to the issue fork and open the MR once this issue has a number.

Notes

  • The existing DomainAliasSortTest only checks pattern presence (array_diff), not order. Should follow up with an order-asserting test, but I'd like to land the fix first.
  • No deprecation needed: both functions are internals; the second one (buildPortPatterns) is private.

Issue fork domain-3588155

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

mably created an issue. See original summary.

mably’s picture

Assigned: mably » Unassigned
Status: Active » Needs review
mably’s picture

Two follow-up commits added per the pre-merge audit:

  • 82c933e7 — inline comment explaining why bare-hostname patterns are omitted on non-default ports (no behavior change).
  • 2389c39bDomainAliasSortTest now asserts the full ordered output of getPatterns() instead of just pattern presence. The previous fixture had an order that did not match the actual implementation, plus a duplicate entry and a missing pattern; both bugs were masked by array_diff(). The new fixtures use explicit-port hostnames so the assertion is deterministic regardless of the test runner.

Release notes

Behavior change worth surfacing in the Domain 3.1.0 release notes:

In domain_alias, the alias-pattern matcher now respects specificity. A bare-hostname alias (e.g. staging.example.com) wins over a :* alias (e.g. staging.example.com:*) on the same hostname when no explicit port is given, and an explicit-port alias (e.g. staging.example.com:8080) wins over :* on the same port. Sites that previously relied on the buggy :*-first ordering should review their alias config — the new ordering is what the surrounding documentation and docblocks already claim.

MR description on git.drupalcode.org has the full list of commits and a Release notes section ready to copy into the project release notes when 3.1.0 ships.

Tested locally (Drupal 11.x + MySQL): testAliasSort passes with the new fixtures.

  • mably committed a17f0986 on 3.x
    fix: #3588155 DomainAliasStorage pattern matching: sort() comparator...
mably’s picture

Status: Needs review » 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.

Status: Fixed » Closed (fixed)

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