2

We have a site where the homepage is not being cached and contains the headers:

x-cache: MISS, MISS
x-cache-hits: 0, 0
x-content-type-options: nosniff
x-drupal-dynamic-cache: UNCACHEABLE

I narrowed this down to the content regions contents, and disabled the "Main page content" for the front page. This then gave me a cache HIT, and no longer responded as UNCACHEABLE. From there, I narrowed it down to a field formatter being used on a paragraph. We have a custom one that extends the normal entity render formatter.

If I swap it back to the original "Render entity" formatter, everything is fine. So then, it must be something we are doing in this custom formatter causing the issue.

I can see when I follow with xdebug that shouldCacheResponse of DynamicPageCacheSubscriber returns FALSE, because something is setting max-age to 0 (not by code). It looks like calls to addCacheableDependency may be triggering this behavior in the formatter:

Essentially, the formatter adds cache data to the render so if any of its referenced items are updated, the cache should be invalidated for that host paragraph so it re-renders:

     $view_builder = \Drupal::entityTypeManager()->getViewBuilder($entity->getEntityTypeId());
      $elements[$delta] = $view_builder->view($entity, $view_mode, $entity->language()->getId());

      try {
        $parent = $items->getParent();
        $parent_entity = $parent->getValue();
        $elements[$delta]['#cache']['keys'][] = $parent_entity->id();
        $elements[$delta]['#cache']['keys'][] = $parent_entity->bundle();
        $elements[$delta]['#cache']['keys'][] = $parent_entity->getEntityTypeId();
        $elements[$delta]['#cache']['keys'][] = 'delta_' . $delta;
        $elements[$delta]['#cache']['keys'][] = 'context_aware';

        $this->renderer->addCacheableDependency($elements[$delta], $parent);

        if ($entity->hasField('field_author')) {
          $child = $entity->field_author->entity;

          if (isset($child)) {
            $this->renderer->addCacheableDependency($elements[$delta], $child);
          }
        }

        // similar statements with addCacheableDependency

If I comment out this initial line:

$this->renderer->addCacheableDependency($elements[$delta], $parent);

Then I get a cacheable response. This looks to be because the $parent item (even though it is a node or paragraph or media entity) triggers this:

  /**
   * Creates a CacheableMetadata object from a depended object.
   *
   * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
   *   The object whose cacheability metadata to retrieve. If it implements
   *   CacheableDependencyInterface, its cacheability metadata will be used,
   *   otherwise, the passed in object must be assumed to be uncacheable, so
   *   max-age 0 is set.
   *
   * @return static
   */
  public static function createFromObject($object) {
    if ($object instanceof CacheableDependencyInterface) {
      $meta = new static();
      $meta->cacheContexts = $object->getCacheContexts();
      $meta->cacheTags = $object->getCacheTags();
      $meta->cacheMaxAge = $object->getCacheMaxAge();
      return $meta;
    }

    // Objects that don't implement CacheableDependencyInterface must be assumed
    // to be uncacheable, so set max-age 0.
    $meta = new static();
    $meta->cacheMaxAge = 0;
    return $meta;
  }

Setting cacheMaxAge to 0 because it is not an instance of CacheableDependencyInterface.

If I am already setting the cache keys, is this line even needed:

$this->renderer->addCacheableDependency($elements[$delta], $parent);

If I remove that, will there be an adverse effect (like render displays not re-rendering when referenced items are saved)?

5
  • 2
    Setting cache keys is not enough, you need the cache tags as well. So don't remove this line, just check the object is not NULL.
    – 4k4
    Jul 27 at 16:13
  • 1
    Yeah cache keys don't really do anything - contexts, tags, and max-age are what you'd want to make sure to carry over.
    – sonfd
    Jul 27 at 16:16
  • $parent is not null, but is received in createFromObject as an instance of EntityAdapter (containing the entity node, or paragraph) which I can't trace as implementing CacheableDependencyInterface
    – Kevin
    Jul 27 at 16:16
  • 2
    OK, now I see the problem, the entity with the cache data is $parent_entity.
    – 4k4
    Jul 27 at 16:17
  • That is what I suspected, thanks for confirming. Changing that returns a cacheable response to the browser.
    – Kevin
    Jul 27 at 16:20
4

This was a good debug deep dive. As mentioned by 4k4 the problem is the first addCacheableDependency line.

Instead of passing the host entity itself, I was mistakenly passing the object returned from getParent which is a TypedData instance that does not implement CacheableDependencyInterface - thus causing the max-age to be set to 0 and the UNCACHEABLE header result.

Passing the entity (returned from getValue()) resolved the issue:

    $parent = $items->getParent();
    $parent_entity = $parent->getValue();
    ...
    $this->renderer->addCacheableDependency($elements[$delta], $parent_entity);

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.