Codi font en execució

Drupal 8 - Invalidar la cache del node referenciat

Avui ens hem trobat amb una situació un pel estranya al activar les caches d'un projecte abans de posar-lo en producció.

Es tracta d'una web on hi ha una sèrie de cursos on els usuaris s'hi poden apuntar. Els templates del content type curs estan personalitzats per mostrar informació diferent en funció de si l'usuari s'hi ha apuntat o no, de l'estat de la inscripció, etc.

La relació la tenim muntada de manera que la inscripció té un camp que referència al curs al que pertany, de manera que quan l'usuari s'hi inscrivia o en modificava la inscripció, el curs no se n'assabentava i per tant no es refrescava la cache. És a dir, fes el que fes sempre veia el botó "Inscriu-te" fins que un administrador netejava les caches...

Quin mal de cap... buscar a tot arreu on està cachejat el curs (diferents modes de visualització, les vistes, etc) i anul·lar-ne la cache... o esborrar-les totes, nooooo....

Per sort, a cada entrada de cache drupal porta associada una sèrie de tags del que conté, el que ens permet invalidar-los individualment, el qual afecta a totes les entrades de cache amb aquell tag. És a dir, totes les entrades de cache on hi aparegui el curs amb id 42 incorporaran el tag 'node:42'.

Ara que ja sabem què passa i com es comporta, ja podem pensar com solucionar-ho al nostre mòdul:

// cursos.module

function cursos_node_insert(NodeInterface $node) {
  if ($node->bundle() === 'inscripcio') {
    Cache::invalidateTags(["node:{$node->field_curs->target_id}"]);
  }
}

function cursos_node_update(NodeInterface $node) {
  if ($node->bundle() === 'inscripcio') {
    Cache::invalidateTags(["node:{$node->field_curs->target_id}"]);
  }
}

function cursos_node_delete(NodeInterface $node) {
  if ($node->bundle() === 'inscripcio') {
    Cache::invalidateTags(["node:{$node->field_curs->target_id}"]);
  }
}

Alternativament si tenim el curs carregat també en podem obtenir els tags així:

Cache::invalidateTags($entity->getCacheTagsToInvalidate());

És a dir a cada creació o actualització d'una inscripció, n'invalidem la cache del curs referenciat, de manera que en el pròxim accés sortirà actualitzat.

Fins aquí aconseguim tenir el comptador d'inscripcions sempre al dia, i si només fos un usuari se li refrescaria correctament l'estat de l'inscripció (Inscrit vs botó Inscriu-te). Ara bé tenim el problema que la resta d'usuaris veuen tots la primera versió cachejada... problemón.

Així que no tenim més remei que, o bé deshabilitar caches, mal assumpte, o bé discriminar la cache per usuari, som-hi:

/**
 * Implements hook_preprocess_HOOK().
 * @param array $variables
 */
function cursos_preprocess_node(&$variables) {
  /** @var NodeInterface $node */
  $node = $variables['node'];
  if ($node->getType() === 'curs') {
    /** @var Renderer $renderer */
    $renderer = Drupal::service('renderer');
    $current_user = Drupal::currentUser();

    $variables['#cache']['contexts'][] = 'user';
    $renderer->addCacheableDependency($variables, User::load($current_user->id()));
  }
}

Referència: https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays