From 0cfc2e11daf0063f1ff25754e2527269da5b00c8 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 8 Apr 2024 16:09:01 -0400 Subject: [PATCH 01/12] allow caching of uri to property, and set it up front so it gets cached. --- src/Entries/Entry.php | 9 +++++++-- src/Stache/Stores/CollectionEntriesStore.php | 7 +++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index f3cfe04769..9f024742f0 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -80,6 +80,7 @@ class Entry implements Arrayable, ArrayAccess, Augmentable, ContainsQueryableVal protected $layout; private $computedCallbackCache; private $siteCache; + private $uri; public function __construct() { @@ -861,15 +862,19 @@ public function routeData() public function uri() { + if ($this->uri) { + return $this->uri; + } + if (! $this->route()) { return null; } if ($structure = $this->structure()) { - return $structure->entryUri($this); + return $this->uri = $structure->entryUri($this); } - return $this->routableUri(); + return $this->uri = $this->routableUri(); } public function fileExtension() diff --git a/src/Stache/Stores/CollectionEntriesStore.php b/src/Stache/Stores/CollectionEntriesStore.php index a1da08c3b5..023afe99cb 100644 --- a/src/Stache/Stores/CollectionEntriesStore.php +++ b/src/Stache/Stores/CollectionEntriesStore.php @@ -6,6 +6,7 @@ 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; @@ -95,6 +96,12 @@ 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. Then build the URI now so that it gets placed + // into the property and stored in the cache, and we don't have to repeatedly re-build it. + Blink::store('structure-entries')->put($id, $entry); + $entry->uri(); + if (isset($idGenerated) || isset($positionGenerated)) { $this->writeItemToDiskWithoutIncrementing($entry); } From e0d2e8602b4ff5c7e8b02ce6af8e89305141b34a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Apr 2024 18:34:17 -0400 Subject: [PATCH 02/12] use blink and reset appropriately --- src/Entries/Entry.php | 21 +++++++++++++++------ src/Stache/Repositories/EntryRepository.php | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 1f456f3541..7950f9ece9 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -80,7 +80,6 @@ class Entry implements Arrayable, ArrayAccess, Augmentable, ContainsQueryableVal protected $layout; private $computedCallbackCache; private $siteCache; - private $uri; public function __construct() { @@ -382,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()); @@ -877,19 +877,23 @@ public function routeData() public function uri() { - if ($this->uri) { - return $this->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 $this->uri = $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->uri = $this->routableUri(); + return $uri; } public function fileExtension() @@ -1016,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; } From add8255879d0773c106e7c87e003a5afa5995bd6 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Apr 2024 18:34:27 -0400 Subject: [PATCH 03/12] mock --- tests/Data/Entries/EntryTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index 833b0dc3fd..b32fcc9787 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -1363,8 +1363,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(); From 88d1159889b869055a5436e2477c144c51cd8494 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Apr 2024 18:34:58 -0400 Subject: [PATCH 04/12] compare ids since the objects arent technically identical (computedCacheCallback property is different) --- tests/Data/Structures/TreeTest.php | 2 +- tests/Feature/Entries/MountingTest.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) 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..5353ea4046 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 */ From 7ae2be0239d20701ddbe05dbe912d81991ffa332 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Apr 2024 18:35:20 -0400 Subject: [PATCH 05/12] set the handles otherwise you get an error when it tries to do a lookup now --- tests/Stache/Stores/EntriesStoreTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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( From 1deddc3dd1cdcdbb1b0d26fde245c19e15d5b969 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 23 Apr 2024 23:06:07 -0400 Subject: [PATCH 06/12] cache the uri along with the entry. --- src/Stache/Stores/CollectionEntriesStore.php | 31 +++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Stache/Stores/CollectionEntriesStore.php b/src/Stache/Stores/CollectionEntriesStore.php index e84f3dc979..ca6d0a563c 100644 --- a/src/Stache/Stores/CollectionEntriesStore.php +++ b/src/Stache/Stores/CollectionEntriesStore.php @@ -2,6 +2,7 @@ namespace Statamic\Stache\Stores; +use Illuminate\Support\Facades\Cache; use Statamic\Entries\GetDateFromPath; use Statamic\Entries\GetSlugFromPath; use Statamic\Entries\GetSuffixFromPath; @@ -96,11 +97,9 @@ 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. Then build the URI now so that it gets placed - // into the property and stored in the cache, and we don't have to repeatedly re-build it. + // 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); - $entry->uri(); if (isset($idGenerated) || isset($positionGenerated)) { $this->writeItemToDiskWithoutIncrementing($entry); @@ -224,4 +223,28 @@ 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 ($cache['uri']) { + Blink::store('entry-uris')->put($cache['entry']->id(), $cache['uri']); + } + + return $cache['entry']; + } } From 6ba8099425083ef49a0d50177a8ce23b58c3696e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 29 Apr 2024 12:45:30 -0400 Subject: [PATCH 07/12] Fix test by moving the tag before content creation ... When the uri is evaluated, the parser caches the tag list. Since the tag isn't available at that point, when we try to use it in the view, it's missing. Moving the tag registration earlier makes it available when the parser does the uri. It's more reflective of a real life solution anyway. Tags would be registered early. --- tests/Antlers/Runtime/RuntimeValuesTest.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 }}'); From 066416845e842b106a1326f3cd1b306020507153 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 29 Apr 2024 13:00:56 -0400 Subject: [PATCH 08/12] ditto --- tests/Antlers/Runtime/TagCheckScopeTest.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 }} From 972ee903fde71acafa7bbfd2397f983486aacec1 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 29 Apr 2024 13:06:37 -0400 Subject: [PATCH 09/12] the cached item is now an array and the entry is one of the values. --- tests/Data/Entries/EntryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index 0483dd52d4..8d5ad4d03e 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -1356,7 +1356,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); From 1a76cffd7714b80eae0011dac3de89dd5b01167e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 29 Apr 2024 13:52:03 -0400 Subject: [PATCH 10/12] Fix this test ... First of all, a slug of just a slash is a bit weird. Surprising it worked to begin with. Second, now that when generating the uri, it might end up reading the file again. Since the collectino created a line above requires slugs by default, it took the filename which was just the id, and used it as a slug. When we did ->get('/') it would 404 because it thinks the uri is actually /1. Easy fix is to just use an explicit not-a-slash slug. --- tests/Antlers/Runtime/PartialsTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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' From f74001831108a56f41d49072ac0a1186f869cf98 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 29 Apr 2024 13:56:37 -0400 Subject: [PATCH 11/12] compare ids. the objects might be have different properties - like the computedCallbackCache for example --- tests/Feature/Entries/MountingTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Feature/Entries/MountingTest.php b/tests/Feature/Entries/MountingTest.php index 5353ea4046..4915bb164a 100644 --- a/tests/Feature/Entries/MountingTest.php +++ b/tests/Feature/Entries/MountingTest.php @@ -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(); From 0f90874e072c7ff83149318853995f2e069cc11e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Mon, 29 Apr 2024 14:43:56 -0400 Subject: [PATCH 12/12] when entries are saved, update them in the stache --- src/Stache/Stores/CollectionEntriesStore.php | 12 +++++++++++- src/Stache/Stores/CollectionsStore.php | 17 +++++++++++++---- tests/Data/Entries/EntryTest.php | 2 -- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Stache/Stores/CollectionEntriesStore.php b/src/Stache/Stores/CollectionEntriesStore.php index ca6d0a563c..ff6df06611 100644 --- a/src/Stache/Stores/CollectionEntriesStore.php +++ b/src/Stache/Stores/CollectionEntriesStore.php @@ -22,6 +22,7 @@ class CollectionEntriesStore extends ChildStore { protected $collection; + private bool $shouldBlinkEntryUris = true; protected function collection() { @@ -241,10 +242,19 @@ protected function getCachedItem($key) return null; } - if ($cache['uri']) { + 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/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index 8d5ad4d03e..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'], ]);