ADR-0016: Public RequestFactory as the URI boundary 

Status

Accepted

Date

2026-10-10

Relation

Supplements ADR-0003; its middleware and transport boundaries remain.

Context 

A PSR-7 URI object may already have lost information from the original string. Middleware cannot recover that information to distinguish forbidden raw syntax. The registered TYPO3 path therefore needs validation before Core processes the raw URI. The public TYPO3CMSCoreHttpRequestFactory is the entry point for this boundary; the internal GuzzleClientFactory is not replaced.

Core 13 and Core 14 differ in readonly inheritance. A version number alone does not establish a compatible parent class. A conflicting object mapping or different factory instances would also change the qualified entry point.

Decision 

ext_localconf.php registers GuardedRequestFactory13 or GuardedRequestFactory14 according to the actual supported Core shape, when no existing mapping is present. Registration is validated on every request, including Disabled mode. Conflicts are neither overwritten nor silently accepted. The Core factory and the PSR factory alias must resolve to the same expected instance.

RequestFactoryCompatibility validates the supported version, inheritance, visibility, return type, parameter names, types and defaults, and constructor before loading the replacement class. The parent's readonly shape must match its Core branch. The public request signature remains intact.

RawRequestGuardTrait validates the raw URI before PSR-7 processing in Enforce mode. Observe reports a denial and continues to delegate; Disabled skips raw policy validation. The replacement then delegates to the exact original Core factory, preserving method, options and context. Middleware ordering, Core context restrictions and the controlled terminal transport from ADR-0003: Two middleware boundaries and a controlled terminal transport remain additional boundaries.

Alternatives considered 

  • PSR-7 middleware alone: cannot validate raw information already lost.
  • Replace the internal GuzzleClientFactory: couples the extension to an internal Core API and replaces more than the required URI boundary.
  • One replacement class for both Core branches: ignores incompatible readonly inheritance rules.
  • Accept only a version number or any existing mapping: establishes neither the actual ABI nor the active factory identity.

Consequences 

The registered Core factory gains an earlier validation boundary without a second HTTP stack. Incompatible APIs or conflicting registration produce a controlled failure. Projects with their own factory replacement need an explicit integration. Early bootstrap calls, independent clients and direct sockets remain outside this boundary. For an already constructed PSR-18 request, raw URI validation remains the caller's responsibility; see Raw URLs and PSR-7 and Coverage and trust boundary.

Sources and evidence 

The implementation is in Classes/Http/RequestFactoryCompatibility.php, Classes/Http/RawRequestFactoryRegistration.php, Classes/Http/RawRequestGuardTrait.php and the two replacement classes. Wiring is in Configuration/Services.yaml and ext_localconf.php.

Tests/Unit/RequestFactoryCompatibilityTest.php, Tests/Unit/RequestFactoryAbiContractTest.php, Tests/Unit/RawRequestFactoryModeTest.php and Tests/Unit/CoreConfigurationShapeTest.php maintain compatibility, mode and configuration controls. The genuine Core bootstrap probe in Tests/Functional/production-bootstrap.php checks factory identity and raw syntax rejection with separate contact counters. Previously executed results are recorded under Verification report. Authoring this ADR inspected the current sources; it did not execute these tests again or produce a new Native mutation measurement.

Reconsideration trigger 

Changes to the public Core signature, inheritance, object registration or PSR alias wiring require renewed ABI and bootstrap qualification. An expanded coverage promise needs evidence for each additional entry path.