Problem/Motivation

Drupal 11.4 introduced two independent core changes that each break purl in a distinct way. Both are fixed on this branch, which requires core_version_requirement: ^11.4 (see companion 2.x issue #3608542, which keeps purl working unmodified on core <11.4).

1. Subrequest invisible to Drupal's request_stack service

Starting with Drupal 11.4, core's http_kernel.basic service (Symfony\Component\HttpKernel\HttpKernel) is constructed with null for its RequestStack argument instead of Drupal's real @request_stack service (see the comment on that service definition in core.services.yml). Symfony's raw HttpKernel then keeps its own private, internal RequestStack for push()/pop(), completely separate from Drupal's request_stack service.

RequestSubscriber::onRequest() strips a purl prefix and processes the result as a Symfony subrequest by calling $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST). $event->getKernel() is always the raw Symfony HttpKernel - RequestEvent is constructed with $this inside HttpKernel::handleRaw(), never the outer StackedHttpKernel/DrupalKernel. Calling handle() on it directly therefore bypasses DrupalKernel::preHandle() and the KernelPreHandle middleware entirely, and on 11.4+ makes the subrequest invisible to Drupal's request_stack service for its entire lifecycle - so any code asking "what's the current request/route" during that subrequest gets the main request instead (which has no route yet, since the subrequest short-circuited routing on the main request).

Confirmed: Drupal 10.1, 10.3, 10.4, 11.0, and 11.3 all pass Drupal's real @request_stack into http_kernel.basic; only 11.4 changed that argument to null.

Symptoms: Error: Call to a member function getOptions() on null in PurlNodeContextRoutes::purlCheckNodeContext(); fatal errors in any code reading \Drupal::routeMatch() during a purl-processed pageview (core's HtmxContentViewSubscriber, custom breadcrumb builders, etc.); in combination with group_purl's keep_context handling, infinite redirect loops on already-correctly-prefixed group URLs (see group_purl issue #3608553).

2. PurlRouteProvider constructor incompatible with core's new RouteProvider signature

Separately, Drupal 11.4 (#3503843, route cache refactor) inserted a new $fast_cache parameter into Drupal\Core\Routing\RouteProvider::__construct(), before the existing $table parameter:

__construct(Connection $connection, StateInterface $state, CurrentPathStack $current_path,
  CacheBackendInterface $cache_backend, InboundPathProcessorInterface $path_processor,
  CacheTagsInvalidatorInterface $cache_tag_invalidator, $fast_cache = NULL, $table = 'router',
  ?LanguageManagerInterface $language_manager = NULL)

Core itself ships a graceful BC shim for this (if $fast_cache isn't a CacheBackendInterface, it re-shifts the remaining arguments with a deprecation notice) - but that shim lives in core's own constructor, and PurlRouteProvider's subclass constructor, which has its own explicit, positionally-typed parameter list (ending in $table, $language_manager, $contextHelper, $matchedModifiers), never reaches it. On Drupal 11.4+, the DI container now passes the new 9-argument core list plus purl's own +2 = 11 positional arguments, which land on the wrong parameters of purl's old, unshifted constructor - concretely, the string 'router' (meant for the new $table slot) lands in the $language_manager parameter, throwing:

TypeError: PurlRouteProvider::__construct(): Argument #8 ($language_manager) must be of
type ?LanguageManagerInterface, string given

This is fatal on every single request (route provider is constructed on essentially every bootstrap), and its failure during Drupal's exception-teardown path (re-entering the same not-yet-constructed service via maintenance_mode_subscriber -> url_generator -> purl.url_generator -> router.route_provider) additionally surfaces as a ServiceCircularReferenceException, which is a symptom of the same root TypeError, not a separate bug.

Steps to reproduce

  • Install purl on a site running Drupal core 11.4 or later.
  • Run any Drush command or visit any page (e.g. drush cr, or any request that constructs router.route_provider).
  • Observe the TypeError above (or, if that happens to be caught, the ServiceCircularReferenceException).
  • Separately: configure a path-prefix modifier/provider, visit a URL matching that prefix, and observe the subrequest-visibility symptoms described above.

Proposed resolution

For the subrequest issue, in src/Event/RequestSubscriber.php:

  • Manually push the subrequest onto Drupal's request_stack service before calling handle($request, SUB_REQUEST), and pop it in a finally block - unconditionally, since this branch requires core >=11.4.
  • After the subrequest completes, copy a narrow allowlist of route-matching attributes (_route, _route_object, _route_params, _raw_variables, _controller, _format) back onto the main request - not a bulk copy, to avoid leaking access-check results or cache-context data.

In src/Event/PurlNodeContextRoutes.php, purlCheckNodeContext(): return early when !$event->isMainRequest(), and treat a null getRouteObject() as a no-op rather than a fatal error.

For the RouteProvider constructor issue, in src/Routing/PurlRouteProvider.php: replace the explicit, positionally-typed constructor with a variadic one that pops purl's own trailing arguments ($matchedModifiers, then $contextHelper) off the argument list and forwards everything else, unchanged and in order, to parent::__construct(...$args). This makes the constructor agnostic to exactly how many positional arguments core's router.route_provider service passes - correct whether core provides 8 arguments (pre-11.4) or 9 (11.4+, with $fast_cache) - with no version-detection code needed at all.

Set purl.info.yml core_version_requirement: >=11.4.

Remaining tasks

  • Apply the RequestSubscriber/PurlNodeContextRoutes changes (already committed on this branch)
  • Apply the PurlRouteProvider variadic-constructor fix (new)
  • tests/src/Unit/PurlNodeContextRoutesTest.php - verifies the isMainRequest() guard and null-route guard. Confirmed to fail with the exact original error against pre-fix code; passes against the fix.
  • tests/src/Kernel/PurlSubrequestRequestStackTest.php - confirms the subrequest resolves to itself, not the main request. Confirmed to fail against pre-fix code; passes against the fix.
  • Add a test covering the PurlRouteProvider constructor against both the pre-11.4 and 11.4+ argument counts core's DI container would pass, to guard against future core signature changes recurring here
  • group_purl companion fix (separate issue): NodeGroupViewController self-redirect prevention for keep_context nodes - #3608553
  • Bump core_version_requirement to >=11.4
  • Release as 3.0.0

User interface changes

None.

API changes

Behavioral only — no public method signatures change. Sites with custom code reading $request->attributes during a purl-prefixed pageview should be aware the main request now also carries a narrow set of route-matching attributes copied from the subrequest.

Data model changes

None.

Issue fork purl-3608029

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

freelock created an issue. See original summary.

freelock’s picture

Title: TypeError: Drupal\purl\Routing\PurlRouteProvider::__construct(): Argument #8 ($language_manager) must be of type ?Drupal\Core\Language\LanguageManagerInterface, string given, on Drupal core 11.4+ » Create new 3.x branch to make compatible with Drupal 11.4+
Issue summary: View changes
Related issues: +#3608542: Purl 2.x incompatible with Drupal core 11.4+

freelock’s picture

Status: Active » Needs review

Needs new 3.0.x branch, and review for plugins other than group_purl subrequests.

freelock’s picture

Title: Create new 3.x branch to make compatible with Drupal 11.4+ » 3.x: Drupal 11.4 compatibility fixes (purl subrequest visibility + RouteProvider constructor signature)
Issue summary: View changes

Updating IS, which was missing info about a circular dependency issue that gets hit first...

freelock’s picture

Status: Needs review » Needs work

The fix so far is still broken for Ajax requests -- particularly this breaks media library, on pages where Purl is active.

freelock’s picture

Status: Needs work » Needs review

Additional commit now fixes media library on pages modified by Purl.