1

I am working on a module that works relatively well on the site, but is failing all functional tests I write, because it keeps serving cached versions of pages.

As far as I can tell I have properly defined cache tags and contexts, so I really would like to learn how Drupal 8 decides it can safely use the 'previous' cached response.

What mechanism is responsible for this decision?

UPDATE

My entity defines defines its tags and contexts like this:

class WhiteLabel extends ContentEntityBase implements WhiteLabelInterface {

  /**
   * {@inheritdoc}
   */
  protected $cacheContexts = ['whitelabel'];

  /**
   * {@inheritdoc}
   *
   * Next to the default entity-type:id cache tag, also invalidate these.
   *
   * whitelabel_view is used to invalidate all white labels on a configuration
   * update.
   *
   * @see: Drupal\whitelabel\EventSubscriber\WhiteLabelCacheConfigInvalidator
   */
  protected $cacheTags = ['whitelabel_view'];

  ...

}

The cache context is defined like this:

class WhiteLabelCacheContext implements CacheContextInterface {

  /**
   * Holds the white label.
   *
   * @var \Drupal\whitelabel\WhiteLabelProviderInterface
   */
  protected $whiteLabelProvider;

  /**
   * WhiteLabelCacheContext constructor.
   *
   * @param \Drupal\whitelabel\WhiteLabelProviderInterface $white_label_provider
   *   The white label provider.
   */
  public function __construct(WhiteLabelProviderInterface $white_label_provider) {
    $this->whiteLabelProvider = $white_label_provider;
  }

  /**
   * {@inheritdoc}
   */
  public static function getLabel() {
    return t('White label');
  }

  /**
   * {@inheritdoc}
   */
  public function getContext() {
    $white_label = $this->whiteLabelProvider->getWhiteLabel();
    return !empty($white_label) ? $white_label->id() : 0;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheableMetadata() {
    return new CacheableMetadata();
  }

}

I am using the system branding block for testing the overriding of the site name, and I add cacheability data like so:

/**
 * Implements hook_block_view_alter().
 *
 * @see whitelabel_block_view_pre_render()
 */
function whitelabel_block_view_alter(array &$build, BlockPluginInterface $block) {
  $build['#pre_render'][] = 'whitelabel_block_view_pre_render';
}

/**
 * #pre_render callback: Sets white label cacheability metadata.
 *
 * Scans all blocks to see if they depend on the system.site cache tag. If so,
 * also make them depend on the white label cache tag and cache context to make
 * sure we can cache one block for every white label.
 */
function whitelabel_block_view_pre_render(array $build) {
  $cacheable_metadata = CacheableMetadata::createFromRenderArray($build)
    // Make sure there always is a no-whitelabel version as well.
    ->addCacheContexts(['whitelabel']);

  // Add cache contexts for blocks depending on config:system.site cache tags.
  $tags = $cacheable_metadata->getCacheTags();
  if (in_array('config:system.site', $tags)) {
    if ($whitelabel = \Drupal::service('whitelabel.whitelabel_provider')->getWhiteLabel()) {
      // Add white label specific cacheability metadata.
      $cacheable_metadata
        ->addCacheableDependency($whitelabel);
    }
  }

  // Add cacheable metadata.
  $cacheable_metadata
    ->applyTo($build);

  return $build;
}

Finally the test fails at the second assertion, and still serves the no white label version of the page:

class WhiteLabelConfigOverrideCacheTest extends WhiteLabelTestBase {

  /**
   * Holds the second generated white label throughout the different tests.
   *
   * @var \Drupal\whitelabel\WhiteLabelInterface
   */
  private $whiteLabel2;

  private $defaultName;

  /**
   * {@inheritdoc}
   */
  public static $modules = [
    'block',
    'whitelabel_test', //TODO: Overrides the WL path processor so caching by URL is rooted out.
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    parent::setUp();

    // Place branding block with site name and slogan into header region.
    $this->drupalPlaceBlock('system_branding_block', [
      'region' => 'header',
    ]);

    $this->defaultName = $this->config('system.site')->get('name');
  }

  /**
   * Test to see if the system branding block shows the right values.
   */
  public function testWhiteLabelPageCaching() {
    // No white label.
    $page = $this->drupalGet('<front>');
    $this->assertSession()->elementTextNotContains('css', '.block-system-branding-block', $this->whiteLabel->getName());

    // White label 1.
    $this->setCurrentWhiteLabel($this->whiteLabel);
    $page = $this->drupalGet('<front>');
    // Check page title.
    $this->assertSession()->elementTextContains('css', '.block-system-branding-block', $this->whiteLabel->getName());

    ...
  }
}

For more code you might want to look at the repo: http://cgit.drupalcode.org/whitelabel/tree/?h=8.x-2.x-failing-tests

  • 1
    "As far as I can tell I have properly defined cache tags and contexts" - then this should be fine if you don't run into a specific issue. Without knowing any details there is one worth mentioning. This is that the Internal Page Cache for anonymous traffic doesn't use cache contexts or max-age. Only invalidating cache tags works. – 4k4 Aug 31 '17 at 8:55
  • As far as I can tell I have properly defined cache tags and contexts mind sharing your code on this? Cause maybe you didn't do it properly :p – No Sssweat Aug 31 '17 at 9:13
  • @4k4, During testing a user is logged in, so the anonymous page cache should not affect me. I deliberately left out the details as the question started to feel like a bug report (but then again I might be doing something wrong as well). If you have some time and would like to look at some code we could open a chat? Including all code here would make the question hard to read. – Neograph734 Aug 31 '17 at 9:13
  • @NoSssweat, updated. – Neograph734 Aug 31 '17 at 9:27
  • I wouldn't use the branding block for testing, because this is AFAIK the only block in core which doesn't render the build array properly, see drupal.stackexchange.com/questions/205210/…. – 4k4 Aug 31 '17 at 9:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.