diff --git a/src/Http/Middleware/CacheTracker.php b/src/Http/Middleware/CacheTracker.php index d60a6eb..dd377e9 100644 --- a/src/Http/Middleware/CacheTracker.php +++ b/src/Http/Middleware/CacheTracker.php @@ -129,6 +129,7 @@ private function setupAugmentationHooks(string $url) app(Entry::class)::hook('augmented', function ($augmented, $next) use ($self) { $self->addContentTag($this->collection()->handle().':'.$this->id()); + $self->addContentTag('collection:'.$this->collection()->handle()); return $next($augmented); }); @@ -136,6 +137,7 @@ private function setupAugmentationHooks(string $url) Page::hook('augmented', function ($augmented, $next) use ($self) { if ($entry = $this->entry()) { $self->addContentTag($entry->collection()->handle().':'.$entry->id()); + $self->addContentTag('collection:'.$entry->collection()->handle()); } return $next($augmented); @@ -143,6 +145,7 @@ private function setupAugmentationHooks(string $url) LocalizedTerm::hook('augmented', function ($augmented, $next) use ($self) { $self->addContentTag('term:'.$this->id()); + $self->addContentTag('taxonomy:'.$this->taxonomy()->handle()); return $next($augmented); }); diff --git a/src/Tracker/Manager.php b/src/Tracker/Manager.php index f5d27e0..f0fb5ab 100644 --- a/src/Tracker/Manager.php +++ b/src/Tracker/Manager.php @@ -81,7 +81,7 @@ public function invalidate(array $tags = []) $storeTags = $data['tags']; $url = $data['url']; - if (count(array_intersect($tags, $storeTags)) > 0) { + if ($this->tagsMatch($tags, $storeTags)) { $urls[] = $url; unset($storeData[$key]); @@ -97,6 +97,25 @@ public function invalidate(array $tags = []) return $this; } + private function tagsMatch(array $tagsToInvalidate, array $storeTags): bool + { + foreach ($tagsToInvalidate as $tag) { + // Handle wildcard tags (ending with *) + if (str_ends_with($tag, '*')) { + $prefix = substr($tag, 0, -1); + foreach ($storeTags as $storeTag) { + if (str_starts_with($storeTag, $prefix)) { + return true; + } + } + } elseif (in_array($tag, $storeTags)) { + return true; + } + } + + return false; + } + private function invalidateUrls($urls) { $cacher = app(Cacher::class); diff --git a/tests/Unit/AdditionalTrackerTest.php b/tests/Unit/AdditionalTrackerTest.php index edac4ec..e42f192 100644 --- a/tests/Unit/AdditionalTrackerTest.php +++ b/tests/Unit/AdditionalTrackerTest.php @@ -17,7 +17,7 @@ public function tracks_additional_closures() $this->get('/'); - $this->assertSame(['test::tag', 'pages:home'], collect(Tracker::all())->firstWhere('url', 'http://localhost')['tags']); + $this->assertSame(['test::tag', 'pages:home', 'collection:pages'], collect(Tracker::all())->firstWhere('url', 'http://localhost')['tags']); } #[Test] @@ -27,7 +27,7 @@ public function tracks_additional_classes() $this->get('/'); - $this->assertSame(['additional::tag', 'pages:home'], collect(Tracker::all())->firstWhere('url', 'http://localhost')['tags']); + $this->assertSame(['additional::tag', 'pages:home', 'collection:pages'], collect(Tracker::all())->firstWhere('url', 'http://localhost')['tags']); } } diff --git a/tests/Unit/TrackerTest.php b/tests/Unit/TrackerTest.php index 785295c..8886a0a 100644 --- a/tests/Unit/TrackerTest.php +++ b/tests/Unit/TrackerTest.php @@ -22,7 +22,7 @@ public function it_tracks_uncached_pages() $this->get('/'); - $this->assertSame(['test::tag', 'pages:home'], collect(Tracker::all())->first()['tags']); + $this->assertSame(['test::tag', 'pages:home', 'collection:pages'], collect(Tracker::all())->first()['tags']); Event::assertDispatched(ContentTracked::class, 1); } @@ -38,11 +38,11 @@ public function it_doesnt_track_already_cached_pages() $this->get('/'); - $this->assertSame(['test::tag', 'pages:home'], collect(Tracker::all())->first()['tags']); + $this->assertSame(['test::tag', 'pages:home', 'collection:pages'], collect(Tracker::all())->first()['tags']); $this->get('/'); - $this->assertSame(['test::tag', 'pages:home'], collect(Tracker::all())->first()['tags']); + $this->assertSame(['test::tag', 'pages:home', 'collection:pages'], collect(Tracker::all())->first()['tags']); Event::assertDispatched(ContentTracked::class, 1); } @@ -82,7 +82,7 @@ public function it_flushes() $this->get('/'); - $this->assertSame(['test::tag', 'pages:home'], collect(Tracker::all())->first()['tags']); + $this->assertSame(['test::tag', 'pages:home', 'collection:pages'], collect(Tracker::all())->first()['tags']); $this->assertCount(1, Tracker::all()); @@ -91,4 +91,94 @@ public function it_flushes() $this->assertCount(0, Tracker::all()); Event::assertDispatched(UrlInvalidated::class); } + + #[Test] + public function it_invalidates_by_exact_tag_match() + { + Tracker::add('/page1', ['products:1', 'category:electronics']); + Tracker::add('/page2', ['products:2', 'category:books']); + Tracker::add('/page3', ['products:3', 'category:electronics']); + + $this->assertCount(3, Tracker::all()); + + Tracker::invalidate(['products:1']); + + $this->assertCount(2, Tracker::all()); + $this->assertNull(Tracker::get('/page1')); + $this->assertNotNull(Tracker::get('/page2')); + $this->assertNotNull(Tracker::get('/page3')); + } + + #[Test] + public function it_invalidates_by_wildcard_tag() + { + Tracker::add('/page1', ['products:1', 'category:electronics']); + Tracker::add('/page2', ['products:2', 'category:books']); + Tracker::add('/page3', ['products:3', 'category:electronics']); + + $this->assertCount(3, Tracker::all()); + + Tracker::invalidate(['products:*']); + + $this->assertCount(0, Tracker::all()); + } + + #[Test] + public function it_invalidates_by_wildcard_tag_with_prefix() + { + Tracker::add('/page1', ['products:1', 'category:electronics']); + Tracker::add('/page2', ['products:2', 'category:books']); + Tracker::add('/page3', ['articles:1', 'category:electronics']); + + $this->assertCount(3, Tracker::all()); + + Tracker::invalidate(['products:*']); + + $this->assertCount(1, Tracker::all()); + $this->assertNull(Tracker::get('/page1')); + $this->assertNull(Tracker::get('/page2')); + $this->assertNotNull(Tracker::get('/page3')); + } + + #[Test] + public function it_invalidates_by_multiple_wildcard_tags() + { + Tracker::add('/page1', ['products:1', 'category:electronics']); + Tracker::add('/page2', ['products:2', 'category:books']); + Tracker::add('/page3', ['articles:1', 'author:john']); + Tracker::add('/page4', ['videos:1', 'author:jane']); + + $this->assertCount(4, Tracker::all()); + + Tracker::invalidate(['products:*', 'author:*']); + + $this->assertCount(0, Tracker::all()); + } + + #[Test] + public function it_invalidates_by_mixed_exact_and_wildcard_tags() + { + Tracker::add('/page1', ['products:1', 'category:electronics']); + Tracker::add('/page2', ['products:2', 'category:books']); + Tracker::add('/page3', ['articles:1', 'featured']); + + $this->assertCount(3, Tracker::all()); + + Tracker::invalidate(['products:*', 'featured']); + + $this->assertCount(0, Tracker::all()); + } + + #[Test] + public function it_doesnt_invalidate_when_wildcard_doesnt_match() + { + Tracker::add('/page1', ['products:1', 'category:electronics']); + Tracker::add('/page2', ['articles:1', 'category:books']); + + $this->assertCount(2, Tracker::all()); + + Tracker::invalidate(['videos:*']); + + $this->assertCount(2, Tracker::all()); + } }