We have a Silverstripe 4 project which acts as a headless CMS returning a group of complex data models formatted as JSON.
Here's an example of the code:
class APIController extends ContentController
{
public function index(HTTPRequest $request)
{
$dataArray['model1'] = AccessPointController::getModel1();
$dataArray['model2'] = AccessPointController::getModel2();
$dataArray['model3'] = AccessPointController::getModel3();
$dataArray['model4'] = AccessPointController::getModel4();
$dataArray['model5'] = AccessPointController::getModel5();
$dataArray['model6'] = AccessPointController::getModel6();
$this->response->addHeader('Content-Type', 'application/json');
$this->response->addHeader('Access-Control-Allow-Origin', '*');
return json_encode($dataArray);
}
The problem we're having is the data models have got so complex the generation time for the JSON is running into seconds.
The JSON should only change when site content has been updates so ideally we'd like to cache the JSON & rather than dynamically generating it for each call.
What is the best way to cache the JSON in the above example?
Have you looked at the silverstripe docs about caching?
They do provide a programmatic way to store things in cache. And configuration options what back-ends are to be used to store the cache.
A simple example might be:
I've extended the cache live time here, but still you should note that this cache is not intended for storing generated static content, but rather to reduce load. Your application will still have to compute the api response every 86400 seconds (24 hours).
# app/_config/apiCache.yml
---
Name: apicacheconfig
---
# [... rest of your config config ...]
SilverStripe\Core\Injector\Injector:
Psr\SimpleCache\CacheInterface.apiResponseCache:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "apiResponseCache"
defaultLifetime: 86400
<?php // app/src/FooController.php
class FooController extends \SilverStripe\Control\Controller {
public function getCache() {
return Injector::inst()->get('Psr\SimpleCache\CacheInterface.apiResponseCache');
// or your own cache (see below):
// return new MyCache();
}
protected function hasDataBeenChanged() {
// alternative to this method, you could also simply include Page::get()->max('LastEdited') or whatever in your cache key inside index(), but if you are using your own cache system, you need to handle deleting of old unused cache files. If you are using SilverStripe's cache, it will do that for you
$c = $this->getCache();
$lastCacheTime = $c->has('cacheTime') ? (int)$c->get('cacheTime') : 0;
$lastDataChangeTime = strtotime(Page::get()->max('LastEdited'));
return $lastDataChangeTime > $lastCacheTime;
}
public function index() {
$c = $this->getCache();
$cacheKey = 'indexActionResponse';
if ($c->has($cacheKey) && !$this->hasDataBeenChanged()) {
$data = $c->get($cacheKey);
} else {
$dataArray['model1'] = AccessPointController::getModel1();
$dataArray['model2'] = AccessPointController::getModel2();
$dataArray['model3'] = AccessPointController::getModel3();
$dataArray['model4'] = AccessPointController::getModel4();
$dataArray['model5'] = AccessPointController::getModel5();
$dataArray['model6'] = AccessPointController::getModel6();
$data = json_encode($dataArray);
$c->set($cacheKey, $data);
$c->set('cacheTime', time());
}
$this->response->addHeader('Content-Type', 'application/json');
$this->response->addHeader('Access-Control-Allow-Origin', '*');
return json_encode($dataArray);
}
}
If you are looking for a permanent/persistent cache, that will only ever update when data changed, I suggest you look for a different back-end or just implement a simple cache yourself and use that instead of the silverstripe cache.
class MyCache {
protected function fileName($key) {
if (strpos($key, '/') !== false || strpos($key, '\\') !== false) {
throw new \Exception("Invalid cache key '$key'");
}
return BASE_PATH . "/api-cache/$key.json";
}
public function get($key) {
if ($this->has($key)) {
return file_get_contents($this->fileName($key));
}
return null;
}
public function set($key, $val) {
file_put_contents($this->fileName($key), $val);
}
public function has($key) {
$f = $this->fileName($key);
return @file_exists($f);
}