After setting a cache tag to use a contextual filter in a view, when checking the cache tags returned on page load both the processed and unprocessed tags are included in the cache tags header.

For example. If a view has a contextual filter of taxonomy term id (tid) and custom cache tag 'custom:{{ arguments.tid }}. When the view is loaded with the term id value of 5, then the X-Drupal-Cache-Tags header will have two values added, one called 'custom:5' and the other 'custom:{{ arugments.tid }}'.

Similar to what's reported in the last comment of this issue: https://www.drupal.org/project/views_custom_cache_tag/issues/2971568

I've raised this as a bug as according to the Cache Tags documentation here: https://www.drupal.org/docs/8/api/cache-api/cache-tags the tags cannot contain spaces (which views replacement tokens all contain). If this issue is not a bug but instead the desired behaviour then feel free to close it.

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

Demma10 created an issue. See original summary.

stooit’s picture

I can replicate this on a fresh install:

  1. Create new view with contextual filter (e.g id: content id via url)
  2. Configure custom tag on view with token (e.g: custom:{{ raw_arguments.nid }})
  3. Emitted header contains both values, including raw token (e.g: custom:{{ raw_arguments.nid }} custom:10

This simple patch will ensure no raw token values are returned.

pcate’s picture

I just installed this module for testing and noticed this as well. For me it just caused confusion as I thought the tokens weren't being processes as cache tags, and I thought I was doing something wrong.

The patch applied for me, but unfortunately caused the following error to be thrown on pages with the view:

The website encountered an unexpected error. Please try again later.
AssertionError: Cache tags must be valid strings in assert() (line 58 of core/lib/Drupal/Core/Cache/Cache.php).
assert(, 'Cache tags must be valid strings') (Line: 58)
Drupal\Core\Cache\Cache::mergeTags(Array, Array) (Line: 124)
Drupal\views_custom_cache_tag\Plugin\views\cache\CustomTag->getCacheTags() (Line: 1566)
Drupal\views\ViewExecutable->getCacheTags() (Line: 2176)
Drupal\views\Plugin\views\display\DisplayPluginBase->applyDisplayCacheabilityMetadata(Array) (Line: 2391)
Drupal\views\Plugin\views\display\DisplayPluginBase->buildRenderable(Array, ) (Line: 1595)
Drupal\views\ViewExecutable->buildRenderable('top_faq_questions_block', Array, ) (Line: 51)
Drupal\views\Plugin\Block\ViewsBlock->build() (Line: 105)
Drupal\layout_builder\EventSubscriber\BlockComponentRenderArray->onBuildRender(Object, 'section_component.build.render_array', Object)
call_user_func(Array, Object, 'section_component.build.render_array', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('section_component.build.render_array', Object) (Line: 113)
Drupal\layout_builder\SectionComponent->toRenderArray(Array, ) (Line: 86)
Drupal\layout_builder\Section->toRenderArray(Array) (Line: 317)
Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay->buildSections(Object) (Line: 276)
Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay->buildMultiple(Array) (Line: 351)
Drupal\Core\Entity\EntityViewBuilder->buildComponents(Array, Array, Array, 'full') (Line: 24)
Drupal\node\NodeViewBuilder->buildComponents(Array, Array, Array, 'full') (Line: 293)
Drupal\Core\Entity\EntityViewBuilder->buildMultiple(Array) (Line: 250)
Drupal\Core\Entity\EntityViewBuilder->build(Array)
call_user_func_array(Array, Array) (Line: 100)
Drupal\Core\Render\Renderer->doTrustedCallback(Array, Array, 'Render #pre_render callbacks must be methods of a class that implements \Drupal\Core\Security\TrustedCallbackInterface or be an anonymous function. The callback was %s. Support for this callback implementation is deprecated in 8.8.0 and will be removed in Drupal 9.0.0. See https://www.drupal.org/node/2966725', 'silenced_deprecation', 'Drupal\Core\Render\Element\RenderCallbackInterface') (Line: 781)
Drupal\Core\Render\Renderer->doCallback('#pre_render', Array, Array) (Line: 372)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 200)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 226)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 573)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 227)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 117)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.view', Object) (Line: 156)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 694)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
pcate’s picture

Status: Active » Needs work
pcate’s picture

Issue tags: +Needs tests
pcate’s picture

@stooit I'm wondering if the error I am getting is related to using array_map to filter out the unprocessed tags. Specifically, if the strpos check you added is true, an empty array item is probably being added to $custom_tags. array_filter might be a better function to use.

berdir’s picture

You can't change the value with array_filter() so you'd need both.

I don't understand why they would get duplicated though. Sounds to me like you might have another view that doesn't have the same argument or not value for it?

The only cases where I've seen those kind of cache tags on all the sites where we use this for is due to misconfiguration.

The problem with this approach is that it might fix the immediate issue of having invalid cache tags. But if we just drop those cache tags, then the views will not be invalidated when they should. Maybe this should be combined with a log message that logs which view/display this happened?

sonvir249’s picture

@Berdir I see this issue for views blocks but not for views pages.

vivek panicker’s picture

I can replicate this issue in D10.3 also in a view block.

vivek panicker’s picture

Updated the MR to make it compatible with the latest code version.

vivek panicker’s picture

Status: Needs work » Needs review
mdolnik’s picture

Version: 8.x-1.2 » 8.x-1.4

@Berdir

I don't understand why they would get duplicated though. Sounds to me like you might have another view that doesn't have the same argument or not value for it?

I am having the same issue as everyone else when embedding a view on a page.

How to reproduce:

  1. Add a view with a contextual filter.
  2. Add custom cache tag for: my-custom-tag:{{ arguments.field_name }}
  3. Add a custom page controller, and add an embedded view via: $render_array = Views::getView($view_name)->buildRenderable($display_name, [$arg]);
  4. Visit the page
  5. Inspect the results of $render_array['#cache']['tags'] and notice that the tokens remain un-resolved

After investigation, the following is occurring:

  1. buildRenderable() will:
    • Call DisplayPluginBase::buildRenderable()
    • Which then calls DisplayPluginBase::applyDisplayCacheabilityMetadata()
    • Which then calls ViewExecutable::getCacheTags()
    • Which ultimately calls CustomTag::getCacheTags()
  2. The issue here is that the arguments are not provided to DisplayPluginBase::applyDisplayCacheabilityMetadata() at this stage so there are no values to process the tokens with.
    • It is at this point where the raw un-resolved cache tags are added to the render array and ultimately stored in the DB
  3. Then, when the view gets rendered, it first caches the results data:
    • View::preRenderViewElement() is called during render.
    • Which then calls ViewExecutable::executeDisplay()
    • Which then calls Block::execute()
    • Which then calls ViewExecutable::render()
    • Which, if storing new cache data calls CachePluginBase::cacheSet()
    • Which then calls CustomTag::getCacheTags()
  4. NOW, since the view is built properly, the arguments are present and the tokens get processed correctly.
  5. Further down the render pipeline, ViewExecutable::render() will then:
    • Call DisplayPluginBase::render().
    • Which then calls DisplayPluginBase::applyDisplayCacheabilityMetadata()
    • Which then calls ViewExecutable::getCacheTags()
    • Which ultimately calls CustomTag::getCacheTags()
  6. BUT, the issue here is applyDisplayCacheabilityMetadata() is applying the cache tags to an existing element, and this element already contains the cache tags with the un-resolved tokens from the first call to CustomTag::getCacheTags(), so the element ends up getting both resolved and unresolved tags.

As for blocks, the process is the same, where ViewsBlock::build() will call DisplayPluginBase::buildRenderable().

I was playing around with modifying CustomTag::getCacheTags() in an attempt to extract the arguments from the element/render array before calling tokenizeValue():

if (empty($this->view->build_info['substitutions'])) {
  if (!empty($this->view->element['#arguments'])) {
    $this->view->setArguments($this->view->element['#arguments']);
    // This seems to be the only public way to call ViewExecutable::_buildArguments() without executing the view.
    $this->view->buildTitle();
  }
}

...Which seemed to work for embedded views where the arguments are passed into buildRenderable(), but I did not have luck with embedded blocks as it doesn't seem to have the arguments by the time it calls buildRenderable().

With my specific issues, the other proposed code in this issue queue seems to resolve the invalid cache tags, while retaining proper caching.

Since the issue is only in the initial render array building and the views render and views results caching end up providing the proper cache tags, changing CustomTag::getCacheTags() to omit the initial broken cache tags seems to have no ill effects.