Drupal 8/9 : invalidate child nodes cache

Profile picture for user a.berramou
Azz-eddine BERRAMOU 3 October, 2021

I was working with graphql, but the breadcrumbs doesn't change when we update the parents nodes so it's always cached even if when i add the cachetag to breadcrumb via hook_system_breadcrumb_alter.

The only solution worked for me is to invalidate cache for all child nodes on node update, so

How to invalidate all child nodes cache?

The first thing we need is to collect all child node of the current node like the following:

  <?php
  $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();

And after that loop through it and invalidate cache for each node like:

  <?php
  // 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]);
    }
  }

So the final helper functions is:

<?php
/**
 * Reset all caches child nodes of passed node.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 *   Current entity.
 */
function reset_cache_all_node_child(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]);
    }
  }
}

And the i call this function in hook_ENTITY_TYPE_update like this:

<?php
use Drupal\Core\Entity\EntityInterface;

/**
 * Implements hook_ENTITY_TYPE_update().
 */
function MODULENAME_node_update(EntityInterface $entity) {
  reset_cache_all_node_child($entity);
}

So just like that on each node update all child nodes cache will be invalidated !