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

API Caching #3168

Merged
merged 31 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3faeb6a
Add default API caching config.
jesseleite Jan 27, 2021
6cd38e2
Move API service provider into new API namespace.
jesseleite Jan 27, 2021
23aa6d9
Add basic API cache manager.
jesseleite Jan 27, 2021
53e04d8
Add basic Cacher interface.
jesseleite Jan 27, 2021
c6186b8
Add dummy NullCacher.
jesseleite Jan 27, 2021
8013043
Implement basic DefaultCacher, with extendable AbstractCacher class.
jesseleite Jan 27, 2021
a115596
Bind API cache manager.
jesseleite Jan 27, 2021
580f785
Cache collection entries API endpoints.
jesseleite Jan 27, 2021
83b35e3
Fix `UserSaved` event firing before setting initial button options.
jesseleite Jan 27, 2021
a83b06f
Extract content events prop for reuse.
jesseleite Jan 27, 2021
d7347c0
Add subcriber for handling event-based API cache invalidation.
jesseleite Jan 27, 2021
400082c
Handle API cache validation events.
jesseleite Jan 27, 2021
f277a46
Merge branch 'master' of https://github.com/statamic/cms into feature…
jesseleite Jan 27, 2021
6d97fba
Not passing in cache repository instance at the moment.
jesseleite Jan 27, 2021
d26a6b5
Cleanup.
jesseleite Jan 27, 2021
e12f5e8
Add basic API Cacher test case.
jesseleite Jan 27, 2021
cef1593
Test configured expiry.
jesseleite Jan 28, 2021
2fe8253
Ensure default API cacher converts resources to responses.
jesseleite Jan 28, 2021
c3bb9be
Move response helper to parent `API\AbstractCacher`.
jesseleite Jan 28, 2021
49adf88
Reverting.
jesseleite Jan 28, 2021
237543c
Use wrapper method for getting the response
jasonvarga Jan 28, 2021
05a54a6
Extract helper for getting common query params.
jesseleite Jan 28, 2021
e777ae4
We already have the request here.
jesseleite Jan 28, 2021
1c917b9
Docblock.
jesseleite Jan 28, 2021
5e0569e
Wrap all API endpoints `withCache()`.
jesseleite Jan 28, 2021
1b1c508
Pass tests again.
jesseleite Jan 28, 2021
87356b2
Avoid injecting request into controllers.
jesseleite Jan 29, 2021
ebc029d
Subscribe within boot
jasonvarga Jan 29, 2021
4f2cf9b
Cache 404s
jasonvarga Jan 29, 2021
e963600
Refactor to a middleware
jasonvarga Jan 29, 2021
41d7244
Unwrap controller responses
jasonvarga Jan 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,21 @@

'pagination_size' => 50,

/*
|--------------------------------------------------------------------------
| Caching
|--------------------------------------------------------------------------
|
| By default, Statamic will cache each endpoint until the specified
| expiry, or until content is changed. See the documentation for
| more details on how to customize your cache implementation.
|
| https://statamic.dev/content-api#caching
|
*/

'cache' => [
'expiry' => 60,
],

];
6 changes: 2 additions & 4 deletions resources/js/components/publish/SaveButtonOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,10 @@ export default {
},
},

watch: {
currentOption: 'setPreference',
},

mounted() {
this.setInitialValue();

this.$watch('currentOption', value => this.setPreference(value));
},

methods: {
Expand Down
2 changes: 2 additions & 0 deletions routes/routes.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Illuminate\Support\Facades\Route;
use Statamic\API\Middleware\Cache;
use Statamic\Http\Middleware\API\SwapExceptionHandler as SwapAPIExceptionHandler;
use Statamic\Http\Middleware\CP\SwapExceptionHandler as SwapCpExceptionHandler;
use Statamic\Http\Middleware\RequireStatamicPro;
Expand All @@ -9,6 +10,7 @@
Route::middleware([
SwapApiExceptionHandler::class,
RequireStatamicPro::class,
Cache::class,
])->group(function () {
Route::middleware(config('statamic.api.middleware'))
->name('statamic.api.')
Expand Down
60 changes: 60 additions & 0 deletions src/API/AbstractCacher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace Statamic\API;

use Illuminate\Support\Carbon;

abstract class AbstractCacher implements Cacher
{
/**
* @var \Illuminate\Support\Collection
*/
private $config;

/**
* Create cacher instance.
*
* @param array $config
*/
public function __construct($config)
{
$this->config = collect($config);
}

/**
* Get a config value.
*
* @param mixed $key
* @param mixed $default
* @return mixed
*/
public function config($key = null, $default = null)
{
if (is_null($key)) {
return $this->config;
}

return $this->config->get($key, $default);
}

/**
* Prefix a cache key.
*
* @param string $key
* @return string
*/
protected function normalizeKey($key)
{
return "api-cache:$key";
}

/**
* Get cache expiry.
*
* @return \Carbon\Carbon
*/
public function cacheExpiry()
{
return Carbon::now()->addMinutes($this->config('expiry'));
}
}
68 changes: 68 additions & 0 deletions src/API/ApiCacheManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Statamic\API;

use InvalidArgumentException;
use Statamic\API\Cachers\DefaultCacher;
use Statamic\API\Cachers\NullCacher;
use Statamic\Support\Manager;

class ApiCacheManager extends Manager
{
public function getDefaultDriver()
{
$config = $this->app['config']['statamic.api.cache'];
$class = $this->app['config']['statamic.api.cache.class'];

switch (true) {
case $config === false:
case $class === false:
return false;
case is_string($class):
return $class;
default:
return DefaultCacher::class;
}
}

public function createNullDriver()
{
return new NullCacher;
}

public function createClassDriver(string $driverClass, array $config)
{
return new $driverClass($config);
}

protected function getConfig($name)
{
if (! $config = $this->app['config']['statamic.api.cache']) {
return null;
}

return collect($config)
->except('class')
->all();
}

protected function resolve($driver)
{
if (! $driver) {
return $this->createNullDriver();
}

$config = $this->getConfig($driver);

if (is_null($config)) {
throw new InvalidArgumentException($this->invalidImplementationMessage($driver));
}

return $this->createClassDriver($driver, $config);
}

protected function invalidImplementationMessage($name)
{
return "Api cache config for [{$name}] is not properly defined.";
}
}
35 changes: 35 additions & 0 deletions src/API/Cacher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Statamic\API;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Statamic\Events\Event;

interface Cacher
{
/**
* Get a response from the cache.
*
* @param Request $request
* @return JsonResponse|null
*/
public function get(Request $request);

/**
* Put a response into the cache.
*
* @param Request $request
* @param JsonResponse $response
* @return void
*/
public function put(Request $request, JsonResponse $response);

/**
* Handle event based API cache invalidation.
*
* @param Event $event
* @return void
*/
public function handleInvalidationEvent(Event $event);
}
103 changes: 103 additions & 0 deletions src/API/Cachers/DefaultCacher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace Statamic\API\Cachers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Statamic\API\AbstractCacher;
use Statamic\Events\Event;

class DefaultCacher extends AbstractCacher
{
/**
* {@inheritdoc}
*/
public function get(Request $request)
{
return Cache::get($this->getCacheKey($request));
}

/**
* {@inheritdoc}
*/
public function put(Request $request, JsonResponse $response)
{
$key = $this->trackEndpoint($request);

Cache::put($key, $response, $this->cacheExpiry());
}

/**
* Handle event based API cache invalidation.
*
* @param Event $event
* @return void
*/
public function handleInvalidationEvent(Event $event)
{
$this->getTrackedResponses()->each(function ($key) {
Cache::forget($key);
});

Cache::forget($this->getTrackingKey());
}

/**
* Track endpoint and return new cache key.
*
* @param Request $request
* @return string
*/
protected function trackEndpoint($request)
{
$newKey = $this->getCacheKey($request);

$keys = $this
->getTrackedResponses()
->push($newKey)
->unique()
->values()
->all();

Cache::put($this->getTrackingKey(), $keys);

return $newKey;
}

/**
* Get tracked responses.
*
* @return \Illuminate\Support\Collection
*/
protected function getTrackedResponses()
{
return collect(Cache::get($this->getTrackingKey(), []));
}

/**
* Get tracking cache key for storing invalidatable endpoints.
*
* @return string
*/
protected function getTrackingKey()
{
return $this->normalizeKey('tracked-responses');
}

/**
* Get cache key for endpoint.
*
* @param Request $request
* @return string
*/
protected function getCacheKey(Request $request)
{
$domain = $request->root();
$fullUrl = $request->fullUrl();

$key = str_replace($domain, '', $fullUrl);

return $this->normalizeKey($key);
}
}
26 changes: 26 additions & 0 deletions src/API/Cachers/NullCacher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Statamic\API\Cachers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Statamic\API\Cacher;
use Statamic\Events\Event;

class NullCacher implements Cacher
{
public function get(Request $request)
{
//
}

public function put(Request $request, JsonResponse $response)
{
//
}

public function handleInvalidationEvent(Event $event)
{
//
}
}
48 changes: 48 additions & 0 deletions src/API/Middleware/Cache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace Statamic\API\Middleware;

use Closure;
use Statamic\API\Cacher;
use Statamic\Exceptions\NotFoundHttpException;

class Cache
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$cacher = app(Cacher::class);

if ($response = $cacher->get($request)) {
return $response;
}

$response = $next($request);

if ($this->shouldBeCached($response)) {
$cacher->put($request, $this->cleanResponse($response));
}

return $response;
}

private function shouldBeCached($response)
{
return $response->isOk() || $response->isNotFound();
}

private function cleanResponse($response)
{
if ($response->exception instanceof NotFoundHttpException) {
$response->exception = null;
}

return $response;
}
}
Loading