Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.x] Cache entry uri #9844

Merged
merged 18 commits into from Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 17 additions & 3 deletions src/Entries/Entry.php
Expand Up @@ -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());
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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}();
}
Expand Down
2 changes: 2 additions & 0 deletions src/Stache/Repositories/EntryRepository.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
40 changes: 40 additions & 0 deletions src/Stache/Stores/CollectionEntriesStore.php
Expand Up @@ -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;
Expand All @@ -20,6 +22,7 @@
class CollectionEntriesStore extends ChildStore
{
protected $collection;
private bool $shouldBlinkEntryUris = true;

protected function collection()
{
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
}
17 changes: 13 additions & 4 deletions src/Stache/Stores/CollectionsStore.php
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions tests/Antlers/Runtime/PartialsTest.php
Expand Up @@ -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 }}
Expand All @@ -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'
Expand Down
20 changes: 10 additions & 10 deletions tests/Antlers/Runtime/RuntimeValuesTest.php
Expand Up @@ -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';
Expand All @@ -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 }}');

Expand Down
7 changes: 4 additions & 3 deletions tests/Antlers/Runtime/TagCheckScopeTest.php
Expand Up @@ -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';
Expand All @@ -144,6 +141,10 @@ public function index()
return [];
}
})::register();

$this->createData();
$this->withFakeViews();

$template = <<<'EOT'

{{ just_a_tag }}
Expand Down
11 changes: 6 additions & 5 deletions tests/Data/Entries/EntryTest.php
Expand Up @@ -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'],
]);
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion tests/Data/Structures/TreeTest.php
Expand Up @@ -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 */
Expand Down
12 changes: 6 additions & 6 deletions tests/Feature/Entries/MountingTest.php
Expand Up @@ -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 */
Expand All @@ -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();
Expand Down
6 changes: 3 additions & 3 deletions tests/Stache/Stores/EntriesStoreTest.php
Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand Down