From 6b63786c52d39135106c67f65deafaba1094c87e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 29 Apr 2024 15:56:35 -0400 Subject: [PATCH] [5.x] Cache entry uri (#9844) --- src/Entries/Entry.php | 20 ++++++++-- src/Stache/Repositories/EntryRepository.php | 2 + src/Stache/Stores/CollectionEntriesStore.php | 40 ++++++++++++++++++++ src/Stache/Stores/CollectionsStore.php | 17 +++++++-- tests/Antlers/Runtime/PartialsTest.php | 4 +- tests/Antlers/Runtime/RuntimeValuesTest.php | 20 +++++----- tests/Antlers/Runtime/TagCheckScopeTest.php | 7 ++-- tests/Data/Entries/EntryTest.php | 11 +++--- tests/Data/Structures/TreeTest.php | 2 +- tests/Feature/Entries/MountingTest.php | 12 +++--- tests/Stache/Stores/EntriesStoreTest.php | 6 +-- 11 files changed, 104 insertions(+), 37 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index e3a46ddd18..7950f9ece9 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -381,6 +381,7 @@ public function save() Facades\Entry::save($this); if ($this->id()) { + Blink::store('entry-uris')->forget($this->id()); Blink::store('structure-uris')->forget($this->id()); Blink::store('structure-entries')->forget($this->id()); Blink::forget($this->getOriginBlinkKey()); @@ -876,15 +877,23 @@ public function routeData() public function uri() { + if ($this->id() && Blink::store('entry-uris')->has($this->id())) { + return Blink::store('entry-uris')->get($this->id()); + } + if (! $this->route()) { return null; } - if ($structure = $this->structure()) { - return $structure->entryUri($this); + $uri = ($structure = $this->structure()) + ? $structure->entryUri($this) + : $this->routableUri(); + + if ($uri && $this->id()) { + Blink::store('entry-uris')->put($this->id(), $uri); } - return $this->routableUri(); + return $uri; } public function fileExtension() @@ -1011,6 +1020,11 @@ public function getQueryableValue(string $field) return $this->value('authors'); } + // Reset the cached uri so it gets recalculated. + if ($field === 'uri') { + Blink::store('entry-uris')->forget($this->id()); + } + if (method_exists($this, $method = Str::camel($field))) { return $this->{$method}(); } diff --git a/src/Stache/Repositories/EntryRepository.php b/src/Stache/Repositories/EntryRepository.php index be2d678544..511d8a7ee6 100644 --- a/src/Stache/Repositories/EntryRepository.php +++ b/src/Stache/Repositories/EntryRepository.php @@ -8,6 +8,7 @@ use Statamic\Entries\EntryCollection; use Statamic\Exceptions\CollectionNotFoundException; use Statamic\Exceptions\EntryNotFoundException; +use Statamic\Facades\Blink; use Statamic\Facades\Collection; use Statamic\Rules\Slug; use Statamic\Stache\Query\EntryQueryBuilder; @@ -151,6 +152,7 @@ public static function bindings(): array public function substitute($item) { + Blink::store('entry-uris')->forget($item->id()); $this->substitutionsById[$item->id()] = $item; $this->substitutionsByUri[$item->locale().'@'.$item->uri()] = $item; } diff --git a/src/Stache/Stores/CollectionEntriesStore.php b/src/Stache/Stores/CollectionEntriesStore.php index 5b61f7fe24..ff6df06611 100644 --- a/src/Stache/Stores/CollectionEntriesStore.php +++ b/src/Stache/Stores/CollectionEntriesStore.php @@ -2,10 +2,12 @@ namespace Statamic\Stache\Stores; +use Illuminate\Support\Facades\Cache; use Statamic\Entries\GetDateFromPath; use Statamic\Entries\GetSlugFromPath; use Statamic\Entries\GetSuffixFromPath; use Statamic\Entries\RemoveSuffixFromPath; +use Statamic\Facades\Blink; use Statamic\Facades\Collection; use Statamic\Facades\Entry; use Statamic\Facades\File; @@ -20,6 +22,7 @@ class CollectionEntriesStore extends ChildStore { protected $collection; + private bool $shouldBlinkEntryUris = true; protected function collection() { @@ -95,6 +98,10 @@ public function makeItemFromFile($path, $contents) $entry->date((new GetDateFromPath)($path)); } + // Blink the entry so that it can be used when building the URI. If it's not + // in there, it would try to retrieve the entry, which doesn't exist yet. + Blink::store('structure-entries')->put($id, $entry); + if (isset($idGenerated) || isset($positionGenerated)) { $this->writeItemToDiskWithoutIncrementing($entry); } @@ -217,4 +224,37 @@ protected function writeItemToDisk($item) $item->writeFile($path); } + + protected function cacheItem($item) + { + $key = $this->getItemKey($item); + + $cacheKey = $this->getItemCacheKey($key); + + Cache::forever($cacheKey, ['entry' => $item, 'uri' => $item->uri()]); + } + + protected function getCachedItem($key) + { + $cacheKey = $this->getItemCacheKey($key); + + if (! $cache = Cache::get($cacheKey)) { + return null; + } + + if ($this->shouldBlinkEntryUris && $cache['uri']) { + Blink::store('entry-uris')->put($cache['entry']->id(), $cache['uri']); + } + + return $cache['entry']; + } + + public function withoutBlinkingEntryUris($callback) + { + $this->shouldBlinkEntryUris = false; + $return = $callback(); + $this->shouldBlinkEntryUris = true; + + return $return; + } } diff --git a/src/Stache/Stores/CollectionsStore.php b/src/Stache/Stores/CollectionsStore.php index 9947adfc3d..b4ab6f94a7 100644 --- a/src/Stache/Stores/CollectionsStore.php +++ b/src/Stache/Stores/CollectionsStore.php @@ -84,11 +84,20 @@ protected function getDefaultPublishState($data) public function updateEntryUris($collection, $ids = null) { - $index = Stache::store('entries') - ->store($collection->handle()) - ->index('uri'); + $store = Stache::store('entries')->store($collection->handle()); + $this->updateEntriesWithinIndex($store->index('uri'), $ids); + $this->updateEntriesWithinStore($store, $ids); + } - $this->updateEntriesWithinIndex($index, $ids); + private function updateEntriesWithinStore($store, $ids) + { + if (empty($ids)) { + $ids = $store->paths()->keys(); + } + + $entries = $store->withoutBlinkingEntryUris(fn () => collect($ids)->map(fn ($id) => Entry::find($id))->filter()); + + $entries->each(fn ($entry) => $store->cacheItem($entry)); } public function updateEntryOrder($collection, $ids = null) diff --git a/tests/Antlers/Runtime/PartialsTest.php b/tests/Antlers/Runtime/PartialsTest.php index 8c7fa7ab53..99b5de565a 100644 --- a/tests/Antlers/Runtime/PartialsTest.php +++ b/tests/Antlers/Runtime/PartialsTest.php @@ -37,7 +37,7 @@ public function test_sections_work_inside_the_main_slot_content() { Collection::make('pages')->routes('{slug}')->save(); - EntryFactory::collection('pages')->id('1')->data(['title' => 'The Title', 'content' => 'The content'])->slug('/')->create(); + EntryFactory::collection('pages')->id('1')->data(['title' => 'The Title', 'content' => 'The content'])->slug('test')->create(); $layout = <<<'LAYOUT' {{ yield:test }} @@ -59,7 +59,7 @@ public function test_sections_work_inside_the_main_slot_content() $this->viewShouldReturnRaw('default', $default); $this->viewShouldReturnRaw('test', $partial); - $response = $this->get('/')->assertOk(); + $response = $this->get('test')->assertOk(); $content = trim(StringUtilities::normalizeLineEndings($response->content())); $expected = <<<'EXPECTED' diff --git a/tests/Antlers/Runtime/RuntimeValuesTest.php b/tests/Antlers/Runtime/RuntimeValuesTest.php index 8ee8ebf886..d8c627393a 100644 --- a/tests/Antlers/Runtime/RuntimeValuesTest.php +++ b/tests/Antlers/Runtime/RuntimeValuesTest.php @@ -20,16 +20,6 @@ public function test_supplemented_values_are_not_cached() { $this->withFakeViews(); - Collection::make('pages')->routes(['en' => '{slug}'])->save(); - EntryFactory::collection('pages')->id('1')->slug('home')->data(['title' => 'Home'])->create(); - EntryFactory::collection('pages')->id('2')->slug('about')->data(['title' => 'About'])->create(); - - $template = <<<'EOT' -{{ title }} - -{{ dont_cache:me_please }}{{ foo }}{{ /dont_cache:me_please }} -EOT; - $instance = (new class extends Tags { public static $handle = 'dont_cache'; @@ -49,6 +39,16 @@ public function mePlease() $instance::register(); + Collection::make('pages')->routes(['en' => '{slug}'])->save(); + EntryFactory::collection('pages')->id('1')->slug('home')->data(['title' => 'Home'])->create(); + EntryFactory::collection('pages')->id('2')->slug('about')->data(['title' => 'About'])->create(); + + $template = <<<'EOT' +{{ title }} + +{{ dont_cache:me_please }}{{ foo }}{{ /dont_cache:me_please }} +EOT; + $this->viewShouldReturnRaw('default', $template); $this->viewShouldReturnRaw('layout', '{{ template_content }}'); diff --git a/tests/Antlers/Runtime/TagCheckScopeTest.php b/tests/Antlers/Runtime/TagCheckScopeTest.php index f58c5c035c..90cb15dc1a 100644 --- a/tests/Antlers/Runtime/TagCheckScopeTest.php +++ b/tests/Antlers/Runtime/TagCheckScopeTest.php @@ -132,9 +132,6 @@ public function test_node_processor_does_not_trash_scope_when_checking_if_someth public function test_condition_augmentation_doesnt_reset_up_the_scope() { - $this->createData(); - $this->withFakeViews(); - (new class extends Tags { public static $handle = 'just_a_tag'; @@ -144,6 +141,10 @@ public function index() return []; } })::register(); + + $this->createData(); + $this->withFakeViews(); + $template = <<<'EOT' {{ just_a_tag }} diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index dbd2e27513..4b1bdc26f5 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -637,8 +637,6 @@ public function a_localized_entry_in_a_structured_collection_without_a_route_for */ public function it_gets_urls_for_first_child_redirects($value) { - \Event::fake(); // Don't invalidate static cache etc when saving entries. - $this->setSites([ 'en' => ['url' => 'http://domain.com/', 'locale' => 'en_US'], ]); @@ -1356,7 +1354,7 @@ public function when_saving_quietly_the_cached_entrys_withEvents_flag_will_be_se $entry->saveQuietly(); - $cached = Cache::get('stache::items::entries::blog::1'); + $cached = Cache::get('stache::items::entries::blog::1')['entry']; $reflection = new ReflectionClass($cached); $property = $reflection->getProperty('withEvents'); $property->setAccessible(true); @@ -1375,8 +1373,11 @@ public function it_clears_blink_caches_when_saving() $mock->shouldReceive('store')->with('structure-uris')->once()->andReturn( $this->mock(\Spatie\Blink\Blink::class)->shouldReceive('forget')->with('a')->once()->getMock() ); - $mock->shouldReceive('store')->with('structure-entries')->once()->andReturn( - $this->mock(\Spatie\Blink\Blink::class)->shouldReceive('forget')->with('a')->once()->getMock() + $mock->shouldReceive('store')->with('structure-entries')->twice()->andReturn( + tap($this->mock(\Spatie\Blink\Blink::class), function ($m) { + $m->shouldReceive('forget')->with('a')->once(); + $m->shouldReceive('put')->once(); + }) ); $entry->save(); diff --git a/tests/Data/Structures/TreeTest.php b/tests/Data/Structures/TreeTest.php index 101bc90f9f..34e9511936 100644 --- a/tests/Data/Structures/TreeTest.php +++ b/tests/Data/Structures/TreeTest.php @@ -99,7 +99,7 @@ public function it_gets_the_parent() $parent = $tree->parent(); $this->assertInstanceOf(Page::class, $parent); - $this->assertEquals(Entry::find('pages-home'), $parent->entry()); + $this->assertEquals(Entry::find('pages-home')->id(), $parent->entry()->id()); } /** @test */ diff --git a/tests/Feature/Entries/MountingTest.php b/tests/Feature/Entries/MountingTest.php index 54800821e7..4915bb164a 100644 --- a/tests/Feature/Entries/MountingTest.php +++ b/tests/Feature/Entries/MountingTest.php @@ -28,15 +28,15 @@ public function updating_a_mounted_page_will_update_the_uris_for_each_entry_in_t $one = EntryFactory::collection('blog')->slug('one')->create(); $two = EntryFactory::collection('blog')->slug('two')->create(); - $this->assertEquals($one, Entry::findByUri('/pages/blog/one')); - $this->assertEquals($two, Entry::findByUri('/pages/blog/two')); + $this->assertEquals($one->id(), Entry::findByUri('/pages/blog/one')->id()); + $this->assertEquals($two->id(), Entry::findByUri('/pages/blog/two')->id()); $mount->slug('diary')->save(); $this->assertNull(Entry::findByUri('/pages/blog/one')); $this->assertNull(Entry::findByUri('/pages/blog/two')); - $this->assertEquals($one, Entry::findByUri('/pages/diary/one')); - $this->assertEquals($two, Entry::findByUri('/pages/diary/two')); + $this->assertEquals($one->id(), Entry::findByUri('/pages/diary/one')->id()); + $this->assertEquals($two->id(), Entry::findByUri('/pages/diary/two')->id()); } /** @test */ @@ -54,8 +54,8 @@ public function updating_a_mounted_page_will_not_update_the_uris_when_slug_is_cl $one = EntryFactory::collection('blog')->slug('one')->create(); $two = EntryFactory::collection('blog')->slug('two')->create(); - $this->assertEquals($one, Entry::findByUri('/pages/blog/one')); - $this->assertEquals($two, Entry::findByUri('/pages/blog/two')); + $this->assertEquals($one->id(), Entry::findByUri('/pages/blog/one')->id()); + $this->assertEquals($two->id(), Entry::findByUri('/pages/blog/two')->id()); // Since we're just saving the mount without changing the slug, we don't want to update the URIs. $mock = \Mockery::mock(Collection::getFacadeRoot())->makePartial(); diff --git a/tests/Stache/Stores/EntriesStoreTest.php b/tests/Stache/Stores/EntriesStoreTest.php index 4191f810c4..c9f446eaf2 100644 --- a/tests/Stache/Stores/EntriesStoreTest.php +++ b/tests/Stache/Stores/EntriesStoreTest.php @@ -106,7 +106,7 @@ public function it_makes_entry_instances_from_files() public function if_slugs_are_not_required_the_filename_still_becomes_the_slug() { Facades\Collection::shouldReceive('findByHandle')->with('blog')->andReturn( - (new \Statamic\Entries\Collection)->requiresSlugs(false) + (new \Statamic\Entries\Collection)->handle('blog')->requiresSlugs(false) ); $item = $this->parent->store('blog')->makeItemFromFile( @@ -122,7 +122,7 @@ public function if_slugs_are_not_required_the_filename_still_becomes_the_slug() public function if_slugs_are_not_required_and_the_filename_is_the_same_as_the_id_then_slug_is_null() { Facades\Collection::shouldReceive('findByHandle')->with('blog')->andReturn( - (new \Statamic\Entries\Collection)->requiresSlugs(false) + (new \Statamic\Entries\Collection)->handle('blog')->requiresSlugs(false) ); $item = $this->parent->store('blog')->makeItemFromFile( @@ -138,7 +138,7 @@ public function if_slugs_are_not_required_and_the_filename_is_the_same_as_the_id public function if_slugs_are_required_and_the_filename_is_the_same_as_the_id_then_slug_is_the_id() { Facades\Collection::shouldReceive('findByHandle')->with('blog')->andReturn( - (new \Statamic\Entries\Collection)->requiresSlugs(true) + (new \Statamic\Entries\Collection)->handle('blog')->requiresSlugs(true) ); $item = $this->parent->store('blog')->makeItemFromFile(