From f2c6fd37deb6f8e0bce78c59a8866ce734df5b31 Mon Sep 17 00:00:00 2001 From: Minnur Yunusov Date: Thu, 16 Jan 2025 01:44:47 +0300 Subject: [PATCH 1/5] feat: Add Support for App Router Invalidation by Tag in Drupal Module (#812) --- .../src/Plugin/Next/Revalidator/CacheTag.php | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 modules/next/src/Plugin/Next/Revalidator/CacheTag.php diff --git a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php new file mode 100644 index 00000000..48e955c4 --- /dev/null +++ b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php @@ -0,0 +1,139 @@ +getEntity(); + $sites = $event->getSites(); + + if (!($entity instanceof FieldableEntityInterface)) { + return FALSE; + } + + $tags = []; + $tags[] = $entity->getEntityTypeId() . ':' . $entity->id(); + if ($entity->getEntityTypeId() == 'menu_link_content') { + $tags[] = $entity->getEntityTypeId() . ':' . $entity->getMenuName(); + } + else { + $tags[] = $entity->getEntityTypeId() . '_list'; + $tags[] = $entity->getEntityTypeId() . '_list:' . $entity->bundle(); + } + + /** @var \Drupal\next\Entity\NextSite $site */ + foreach ($sites as $site) { + try { + $tags_string = implode(',', $tags); + $revalidate_url = $this->getRevalidateUrlForTags($site, $tags_string); + + if (!$revalidate_url) { + throw new \Exception('No revalidate url set.'); + } + + if ($this->nextSettingsManager->isDebug()) { + $this->logger->notice('(@action): Revalidating tags %tags for the site %site. URL: %url', [ + '@action' => $event->getAction(), + '%tags' => $tags_string, + '%site' => $site->label(), + '%url' => $revalidate_url->toString(), + ]); + } + + $response = $this->httpClient->request('GET', $revalidate_url->toString()); + if ($response && $response->getStatusCode() === Response::HTTP_OK) { + if ($this->nextSettingsManager->isDebug()) { + $this->logger->notice('(@action): Successfully revalidated tags %path for the site %site. URL: %url', [ + '@action' => $event->getAction(), + '%tags' => $tags_string, + '%site' => $site->label(), + '%url' => $revalidate_url->toString(), + ]); + } + + $revalidated = TRUE; + } + } + catch (\Exception $exception) { + Error::logException($this->logger, $exception); + $revalidated = FALSE; + } + } + + return $revalidated; + } + + /** + * Returns the revalidate url for given cache tags. + * + * @param NextSite $site + * @param string $cache_tags + * The cache tags as string. + * + * @return \Drupal\Core\Url|null + * The revalidate url. + */ + protected function getRevalidateUrlForTags(NextSite $site, string $cache_tags): ?Url { + $revalidate_url = $site->getRevalidateUrl(); + if (!$revalidate_url) { + return NULL; + } + $query = [ + 'tags' => $cache_tags, + ]; + if ($secret = $site->getRevalidateSecret()) { + $query['secret'] = $secret; + } + return Url::fromUri($site->getRevalidateUrl(), [ + 'query' => $query, + ]); + } + +} From f59d07ccd39d6098ae24cdf2e397b7b0f7d21354 Mon Sep 17 00:00:00 2001 From: Minnur Yunusov Date: Thu, 16 Jan 2025 03:05:49 +0300 Subject: [PATCH 2/5] Refactor URL revalidator method in NextSite entity. --- modules/next/src/Entity/NextSite.php | 11 ++----- modules/next/src/Entity/NextSiteInterface.php | 6 ++-- .../src/Plugin/Next/Revalidator/CacheTag.php | 30 +------------------ .../next/src/Plugin/Next/Revalidator/Path.php | 3 +- .../tests/src/Kernel/Entity/NextSiteTest.php | 8 ++--- 5 files changed, 12 insertions(+), 46 deletions(-) diff --git a/modules/next/src/Entity/NextSite.php b/modules/next/src/Entity/NextSite.php index 5a75cd3f..098f06a2 100644 --- a/modules/next/src/Entity/NextSite.php +++ b/modules/next/src/Entity/NextSite.php @@ -265,22 +265,15 @@ public function getLiveUrlForEntity(EntityInterface $entity): ?Url { /** * {@inheritdoc} */ - public function getRevalidateUrlForPath(string $path): ?Url { + public function buildRevalidateUrl(array $query = []): ?Url { $revalidate_url = $this->getRevalidateUrl(); - if (!$revalidate_url) { return NULL; } - - $query = [ - 'path' => $path, - ]; - if ($secret = $this->getRevalidateSecret()) { $query['secret'] = $secret; } - - return Url::fromUri($this->getRevalidateUrl(), [ + return Url::fromUri($revalidate_url, [ 'query' => $query, ]); } diff --git a/modules/next/src/Entity/NextSiteInterface.php b/modules/next/src/Entity/NextSiteInterface.php index c09a21c0..95a69a84 100644 --- a/modules/next/src/Entity/NextSiteInterface.php +++ b/modules/next/src/Entity/NextSiteInterface.php @@ -131,12 +131,12 @@ public function setRevalidateSecret(string $revalidate_secret): self; /** * Returns the revalidate url for given path. * - * @param string $path - * The path. + * @param array $query + * The revalidate URL query parameters. * * @return \Drupal\Core\Url|null * The revalidate url. */ - public function getRevalidateUrlForPath(string $path): ?Url; + public function buildRevalidateUrl(array $query = []): ?Url; } diff --git a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php index 48e955c4..d44db253 100644 --- a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php +++ b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php @@ -4,9 +4,7 @@ use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Url; use Drupal\Core\Utility\Error; -use Drupal\next\Entity\NextSite; use Drupal\next\Event\EntityActionEvent; use Drupal\next\Plugin\ConfigurableRevalidatorBase; use Drupal\next\Plugin\RevalidatorInterface; @@ -72,7 +70,7 @@ public function revalidate(EntityActionEvent $event): bool { foreach ($sites as $site) { try { $tags_string = implode(',', $tags); - $revalidate_url = $this->getRevalidateUrlForTags($site, $tags_string); + $revalidate_url = $site->buildRevalidateUrl(['tags' => $tags_string]); if (!$revalidate_url) { throw new \Exception('No revalidate url set.'); @@ -110,30 +108,4 @@ public function revalidate(EntityActionEvent $event): bool { return $revalidated; } - /** - * Returns the revalidate url for given cache tags. - * - * @param NextSite $site - * @param string $cache_tags - * The cache tags as string. - * - * @return \Drupal\Core\Url|null - * The revalidate url. - */ - protected function getRevalidateUrlForTags(NextSite $site, string $cache_tags): ?Url { - $revalidate_url = $site->getRevalidateUrl(); - if (!$revalidate_url) { - return NULL; - } - $query = [ - 'tags' => $cache_tags, - ]; - if ($secret = $site->getRevalidateSecret()) { - $query['secret'] = $secret; - } - return Url::fromUri($site->getRevalidateUrl(), [ - 'query' => $query, - ]); - } - } diff --git a/modules/next/src/Plugin/Next/Revalidator/Path.php b/modules/next/src/Plugin/Next/Revalidator/Path.php index 559ef03f..ececdba7 100644 --- a/modules/next/src/Plugin/Next/Revalidator/Path.php +++ b/modules/next/src/Plugin/Next/Revalidator/Path.php @@ -84,9 +84,10 @@ public function revalidate(EntityActionEvent $event): bool { } foreach ($paths as $path) { + /** @var \Drupal\next\Entity\NextSite $site */ foreach ($sites as $site) { try { - $revalidate_url = $site->getRevalidateUrlForPath($path); + $revalidate_url = $site->buildRevalidateUrl(['path' => $path]); if (!$revalidate_url) { throw new \Exception('No revalidate url set.'); diff --git a/modules/next/tests/src/Kernel/Entity/NextSiteTest.php b/modules/next/tests/src/Kernel/Entity/NextSiteTest.php index abeb9675..18e676f3 100644 --- a/modules/next/tests/src/Kernel/Entity/NextSiteTest.php +++ b/modules/next/tests/src/Kernel/Entity/NextSiteTest.php @@ -117,7 +117,7 @@ public function test() { } /** - * @covers ::getRevalidateUrlForPath + * @covers ::buildRevalidateUrl */ public function testGetRevalidateUrlForPath() { $marketing = NextSite::create([ @@ -129,13 +129,13 @@ public function testGetRevalidateUrlForPath() { ]); $marketing->save(); - $this->assertNull($marketing->getRevalidateUrlForPath('/foo')); + $this->assertNull($marketing->buildRevalidateUrl(['path' => '/foo'])); $marketing->setRevalidateUrl('http://example.com/api/revalidate'); - $this->assertSame('http://example.com/api/revalidate?path=/foo', $marketing->getRevalidateUrlForPath('/foo')->toString()); + $this->assertSame('http://example.com/api/revalidate?path=/foo', $marketing->buildRevalidateUrl(['path' => '/foo'])->toString()); $marketing->setRevalidateSecret('12345'); - $this->assertSame('http://example.com/api/revalidate?path=/foo&secret=12345', $marketing->getRevalidateUrlForPath('/foo')->toString()); + $this->assertSame('http://example.com/api/revalidate?path=/foo&secret=12345', $marketing->buildRevalidateUrl(['path' => '/foo'])->toString()); } } From 59281a977563c37e5fbebbe350940f0d4ce68bf0 Mon Sep 17 00:00:00 2001 From: Minnur Yunusov Date: Fri, 17 Jan 2025 15:50:26 +0300 Subject: [PATCH 3/5] Use entity provided method to get cache tags. --- modules/next/src/Plugin/Next/Revalidator/CacheTag.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php index d44db253..7d591f6a 100644 --- a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php +++ b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php @@ -56,8 +56,7 @@ public function revalidate(EntityActionEvent $event): bool { return FALSE; } - $tags = []; - $tags[] = $entity->getEntityTypeId() . ':' . $entity->id(); + $tags = $entity->getCacheTags() ?? []; if ($entity->getEntityTypeId() == 'menu_link_content') { $tags[] = $entity->getEntityTypeId() . ':' . $entity->getMenuName(); } From a38006f3722292d34ba35969c4f46cb4a5677f4f Mon Sep 17 00:00:00 2001 From: Minnur Yunusov Date: Fri, 17 Jan 2025 16:31:52 +0300 Subject: [PATCH 4/5] Refine cache tag revalidation plugin. --- .../src/Plugin/Next/Revalidator/CacheTag.php | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php index 7d591f6a..0d1d97c2 100644 --- a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php +++ b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php @@ -11,7 +11,7 @@ use Symfony\Component\HttpFoundation\Response; /** - * Provides a revalidator for revalidating Next Site references by cache tags. + * Cache tag based on-demand revalidation plugin. * * @Revalidator( * id = "cache_tag", @@ -56,29 +56,20 @@ public function revalidate(EntityActionEvent $event): bool { return FALSE; } - $tags = $entity->getCacheTags() ?? []; - if ($entity->getEntityTypeId() == 'menu_link_content') { - $tags[] = $entity->getEntityTypeId() . ':' . $entity->getMenuName(); - } - else { - $tags[] = $entity->getEntityTypeId() . '_list'; - $tags[] = $entity->getEntityTypeId() . '_list:' . $entity->bundle(); - } + $cache_tags = implode(',', $entity->getCacheTags()); /** @var \Drupal\next\Entity\NextSite $site */ foreach ($sites as $site) { try { - $tags_string = implode(',', $tags); - $revalidate_url = $site->buildRevalidateUrl(['tags' => $tags_string]); - + $revalidate_url = $site->buildRevalidateUrl(['tags' => $cache_tags]); if (!$revalidate_url) { throw new \Exception('No revalidate url set.'); } if ($this->nextSettingsManager->isDebug()) { - $this->logger->notice('(@action): Revalidating tags %tags for the site %site. URL: %url', [ + $this->logger->notice('(@action): Revalidating tags %list for the site %site. URL: %url', [ '@action' => $event->getAction(), - '%tags' => $tags_string, + '%list' => $cache_tags, '%site' => $site->label(), '%url' => $revalidate_url->toString(), ]); @@ -87,9 +78,9 @@ public function revalidate(EntityActionEvent $event): bool { $response = $this->httpClient->request('GET', $revalidate_url->toString()); if ($response && $response->getStatusCode() === Response::HTTP_OK) { if ($this->nextSettingsManager->isDebug()) { - $this->logger->notice('(@action): Successfully revalidated tags %path for the site %site. URL: %url', [ + $this->logger->notice('(@action): Successfully revalidated tags %list for the site %site. URL: %url', [ '@action' => $event->getAction(), - '%tags' => $tags_string, + '%list' => $cache_tags, '%site' => $site->label(), '%url' => $revalidate_url->toString(), ]); From db2690b16b46033837c050a322dcc5ea7a3cf9f5 Mon Sep 17 00:00:00 2001 From: Minnur Yunusov Date: Fri, 17 Jan 2025 19:25:11 +0300 Subject: [PATCH 5/5] Refine cache tag revalidation plugin to include list cache tags. --- modules/next/src/Plugin/Next/Revalidator/CacheTag.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php index 0d1d97c2..9725ce69 100644 --- a/modules/next/src/Plugin/Next/Revalidator/CacheTag.php +++ b/modules/next/src/Plugin/Next/Revalidator/CacheTag.php @@ -56,7 +56,13 @@ public function revalidate(EntityActionEvent $event): bool { return FALSE; } - $cache_tags = implode(',', $entity->getCacheTags()); + // Get all available cache tags (including list tags). + $list_tags = $entity->getEntityType()->getListCacheTags(); + if ($entity->getEntityType()->hasKey('bundle')) { + $list_tags[] = $entity->getEntityTypeId() . '_list:' . $entity->bundle(); + } + $combined_tags = array_merge($entity->getCacheTags(), $list_tags); + $cache_tags = implode(',', $combined_tags); /** @var \Drupal\next\Entity\NextSite $site */ foreach ($sites as $site) {