Frontpage cache context "url.path.is_front" differs in CLI when parent "url.path" not

Created on 9 September 2025, 21 days ago

Problem/Motivation

Running some code with CLI (e.g., with Drush), "$request->getPathInfo()" returns "/", which is the same as the front page path, but "$pathMatcher->isFrontPage();" returns `0`.

Cacheable plugins or other structures that use "$pathMatcher->isFrontPage();" and "url.path.is_front" have different cache records for CLI and HTTP. This itself isn't an issue. However, when "url.path" is added to the cache context by another plugin/alter function, cache tags are optimized, and the higher-level "url.path" absorbs "url.path.is_front". From this point, CLI and HTTP cache intersect, leading to unexpected behavior. Cache built in CLI follows non-front page logic, and then the front page via HTTP uses this cache and displays incorrect data.

Some code experiments:

Test code:

$s = \Drupal::service("cache_contexts_manager"); 
var_dump($s->convertTokensToKeys(["url.path"])->getKeys()); 
var_dump($s->convertTokensToKeys(["url.path.is_front"])->getKeys());
var_dump($s->convertTokensToKeys(["url.path", "url.path.is_front"])->getKeys());

HTTP:

array(1) {
  [0] =>  string(12) "[url.path]=/"
}
array(1) {
  [0]=> string(30) "[url.path.is_front]=is_front.1" // Look here.
}
array(1) {
  [0]=>  string(12) "[url.path]=/" // Keys optimized, value lost.
}

CLI:

array(1) {
  [0] =>  string(12) "[url.path]=/"
}
array(1) {
  [0]=> string(30) "[url.path.is_front]=is_front.0" // Look here.
}
array(1) {
  [0]=>  string(12) "[url.path]=/" // Keys optimized, value lost.
}

Partially, this is a Drush issue because it creates the request as "$request = Request::createFromGlobals();", and it has "/" in "::getPathInfo()".

However, there are things that I think should be highlighted to Drupal core:

  • If we have "/" in the request path but no route name, "PathMatcher::isFrontPage" returns false. Having both "url.path" and "url.path.is_front" keys causes the difference in the cache ID to be lost.
  • If there is no request in the request stack, PathCacheContext::getContext leads to: "Fatal error: Uncaught Error: Call to a member function getBasePath() on null"

Steps to reproduce

We found this issue while creating an access policy that allows anonymous users to see the front page only. With the "url.path.is_front" cache context, it worked perfectly. Then, we added simple pages to the allowed area (like About Us, Privacy Policy) with the "url.path" cache context, and sometimes the front page became forbidden for a user, while other times it worked. Code to check:


namespace Drupal\example\Session;

use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccessPolicyBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\CalculatedPermissionsItem;
use Drupal\Core\Session\RefinableCalculatedPermissionsInterface;

/**
 * Grants view permissions on some pages for anonymous user.
 */
class AnonymousAccessPolicy extends AccessPolicyBase {

  /**
   * The path matcher.
   *
   * @var \Drupal\Core\Path\PathMatcherInterface
   */
  protected $pathMatcher;

  /**
   * The current route match.
   *
   * @var \Drupal\Core\Routing\RouteMatchInterface
   */
  protected $routeMatch;

  /**
   * {@inheritdoc}
   */
  public function __construct(PathMatcherInterface $path_matcher, RouteMatchInterface $route_match) {
    $this->pathMatcher = $path_matcher;
    $this->routeMatch = $route_match;
  }

  /**
   * {@inheritdoc}
   */
  public function calculatePermissions(AccountInterface $account, string $scope): RefinableCalculatedPermissionsInterface {
    $calculated_permissions = parent::calculatePermissions($account, $scope)
      ->addCacheContexts($this->getPersistentCacheContexts());

    if ($account->isAnonymous()) {
      $calculated_permissions->addCacheContexts(['url.path', 'url.path.is_front']);
      if ($this->routeMatch->getRouteName() === 'entity.node.canonical') {
        $node = $this->routeMatch->getParameter('node');
      }
      if ($this->pathMatcher->isFrontPage() || (isset($node) && $node->bundle() === 'page')) {
        $calculated_permissions->addItem(new CalculatedPermissionsItem(['access content', 'view media']));
      }
    }

    return $calculated_permissions;
  }

  /**
   * {@inheritdoc}
   */
  public function getPersistentCacheContexts(): array {
    return ['user.roles:anonymous'];
  }

}

Generally it works well, but if cache is build in CLI (e.g. during drush cron) it doesn't work. Use to check:

drush cr && drush ev '\Drupal::currentUser()->hasPermission("access content");'

Cache with ID "access_policies:drupal:[languages:language_interface]=en:[url.path]=/:[user.is_super_user]=0:[user.roles]=anonymous" is created without "access content" permissions because "$pathMatcher->isFrontPage()" returns 0 in CLI.

Proposed resolution

  • Support NULL request in `PathCacheContext::getContext` and in other places too.
  • Don't check route name in IsFrontPathCacheContext, or add dependency like "route.name" cache context, or move it to the "route" section - "route.is_front"
🐛 Bug report
Status

Active

Version

11.0 🔥

Component

cache system

Created by

🇧🇾Belarus dewalt

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024