Score:1

How to invalidate Breadcrumbs cache for child nodes on graphQl

gb flag

I have for example Node A with alias node-a and Node B with alias node-a/node-b so on my Node B i have this breadcrumb Home > Node A > Node B

GraphQl result:

"breadcrumb": [
                {
                    "text": "Home",
                    "url": {
                        "path": "/",
                        "routed": true
                    }
                },
                {
                    "text": "Node A",
                    "url": {
                        "path": "http://example.com/node-a",
                        "routed": true
                    }
                },
                {
                    "text": "Node B",
                    "url": {
                        "path": "",
                        "routed": true
                    }
                }
            ]

But when i change Node A title i get always the same result (breadcrumbs not changed), but when i re-save the node A or clear cache the graphQl result change.

Here is what i have tried:

  1. Clear graphQl cache (results and definitions)on node update:

     /**
     * Implements hook_ENTITY_TYPE_update().
     */
    function MYMODULE_node_update(EntityInterface $entity) {
      // Clear graphql cache.
      /** @var \Drupal\Core\Cache\CacheBackendInterface $graphql_results_cache */
      $graphql_results_cache = \Drupal::service('cache.graphql.results');
      $graphql_results_cache->invalidateAll();
      /** @var \Drupal\Core\Cache\CacheBackendInterface $graphql_definitions_cache */
      $graphql_definitions_cache = \Drupal::service('cache.graphql.definitions');
      $graphql_definitions_cache->invalidateAll();
    }

it doesn't work !

  1. Clear cache of all child node on node update like this:

    /**
     * Implements hook_ENTITY_TYPE_update().
     */
    function MYMODULE_node_update(EntityInterface $entity) {
      $database = \Drupal::database();
      // Get the current node path alias.
      $alias = \Drupal::service('path_alias.manager')
        ->getAliasByPath('/node/' . $entity->id());
      // Get all node child of the current node.
      $child_nodes_alias = $database->select('path_alias', 'pa')->fields('pa', [
        'path',
        'alias',
      ])->condition('path', '/node/%', 'LIKE')
        ->condition('alias', $alias . '%', 'LIKE')
        ->execute()
        ->fetchAll();
      // Invalidate cache for each node child of current node.
      foreach ($child_nodes_alias as $alias_data) {
        $nid = explode('/', $alias_data->path)[2] ?? NULL;
        if ($nid && $nid != $entity->id()) {
          $tags = ['node:' . $nid];
          Cache::invalidateTags($tags);
          \Drupal::entityTypeManager()->getStorage('node')->resetCache([$nid]);
        }
      }
    }

it doesn’t work also

  1. I combined the two previous ways it doesn't work too.

When i clear all caches on node update it works, but it's not the best way it impact site cache.


/**
 * Implements hook_ENTITY_TYPE_update().
 */
function MYMODULE_node_update(EntityInterface $entity) {
  drupal_flush_all_caches();
}

What is the best way to clear breadcrumbs cache, or child nodes without clear all site cache ?

Edit: Thank you @4K4 for your comment, i did tried to add CacheTags too breadcrumb but i didn’t work either !


/**
 * Implements hook_system_breadcrumb_alter().
 */
function MYMODULE_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context) {
  // Append the current page title to the breadcrumb for non-admin routes.
  if ($breadcrumb && !\Drupal::service('router.admin_context')->isAdminRoute()) {
    $links = $breadcrumb->getLinks();
    foreach ($links as $link) {
      $parameters = $link->getUrl()->getRouteParameters();
      if ($parameters) {
        // Make sure the breadcrumb is updated when node title changes.
        $breadcrumb->addCacheTags(['node:' . $parameters['node']]);
        $breadcrumb->addCacheContexts(['url']);
      }
    }
  }
}

Note: Easy Breadcrumb module installed.

Kevin avatar
in flag
Are they actually linked in the menu? How are they connected otherwise? Aliases don't imply cache dependencies.
4uk4 avatar
cn flag
Normally you don't solve caching issues in hook_node_update(). The GraphQL plugin building the breadcrumb has to add the cache tags and then they are invalidated automatically when a node is saved.
berramou avatar
gb flag
i did tried to add node cachetag to breadcrumbs as i mentioned in my edit, and it didn’t work, maybe i should add another cacheTag, what i'm missing ?
berramou avatar
gb flag
@Kevin no they are not linked in the menu, auto generated breadcrumbs using Easy Breadcrumb module
4uk4 avatar
cn flag
OK, provided that the breadcrumbs have all necessary cache tags, it's still unclear which code is building the GraphQL result. Couldn't find such a plugin in the main module. Do you have an extra module for that?
berramou avatar
gb flag
No extra module for breadcrumbs, i use the qraphQl officiel module version 3.X i found the Breadcrumb field defined here **graphql/modules/graphql_core/src/Plugin/GraphQL/Fields/Breadcrumbs/Breadcrumbs.php**
4uk4 avatar
cn flag
This is the previous version. The [latest update](https://git.drupalcode.org/project/graphql/-/commit/58a9b7464bc6cf4aa78632a0efee83f911c8522e) for this code object was addressing exactly this issue, but I don't know if this update was successful. You could file an issue if you can reproduce the missing cache tags and this version is still maintained.
berramou avatar
gb flag
Yes i do have this update, it's the last 3.X release installed !
4uk4 avatar
cn flag
Can you confirm the update was successful? For example by checking that the cache tags you've added to the breadcrumbs end up in the cached response.
Score:0
cn flag

I don't know the caching implementation of GraphQL 3.x field plugins, but as far as I can tell this code (even after the latest update from 2018) doesn't collect the breadcrumb metadata because it only gets the links:

protected function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
    if ($value instanceof Url) {
      $resolve = $this->subRequestBuffer->add($value, function () {
        $links = $this->breadcrumbManager->build($this->routeMatch)->getLinks();
        return $links;
      });

      return function ($value, array $args, ResolveContext $context, ResolveInfo $info) use ($resolve) {
        /** @var \Drupal\graphql\GraphQL\Cache\CacheableValue $response */
        $response = $resolve();
        $links = $response->getValue();

        foreach ($links as $link) {
          yield new CacheableValue($link, [$response]);
        }
      };
    }
  }

Shouldn't it get the breadcrumb object to add its cacheable metadata?

protected function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
    if ($value instanceof Url) {
      $resolve = $this->subRequestBuffer->add($value, function () {
        $breadcrumb = $this->breadcrumbManager->build($this->routeMatch);
        return $breadcrumb;
      });

      return function ($value, array $args, ResolveContext $context, ResolveInfo $info) use ($resolve) {
        /** @var \Drupal\graphql\GraphQL\Cache\CacheableValue $response */
        $response = $resolve();
        $breadcrumb = $response->getValue();
        $links = $breadcrumb->getLinks();

        foreach ($links as $link) {
          yield new CacheableValue($link, [$response, $breadcrumb]);
        }
      };
    }
  }
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.