Skip to content

Commit

Permalink
[4.x] Nocache performance improvements (#8956)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonvarga authored Nov 14, 2023
1 parent 5f1509d commit 4ef6169
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 20 deletions.
21 changes: 17 additions & 4 deletions src/StaticCaching/NoCache/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ public function setUrl(string $url)
*/
public function regions(): Collection
{
return $this->regions;
return $this->regions->mapWithKeys(fn ($key) => [$key => $this->region($key)]);
}

public function region(string $key): Region
{
if ($region = $this->regions[$key] ?? null) {
if ($this->regions->contains($key) && ($region = Cache::get('nocache::region.'.$key))) {
return $region;
}

Expand All @@ -57,14 +57,22 @@ public function pushRegion($contents, $context, $extension): StringRegion
{
$region = new StringRegion($this, trim($contents), $context, $extension);

return $this->regions[$region->key()] = $region;
$this->cacheRegion($region);

$this->regions[] = $region->key();

return $region;
}

public function pushView($view, $context): ViewRegion
{
$region = new ViewRegion($this, $view, $context);

return $this->regions[$region->key()] = $region;
$this->cacheRegion($region);

$this->regions[] = $region->key();

return $region;
}

public function cascade()
Expand Down Expand Up @@ -115,4 +123,9 @@ private function restoreCascade()
->hydrate()
->toArray();
}

private function cacheRegion(Region $region)
{
Cache::forever('nocache::region.'.$region->key(), $region);
}
}
16 changes: 10 additions & 6 deletions src/StaticCaching/NoCache/StringFragment.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,29 @@ public function render(): string
view()->addNamespace('nocache', $this->directory);
File::makeDirectory($this->directory);

$this->createTemporaryView();
$path = $this->createTemporaryView();

$this->data['__frontmatter'] = Arr::pull($this->data, 'view', []);

return view('nocache::'.$this->region, $this->data)->render();
$rendered = view('nocache::'.$this->region, $this->data)->render();

File::delete($path);

return $rendered;
}

private function createTemporaryView()
private function createTemporaryView(): string
{
$path = vsprintf('%s/%s.%s', [
$this->directory,
$this->region,
$this->extension,
]);

if (File::exists($path)) {
return;
if (! File::exists($path)) {
File::put($path, $this->contents);
}

File::put($path, $this->contents);
return $path;
}
}
10 changes: 9 additions & 1 deletion src/StaticCaching/NoCache/Tags.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Statamic\StaticCaching\NoCache;

use Statamic\Facades\Antlers;

class Tags extends \Statamic\Tags\Tags
{
public static $handle = 'nocache';
Expand All @@ -19,9 +21,15 @@ public function __construct(Session $nocache)

public function index()
{
if ($this->params->has('select')) {
$fields = $this->params->explode('select');
} else {
$fields = Antlers::identifiers($this->content);
}

return $this
->nocache
->pushRegion($this->content, $this->context->all(), 'antlers.html')
->pushRegion($this->content, $this->context->only($fields)->all(), 'antlers.html')
->placeholder();
}
}
4 changes: 3 additions & 1 deletion src/StaticCaching/StaticCacheManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ public function flush()
$this->driver()->flush();

collect(Cache::get('nocache::urls', []))->each(function ($url) {
Cache::forget('nocache::session.'.md5($url));
$session = Cache::get($sessionKey = 'nocache::session.'.md5($url));
collect($session['regions'] ?? [])->each(fn ($region) => Cache::forget('nocache::region.'.$region));
Cache::forget($sessionKey);
});

Cache::forget('nocache::urls');
Expand Down
6 changes: 6 additions & 0 deletions tests/StaticCaching/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public function it_flushes()
StaticCache::extend('test', fn () => $mock);

Cache::shouldReceive('get')->with('nocache::urls', [])->once()->andReturn(['/one', '/two']);
Cache::shouldReceive('get')->with('nocache::session.'.md5('/one'))->once()->andReturn(['regions' => ['r1', 'r2']]);
Cache::shouldReceive('get')->with('nocache::session.'.md5('/two'))->once()->andReturn(['regions' => ['r3', 'r4']]);
Cache::shouldReceive('forget')->with('nocache::region.r1')->once();
Cache::shouldReceive('forget')->with('nocache::region.r2')->once();
Cache::shouldReceive('forget')->with('nocache::region.r3')->once();
Cache::shouldReceive('forget')->with('nocache::region.r4')->once();
Cache::shouldReceive('forget')->with('nocache::session.'.md5('/one'))->once();
Cache::shouldReceive('forget')->with('nocache::session.'.md5('/two'))->once();
Cache::shouldReceive('forget')->with('nocache::urls')->once();
Expand Down
22 changes: 14 additions & 8 deletions tests/StaticCaching/NoCacheSessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests\StaticCaching;

use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Mockery;
use Statamic\StaticCaching\NoCache\Session;
Expand Down Expand Up @@ -75,6 +76,8 @@ public function it_gets_the_fragment_data()
/** @test */
public function it_writes()
{
Carbon::setTestNow('2014-02-15');

// Testing that the cache key used is unique to the url.
// The contents aren't really important.

Expand All @@ -101,6 +104,11 @@ public function it_writes()
->with('nocache::urls', ['/', '/foo'])
->once();

// When pushing regions, they will get written too...
Cache::shouldReceive('forever')
->withArgs(fn ($arg) => str_starts_with($arg, 'nocache::region.'))
->twice();

tap(new Session('/'), function ($session) {
$session->pushRegion('test', [], '.html');
})->write();
Expand All @@ -113,11 +121,10 @@ public function it_writes()
/** @test */
public function it_restores_from_cache()
{
Cache::forever('nocache::region.abc', $regionOne = Mockery::mock(StringRegion::class));
Cache::forever('nocache::region.def', $regionTwo = Mockery::mock(StringRegion::class));
Cache::forever('nocache::session.'.md5('http://localhost/test'), [
'regions' => [
$regionOne = Mockery::mock(StringRegion::class),
$regionTwo = Mockery::mock(StringRegion::class),
],
'regions' => ['abc', 'def'],
]);

$this->createPage('/test', [
Expand All @@ -130,7 +137,7 @@ public function it_restores_from_cache()

$session->restore();

$this->assertEquals([$regionOne, $regionTwo], $session->regions()->all());
$this->assertEquals(['abc' => $regionOne, 'def' => $regionTwo], $session->regions()->all());
$this->assertNotEquals([], $cascade = $session->cascade());
$this->assertEquals('/test', $cascade['url']);
$this->assertEquals('Test page', $cascade['title']);
Expand Down Expand Up @@ -202,10 +209,9 @@ public function it_restores_session_if_theres_a_nocache_placeholder_in_the_respo
$this->viewShouldReturnRendered('default', 'Hello <span class="nocache" data-nocache="abc">NOCACHE_PLACEHOLDER</span>');
$this->createPage('test');

Cache::forever('nocache::region.abc', $region = Mockery::mock(StringRegion::class));
Cache::put('nocache::session.'.md5('http://localhost/test'), [
'regions' => [
'abc' => $region = Mockery::mock(StringRegion::class),
],
'regions' => ['abc'],
]);

$region->shouldReceive('render')->andReturn('world');
Expand Down
54 changes: 54 additions & 0 deletions tests/StaticCaching/NocacheTagsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace Tests\StaticCaching;

use Mockery;
use Statamic\Facades\Parse;
use Statamic\StaticCaching\NoCache\Session;
use Statamic\StaticCaching\NoCache\StringRegion;
use Tests\FakesContent;
use Tests\FakesViews;
use Tests\PreventSavingStacheItemsToDisk;
Expand Down Expand Up @@ -99,4 +102,55 @@ public function it_can_keep_nested_nocache_tags_dynamic_inside_cache_tags()
->assertOk()
->assertSeeInOrder(['Updated', 'Updated', 'Existing', 'Updated']);
}

/** @test */
public function it_only_adds_appropriate_fields_of_context_to_session()
{
// We will not add `baz` to the session because it is not used in the template.
// We will not add `nope` to the session because it is not in the context.
$expectedFields = ['foo', 'bar'];
$template = '{{ nocache }}{{ foo }}{{ bar }}{{ nope }}{{ /nocache }}';
$context = [
'foo' => 'alfa',
'bar' => 'bravo',
'baz' => 'charlie',
];

$region = Mockery::mock(StringRegion::class)->shouldReceive('placeholder')->andReturn('the placeholder')->getMock();

$this->mock(Session::class, fn ($mock) => $mock
->shouldReceive('pushRegion')
->withArgs(fn ($arg1, $arg2, $arg3) => array_keys($arg2) === $expectedFields)
->once()->andReturn($region));

$this->assertEquals('the placeholder', $this->tag($template, $context));
}

/** @test */
public function it_only_adds_explicitly_defined_fields_of_context_to_session()
{
// We will not add `bar` to the session because it is not explicitly defined.
// We will not add `nope` to the session because it is not in the context.
$expectedFields = ['foo', 'baz'];
$template = '{{ nocache select="foo|baz|nope" }}{{ foo }}{{ bar }}{{ nope }}{{ /nocache }}';
$context = [
'foo' => 'alfa',
'bar' => 'bravo',
'baz' => 'charlie',
];

$region = Mockery::mock(StringRegion::class)->shouldReceive('placeholder')->andReturn('the placeholder')->getMock();

$this->mock(Session::class, fn ($mock) => $mock
->shouldReceive('pushRegion')
->withArgs(fn ($arg1, $arg2, $arg3) => array_keys($arg2) === $expectedFields)
->once()->andReturn($region));

$this->assertEquals('the placeholder', $this->tag($template, $context));
}

private function tag($tag, $data = [])
{
return (string) Parse::template($tag, $data);
}
}

0 comments on commit 4ef6169

Please sign in to comment.